Estoy experimentando con MATLAB programación orientada a objetos , como un principio he imitado mi C ++ 's clases Logger y yo estoy poniendo todas mis funciones de ayuda de cuerdas en una clase String, pensando que sería genial ser capaz de hacer cosas como a + b
, a == b
, a.find( b )
en lugar de strcat( a b )
, strcmp( a, b )
, recuperar el primer elemento de strfind( a, b )
, etc.
El problema: desaceleración
Puse las cosas anteriores para usar e inmediatamente noté una drástica desaceleración. ¿Lo estoy haciendo mal?
Mi caso de prueba
Aquí está la prueba simple que hice para la cadena, básicamente agregando una cadena y eliminando la parte adjunta nuevamente:
Nota: ¡No escriba una clase de cadena como esta en código real! Matlab tiene un
string
tipo de matriz nativo ahora, y debería usarlo en su lugar.
classdef String < handle
....
properties
stringobj = '';
end
function o = plus( o, b )
o.stringobj = [ o.stringobj b ];
end
function n = Length( o )
n = length( o.stringobj );
end
function o = SetLength( o, n )
o.stringobj = o.stringobj( 1 : n );
end
end
function atest( a, b ) %plain functions
n = length( a );
a = [ a b ];
a = a( 1 : n );
function btest( a, b ) %OOP
n = a.Length();
a = a + b;
a.SetLength( n );
function RunProfilerLoop( nLoop, fun, varargin )
profile on;
for i = 1 : nLoop
fun( varargin{ : } );
end
profile off;
profile report;
a = 'test';
aString = String( 'test' );
RunProfilerLoop( 1000, @(x,y)atest(x,y), a, 'appendme' );
RunProfilerLoop( 1000, @(x,y)btest(x,y), aString, 'appendme' );
Los resultados
Tiempo total en segundos, para 1000 iteraciones:
btest 0.550 (con String.SetLength 0.138, String.plus 0.065, String.Length 0.057)
atest 0.015
Los resultados para el sistema de registro también son: 0.1 segundos para 1000 llamadas a frpintf( 1, 'test\n' )
, 7 (!) Segundos para 1000 llamadas a mi sistema cuando uso la clase String internamente (OK, tiene mucha más lógica, pero para comparar con C ++: la sobrecarga de mi sistema que usa std::string( "blah" )
y std::cout
en el lado de salida vs simple std::cout << "blah"
está en el orden de 1 milisegundo).
¿Es solo una sobrecarga al buscar funciones de clase / paquete?
Dado que MATLAB se interpreta, debe buscar la definición de una función / objeto en tiempo de ejecución. Entonces, me preguntaba que tal vez haya mucha más sobrecarga al buscar la función de clase o paquete frente a las funciones que están en la ruta. Traté de probar esto, y se vuelve más extraño. Para descartar la influencia de clases / objetos, comparé llamar a una función en la ruta frente a una función en un paquete:
function n = atest( x, y )
n = ctest( x, y ); % ctest is in matlab path
function n = btest( x, y )
n = util.ctest( x, y ); % ctest is in +util directory, parent directory is in path
Resultados, reunidos de la misma manera que arriba:
atest 0.004 sec, 0.001 sec en ctest
btest 0.060 sec, 0.014 sec en util.ctest
Entonces, ¿toda esta sobrecarga proviene de que MATLAB pasa tiempo buscando definiciones para su implementación OOP, mientras que esta sobrecarga no está allí para las funciones que están directamente en el camino?
for i = 1:this.get_n_quantities() if(strcmp(id,this.get_quantity_rlz(i).get_id())) ix = i; end end
toma 2.2 segundos, mientras quenq = this.get_n_quantities(); a = this.get_quantity_realizations(); for i = 1:nq c = a{i}; if(strcmp(id,c.get_id())) ix = i; end end
toma 0.01, dos órdenes de magRespuestas:
He estado trabajando con OO MATLAB durante un tiempo y terminé buscando problemas de rendimiento similares.
La respuesta corta es: sí, la POO de MATLAB es un poco lenta. Hay una sobrecarga de llamadas de método sustancial, más alta que los lenguajes OO convencionales, y no hay mucho que pueda hacer al respecto. Parte de la razón puede ser que MATLAB idiomático usa código "vectorizado" para reducir el número de llamadas a métodos, y la sobrecarga por llamada no es una prioridad alta.
Comparé el rendimiento escribiendo funciones "nop" de no hacer nada como los diversos tipos de funciones y métodos. Aquí hay algunos resultados típicos.
Resultados similares en R2008a a R2009b. Esto está en Windows XP x64 ejecutando MATLAB de 32 bits.
El "Java nop ()" es un método Java que no se hace nada que se llama desde un bucle de código M e incluye la sobrecarga de despacho de MATLAB a Java con cada llamada. "Java nop () from Java" es lo mismo que se llama en un bucle Java for () y no incurre en esa penalización de límite. Tome los tiempos de Java y C con un grano de sal; Un compilador inteligente podría optimizar las llamadas por completo.
El mecanismo de definición del paquete es nuevo, introducido aproximadamente al mismo tiempo que las clases classdef. Su comportamiento puede estar relacionado.
Algunas conclusiones tentativas:
obj.nop()
sintaxis es más lenta que lanop(obj)
sintaxis, incluso para el mismo método en un objeto classdef. Lo mismo para los objetos Java (no se muestran). Si quieres ir rápido, llamanop(obj)
.Decir por qué esto es así sería especulación de mi parte. Los componentes internos OO del motor MATLAB no son públicos. No es un problema interpretado vs compilado per se - MATLAB tiene un JIT - pero la sintaxis y la escritura más flexible de MATLAB pueden significar más trabajo en tiempo de ejecución. (Por ejemplo, no puede distinguir solo de la sintaxis si "f (x)" es una llamada de función o un índice en una matriz; depende del estado del espacio de trabajo en tiempo de ejecución). Puede ser porque las definiciones de clase de MATLAB están vinculadas al estado del sistema de archivos de una manera que muchos otros idiomas no lo son.
¿Entonces lo que hay que hacer?
Un enfoque idiomático de MATLAB para esto es "vectorizar" su código estructurando sus definiciones de clase de modo que una instancia de objeto envuelva una matriz; es decir, cada uno de sus campos contiene matrices paralelas (denominadas organización "planar" en la documentación de MATLAB). En lugar de tener una matriz de objetos, cada uno con campos que contienen valores escalares, define objetos que son en sí mismos matrices, y hace que los métodos tomen matrices como entradas y hagan llamadas vectorizadas en los campos y entradas. Esto reduce la cantidad de llamadas a métodos realizadas, es de esperar que la sobrecarga del envío no sea un cuello de botella.
Imitar una clase C ++ o Java en MATLAB probablemente no será óptimo. Las clases Java / C ++ generalmente se crean de manera que los objetos son los bloques de construcción más pequeños, tan específicos como sea posible (es decir, muchas clases diferentes), y los compones en matrices, objetos de colección, etc., y los repites con bucles. Para hacer clases rápidas de MATLAB, cambie ese enfoque al revés. Tenga clases más grandes cuyos campos sean matrices y llame a métodos vectorizados en esas matrices.
El punto es organizar su código para jugar con las fortalezas del lenguaje (manejo de matriz, matemática vectorizada) y evitar los puntos débiles.
EDITAR: Desde la publicación original, han salido R2010b y R2011a. La imagen general es la misma, con las llamadas MCOS cada vez más rápidas, y las llamadas a métodos antiguos y Java cada vez más lentas .
EDITAR: Solía tener algunas notas aquí sobre "sensibilidad de ruta" con una tabla adicional de temporizaciones de llamadas de función, donde los tiempos de función se vieron afectados por cómo se configuró la ruta de Matlab, pero eso parece haber sido una aberración de mi configuración de red particular en el tiempo. El cuadro anterior refleja los tiempos típicos de la preponderancia de mis pruebas a lo largo del tiempo.
Actualización: R2011b
EDITAR (13/02/2012): R2011b está fuera, y la imagen de rendimiento ha cambiado lo suficiente como para actualizar esto.
Creo que el resultado de esto es que:
foo(obj)
sintaxis. Entonces, la velocidad del método ya no es una razón para seguir con las clases de estilo antiguo en la mayoría de los casos. (¡Felicitaciones, MathWorks!)Actualización: R2014a
Reconstruí el código de evaluación comparativa y lo ejecuté en R2014a.
Actualización: R2015b: ¡Los objetos se volvieron más rápidos!
Aquí están los resultados de R2015b, amablemente proporcionados por @Shaked. Este es un gran cambio: OOP es significativamente más rápido, y ahora la
obj.method()
sintaxis es tan rápidamethod(obj)
y mucho más rápida que los objetos OOP heredados.Actualización: R2018a
Aquí están los resultados de R2018a. No es el gran salto que vimos cuando se introdujo el nuevo motor de ejecución en R2015b, pero sigue siendo una mejora apreciable año tras año. En particular, los manejadores de funciones anónimas se volvieron mucho más rápidos.
Actualización: R2018b y R2019a: sin cambios
No hay cambios significativos. No me estoy molestando en incluir los resultados de la prueba.
Código fuente para puntos de referencia
Puse el código fuente de estos puntos de referencia en GitHub, publicado bajo la Licencia MIT. https://github.com/apjanke/matlab-bench
fuente
La clase de identificador tiene una sobrecarga adicional por el seguimiento de todas las referencias a sí misma para fines de limpieza.
Pruebe el mismo experimento sin usar la clase de identificador y vea cuáles son sus resultados.
fuente
El rendimiento de OO depende significativamente de la versión de MATLAB utilizada. No puedo comentar sobre todas las versiones, pero sé por experiencia que 2012a ha mejorado mucho más que las versiones de 2010. No hay puntos de referencia y, por lo tanto, no hay números para presentar. Mi código, escrito exclusivamente usando clases de identificador y escrito bajo 2012a, no se ejecutará en absoluto en versiones anteriores.
fuente
En realidad no hay problema con su código pero es un problema con Matlab. Creo que es una especie de juego para parecer. No es más que una sobrecarga para compilar el código de clase. He realizado la prueba con un punto de clase simple (una vez como identificador) y el otro (una vez como clase de valor)
aqui esta la prueba
Los resultados t1 =
12.0212% Mango
t2 =
Valor 12.0042%
t3 =
t4 =
Por lo tanto, para un rendimiento eficiente, evite usar OOP, en cambio la estructura es una buena opción para agrupar variables
fuente