Trabalho com o OO MATLAB há um tempo e acabei analisando problemas de desempenho semelhantes.
A resposta curta é: sim, o POO do MATLAB é meio lento. Existe uma sobrecarga substancial de chamadas de método, mais alta que as linguagens OO convencionais, e não há muito o que fazer sobre isso. Parte do motivo pode ser que o MATLAB idiomático use código "vetorizado" para reduzir o número de chamadas de método, e a sobrecarga por chamada não é uma alta prioridade.
Avaliei o desempenho escrevendo funções "nop" do-nothing como os vários tipos de funções e métodos. Aqui estão alguns resultados típicos.
>> call_nops
Computador: PCWIN Lançamento: 2009b
Chamando cada função / método 100000 vezes
Função nop (): 0.02261 seg 0.23 usec por chamada
Funções nop1-5 (): 0,02182 seg 0,22 usec por chamada
subfunção nop (): 0.02244 seg 0.22 usec por chamada
@ () [] função anônima: 0,08461 seg 0,85 usec por chamada
método nop (obj): 0,24664 seg 2,47 usec por chamada
métodos nop1-5 (obj): 0.23469 seg. 2,35 usec por chamada
Função privada nop (): 0.02197 seg 0.22 usec por chamada
classdef nop (obj): 0.90547 seg 9.05 usec por chamada
classdef obj.nop (): 1.75522 seg 17,55 usec por chamada
classdef private_nop (obj): 0.84738 seg 8.47 usec por chamada
classdef nop (obj) (arquivo m): 0.90560 seg 9.06 usec por chamada
classdef class.staticnop (): 1.16361 seg 11,64 usec por chamada
Java nop (): 2.43035 seg 24,30 usec por chamada
Java static_nop (): 0,87682 s 8,77 usec por chamada
Java nop () de Java: 0.00014 seg 0.00 usec por chamada
MEX mexnop (): 0.11409 seg 1,14 usec por chamada
C nop (): 0.00001 seg 0.00 usec por chamada
Resultados semelhantes no R2008a a R2009b. Este é no Windows XP x64 executando o MATLAB de 32 bits.
O "Java nop ()" é um método Java do-nothing chamado de dentro de um loop de código M e inclui a sobrecarga de despacho MATLAB para Java a cada chamada. "Java nop () de Java" é a mesma coisa chamada em um loop Java for () e não incorre nessa penalidade de limite. Faça os intervalos de Java e C com um grão de sal; um compilador inteligente pode otimizar completamente as chamadas.
O mecanismo de escopo do pacote é novo, introduzido aproximadamente ao mesmo tempo que as classes classdef. Seu comportamento pode estar relacionado.
Algumas conclusões provisórias:
- Os métodos são mais lentos que as funções.
- Os novos métodos de estilo (classdef) são mais lentos que os métodos de estilo antigo.
- A nova
obj.nop()
sintaxe é mais lenta que a nop(obj)
sintaxe, mesmo para o mesmo método em um objeto classdef. O mesmo para objetos Java (não mostrado). Se você quiser ir mais rápido, ligue nop(obj)
.
- A sobrecarga de chamada de método é maior (cerca de 2x) no MATLAB de 64 bits no Windows. (Não mostrado.)
- O envio do método MATLAB é mais lento que em outros idiomas.
Dizer por que isso é assim seria apenas especulação da minha parte. Os internos de OO do mecanismo MATLAB não são públicos. Não é um problema interpretado versus compilado em si - o MATLAB possui um JIT -, mas a tipagem e sintaxe mais frouxas do MATLAB podem significar mais trabalho em tempo de execução. (Por exemplo, você não pode distinguir apenas da sintaxe "f (x)" é uma chamada de função ou um índice em uma matriz; depende do estado da área de trabalho no tempo de execução.) Pode ser porque as definições de classe do MATLAB estão vinculadas ao estado do sistema de arquivos de uma maneira que muitas outras línguas não o são.
Então o que fazer?
Uma abordagem idiomática do MATLAB para isso é "vetorizar" seu código estruturando suas definições de classe de modo que uma instância de objeto envolva uma matriz; isto é, cada um de seus campos contém matrizes paralelas (denominadas organização "planar" na documentação do MATLAB). Em vez de ter uma matriz de objetos, cada um com campos contendo valores escalares, define objetos que são matrizes e os métodos tomam matrizes como entradas e fazem chamadas vetorizadas nos campos e entradas. Isso reduz o número de chamadas de método feitas, espero que o gasto adicional da expedição não seja um gargalo.
Imitar uma classe C ++ ou Java no MATLAB provavelmente não será o ideal. As classes Java / C ++ são tipicamente construídas de modo que os objetos sejam os menores blocos de construção, o mais específico possível (ou seja, muitas classes diferentes), e você os compõe em matrizes, objetos de coleção etc. e itera sobre eles com loops. Para fazer aulas rápidas do MATLAB, vire essa abordagem do avesso. Tenha classes maiores cujos campos são matrizes e chame métodos vetorizados nessas matrizes.
O objetivo é organizar seu código para que ele atenda aos pontos fortes da linguagem - manipulação de array, matemática vetorizada - e evite os pontos fracos.
EDIT: Desde o post original, R2010b e R2011a foram lançados. A imagem geral é a mesma, com as chamadas do MCOS ficando um pouco mais rápidas e as chamadas do Java e do método antigo ficando mais lentas .
EDIT: Eu costumava fazer algumas anotações aqui sobre "sensibilidade do caminho" com uma tabela adicional de tempos de chamada de função, onde os tempos da função eram afetados pela forma como o caminho do Matlab foi configurado, mas isso parece ter sido uma aberração da minha configuração de rede específica em A Hora. O gráfico acima reflete os horários típicos da preponderância dos meus testes ao longo do tempo.
Atualização: R2011b
EDIT (13/2/2012): O R2011b foi lançado e a imagem de desempenho mudou o suficiente para atualizar isso.
Arco: PCWIN Versão: 2011b
Máquina: R2011b, Windows XP, 8x Core i7-2600 a 3.40GHz, 3 GB de RAM, NVIDIA NVS 300
Fazendo cada operação 100000 vezes
estilo µsec total por chamada
Função nop (): 0,01578 0,16
nop (), desenrolamento de loop 10x: 0,01477 0,15
nop (), desenrolamento de loop de 100x: 0,01518 0,15
subfunção nop (): 0,01559 0,16
@ () [] função anônima: 0,06400 0,64
método nop (obj): 0,28482 2,85
Função 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
propriedade classdef: 0.12992 1.30
propriedade classdef com getter: 1.39912 13.99
+ função pkg.nop (): 0.87345 8.73
+ pkg.nop () de dentro + 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 () (interno): 0,00251 0,03
Eu acho que o resultado disso é o seguinte:
- Os métodos MCOS / classdef são mais rápidos. O custo agora está em pé de igualdade com as classes de estilo antigo, desde que você use a
foo(obj)
sintaxe. Portanto, a velocidade do método não é mais um motivo para seguir as classes de estilo antigo na maioria dos casos. (Parabéns, MathWorks!)
- Colocar funções nos espaços para nome os torna lentos. (Não é novo no R2011b, apenas é novo no meu teste.)
Atualização: R2014a
Eu reconstruí o código de benchmarking e o executei no R2014a.
Matlab R2014a em PCWIN64
Matlab 8.3.0.532 (R2014a) / Java 1.7.0_11 no PCWIN64 Windows 7 6.1 (eilonwy-win7)
Máquina: CPU Core i7-3615QM a 2,30 GHz, 4 GB de RAM (VMware Virtual Platform)
nIters = 100000
Tempo de operação (µsec)
Função nop (): 0.14
subfunção nop (): 0,14
@ () [] função anônima: 0,69
método nop (obj): 3,28
nop () private fcn em @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
propriedade classdef: 1,18
propriedade classdef com getter: 19.26
+ função pkg.nop (): 4.03
+ pkg.nop () de dentro + 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
Construído em j (): 0.02
acesso ao campo struct s.foo: 0,14
isempty (persistente): 0,00
Atualização: R2015b: os objetos ficaram mais rápidos!
Aqui estão os resultados do R2015b, gentilmente fornecidos por @Shaked. Essa é uma grande mudança: OOP é significativamente mais rápido, e agora a obj.method()
sintaxe é tão rápida quanto method(obj)
e muito mais rápida que os objetos OOP herdados.
Matlab R2015b em PCWIN64
Matlab 8.6.0.267246 (R2015b) / Java 1.7.0_60 no PCWIN64 Windows 8 6.2 (nanit-shaked)
Máquina: CPU Core i7-4720HQ a 2.60GHz, 16 GB RAM (20378)
nIters = 100000
Tempo de operação (µsec)
Função nop (): 0.04
subfunção nop (): 0,08
@ () [] função anônima: 1,83
método nop (obj): 3.15
nop () private fcn em @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
propriedade classdef: 0.25
propriedade classdef com getter: 0,64
+ função pkg.nop (): 0.04
+ pkg.nop () de dentro + 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
Construído em j (): 0.15
acesso ao campo struct s.foo: 0,25
isempty (persistente): 0,13
Atualização: R2018a
Aqui estão os resultados do R2018a. Não é o grande salto que vimos quando o novo mecanismo de execução foi introduzido no R2015b, mas ainda é uma melhoria apreciável ano após ano. Notavelmente, identificadores de funções anônimas ficaram muito mais rápidos.
Matlab R2018a em MACI64
Matlab 9.4.0.813654 (R2018a) / Java 1.8.0_144 no MACI64 Mac OS X 10.13.5 (eilonwy)
Máquina: CPU Core i7-3615QM a 2,30 GHz, 16 GB RAM
nIters = 100000
Tempo de operação (µsec)
Função nop (): 0.03
subfunção nop (): 0,04
@ () [] função 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
propriedade classdef: 0.13
propriedade classdef com getter: 0,39
+ função pkg.nop (): 0.02
+ pkg.nop () de dentro + 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
Construído em j (): 0.10
acesso ao campo struct s.foo: 0,16
isempty (persistente): 0,07
Atualização: R2018b e R2019a: nenhuma alteração
Sem alterações significativas. Não estou me incomodando em incluir os resultados do teste.
Código Fonte para Benchmarks
Coloquei o código-fonte desses benchmarks no GitHub, lançado sob a licença MIT. https://github.com/apjanke/matlab-bench