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.
>> call_nops
Computadora: PCWIN Lanzamiento: 2009b
Llamar a cada función / método 100000 veces
función nop (): 0.02261 sec 0.23 usec por llamada
funciones nop1-5 (): 0.02182 sec 0.22 usec por llamada
subfunción nop (): 0.02244 sec 0.22 usec por llamada
@ () [] función anónima: 0.08461 sec 0.85 usec por llamada
método nop (obj): 0.24664 sec 2.47 usec por llamada
métodos nop1-5 (obj): 0.23469 sec 2.35 usec por llamada
función privada nop (): 0.02197 sec 0.22 usec por llamada
classdef nop (obj): 0.90547 sec 9.05 usec por llamada
classdef obj.nop (): 1.75522 sec 17.55 usec por llamada
classdef private_nop (obj): 0.84738 sec 8.47 usec por llamada
classdef nop (obj) (archivo-m): 0.90560 sec 9.06 usec por llamada
classdef class.staticnop (): 1.16361 sec 11.64 usec por llamada
Java nop (): 2.43035 sec 24.30 usec por llamada
Java static_nop (): 0.87682 sec 8.77 usec por llamada
Java nop () de Java: 0.00014 sec 0.00 usec por llamada
MEX mexnop (): 0.11409 sec 1.14 usec por llamada
C nop (): 0.00001 sec 0.00 usec por llamada
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:
- Los métodos son más lentos que las funciones.
- Los nuevos métodos de estilo (classdef) son más lentos que los métodos de estilo antiguo.
- La nueva
obj.nop()
sintaxis es más lenta que la nop(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, llama nop(obj)
.
- La sobrecarga de la llamada al método es mayor (aproximadamente 2 veces) en MATLAB de 64 bits en Windows. (No mostrado.)
- El envío del método MATLAB es más lento que en otros idiomas.
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.
Arco: PCWIN Lanzamiento: 2011b
Máquina: R2011b, Windows XP, 8x Core i7-2600 @ 3.40GHz, 3 GB de RAM, NVIDIA NVS 300
Haciendo cada operación 100000 veces
estilo total µseg por llamada
función nop (): 0.01578 0.16
nop (), desenrollado de bucle 10x: 0.01477 0.15
nop (), desenrollado de bucle 100x: 0.01518 0.15
subfunción nop (): 0.01559 0.16
@ () [] función anónima: 0.06400 0.64
método nop (obj): 0.28482 2.85
función privada nop (): 0.01505 0.15
classdef nop (obj): 0.43323 4.33
classdef obj.nop (): 0.81087 8.11
classdef private_nop (obj): 0.32272 3.23
classdef class.staticnop (): 0.88959 8.90
constante classdef: 1.51890 15.19
propiedad classdef: 0.12992 1.30
propiedad classdef con getter: 1.39912 13.99
+ función pkg.nop (): 0.87345 8.73
+ pkg.nop () desde adentro + pkg: 0.80501 8.05
Java obj.nop (): 1.86378 18.64
Java nop (obj): 0.22645 2.26
Java feval ('nop', obj): 0.52544 5.25
Java Klass.static_nop (): 0.35357 3.54
Java obj.nop () de Java: 0.00010 0.00
MEX mexnop (): 0.08709 0.87
C nop (): 0.00001 0.00
j () (incorporado): 0.00251 0.03
Creo que el resultado de esto es que:
- Los métodos MCOS / classdef son más rápidos. El costo ahora está a la par con las clases de estilo antiguo, siempre que use la
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!)
- Poner funciones en espacios de nombres los hace lentos. (No es nuevo en R2011b, solo nuevo en mi prueba).
Actualización: R2014a
Reconstruí el código de evaluación comparativa y lo ejecuté en R2014a.
Matlab R2014a en PCWIN64
Matlab 8.3.0.532 (R2014a) / Java 1.7.0_11 en PCWIN64 Windows 7 6.1 (eilonwy-win7)
Máquina: CPU Core i7-3615QM a 2.30 GHz, 4 GB de RAM (plataforma virtual VMware)
nIters = 100000
Tiempo de operación (µseg)
función nop (): 0.14
subfunción nop (): 0.14
@ () [] función anónima: 0,69
método nop (obj): 3.28
nop () fcn privado en @class: 0.14
classdef nop (obj): 5.30
classdef obj.nop (): 10.78
classdef pivate_nop (obj): 4.88
classdef class.static_nop (): 11.81
constante classdef: 4.18
propiedad classdef: 1.18
propiedad classdef con getter: 19.26
+ función pkg.nop (): 4.03
+ pkg.nop () desde adentro + pkg: 4.16
feval ('nop'): 2.31
feval (@nop): 0.22
eval ('nop'): 59,46
Java obj.nop (): 26.07
Java nop (obj): 3.72
Java feval ('nop', obj): 9.25
Java Klass.staticNop (): 10.54
Java obj.nop () de Java: 0.01
MEX mexnop (): 0.91
j integrado (): 0.02
struct s.foo field access: 0.14
vacío (persistente): 0.00
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ápida method(obj)
y mucho más rápida que los objetos OOP heredados.
Matlab R2015b en PCWIN64
Matlab 8.6.0.267246 (R2015b) / Java 1.7.0_60 en PCWIN64 Windows 8 6.2 (nanit-shaked)
Máquina: CPU Core i7-4720HQ a 2.60 GHz, 16 GB de RAM (20378)
nIters = 100000
Tiempo de operación (µseg)
función nop (): 0.04
subfunción nop (): 0.08
@ () [] función anónima: 1.83
método nop (obj): 3.15
nop () fcn privado en @class: 0.04
classdef nop (obj): 0.28
classdef obj.nop (): 0.31
classdef pivate_nop (obj): 0.34
classdef class.static_nop (): 0.05
constante classdef: 0.25
propiedad classdef: 0.25
propiedad classdef con getter: 0.64
Función + pkg.nop (): 0.04
+ pkg.nop () desde adentro + pkg: 0.04
feval ('nop'): 8.26
feval (@nop): 0.63
eval ('nop'): 21.22
Java obj.nop (): 14.15
Java nop (obj): 2.50
Java feval ('nop', obj): 10.30
Java Klass.staticNop (): 24.48
Java obj.nop () de Java: 0.01
MEX mexnop (): 0.33
Construido j (): 0.15
struct s.foo field access: 0.25
vacío (persistente): 0.13
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.
Matlab R2018a en MACI64
Matlab 9.4.0.813654 (R2018a) / Java 1.8.0_144 en MACI64 Mac OS X 10.13.5 (eilonwy)
Máquina: CPU Core i7-3615QM @ 2.30GHz, 16 GB de RAM
nIters = 100000
Tiempo de operación (µseg)
función nop (): 0.03
subfunción nop (): 0.04
@ () [] función anónima: 0.16
classdef nop (obj): 0.16
classdef obj.nop (): 0.17
classdef pivate_nop (obj): 0.16
classdef class.static_nop (): 0.03
constante classdef: 0.16
propiedad classdef: 0.13
propiedad classdef con getter: 0.39
Función + pkg.nop (): 0.02
+ pkg.nop () desde adentro + pkg: 0.02
feval ('nop'): 15,62
feval (@nop): 0.43
eval ('nop'): 32.08
Java obj.nop (): 28.77
Java nop (obj): 8.02
Java feval ('nop', obj): 21.85
Java Klass.staticNop (): 45.49
Java obj.nop () de Java: 0.03
MEX mexnop (): 3.54
incorporado j (): 0.10
struct s.foo field access: 0.16
vacío (persistente): 0.07
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