上次大规模使用 Matlab 还是本科的时候,当时还是 5.3 版,现在重新尝试它,已经是 7.8 ( R2009a ),而且 R2010b 版都已经发售。而这些版本引入的一个新玩意儿便是面向对象化编程( object-oriented programming , OOP )。
使用类( class )有很多好处,其中一个重要的好处便是解决变量名冲突和让函数、对象的结构清晰。class 的 static function 可以在不定义类的实例直接调用类的成员函数,比如定义
classdef tools < handle
methods (Static = true)
function a = test(b, c)
a = b + c;
end
end
end
然后可以直接通过
a = tools.test(b, c);
调用 test 函数。这样做的好处是解决 test 的变量名冲突问题,而且可以将类似的函数封装到一块,使得函数结构显示层次化。
但这样做是有代价的,其中一个便是效率问题,假设还有一个完全一样的用普通 m 文件定义的 test 函数,下面两段代码测试了这两者各运行一百万次所需的时间:
tic
for i = 1:1000000, test(i, i); end
toc
tic
for i = 1:1000000, tools.test(i, i); end
toc
那么普通函数的百万次调用的时间开销约 0.25 秒,而封装后的函数的百万次调用的时间开销高达 14.5 秒,相差约 70 倍。而且如果通过 class 的实例来调用,所需要的时间更长,下面这段代码显示百万次调用需要 22.5 秒。
t = tools;
tic
for i = 1:1000000, t.test(i, i); end
toc
所以到底该不该封装,还需取决于实际情况,如果函数本身特别简单,并且会被循环调用,最好还是通过 m 文件函数的形式。之前 MIT 大牛给出了更多地建议
- 虽然 for-loop 的速度有了很大改善, vectorization (向量化)仍旧是改善效率的重要途径,尤其是在能把运算改写成矩阵乘法的情况下,改善尤为显著。
- 在不少情况下, for-loop 本身已经不构成太大问题,尤其是当循环体本身需要较多的计算的时候。这个时候,改善概率的关键在于改善循环体本身而不是去掉 for-loop。
- MATLAB 的函数调用过程(非 built-in function )有显著开销,因此,在效率要求较高的代码中,应该尽可能采用扁平的调用结构,也就是在保持代码清晰和可维护的情况下,尽量直接写表达式和利用 built-in function ,避免不必要的自定义函数调用过程。在次数很多的循环体内(包括在 cellfun, arrayfun 等实际上蕴含循环的函数)形成长调用链,会带来很大的开销。
- 在调用函数时,首选 built-in function ,然后是普通的 m-file 函数,然后才是 function handle 或者 anonymous function。在使用 function handle 或者 anonymous function 作为参数传递时,如果该函数被调用多次,最好先用一个变量接住,再传入该变量。这样,可以有效避免重复的解析过程。
- 在可能的情况下,使用 numeric array 或者 struct array ,它们的效率大幅度高于 cell array (几十倍甚至更多)。对于 struct ,尽可能使用普通的域(字段, field )访问方式,在非效率关键,执行次数较少,而灵活性要求较高的代码中,可以考虑使用动态名称的域访问。
- 虽然 object-oriented 从软件工程的角度更为优胜,而且 object 的使用很多时候很方便,但是 MATLAB 目前对于 OO 的实现效率很低,在效率关键的代码中应该慎用 objects。
- 如果需要设计类,应该尽可能采用普通的 property ,而避免灵活但是效率很低的 dependent property。如非确实必要,避免重载 subsref 和 subsasgn 函数,因为这会全面接管对于 object 的接口调用,往往会带来非常巨大的开销(成千上万倍的减慢),甚至使得本来几乎不是问题的代码成为性能瓶颈。
Q. E. D.