前言
在MATLAB中使用parfor,提高彼此不相关的程序运行的效率(比如一些参数扫描),是简单方便的,但是parfor中也存在一些限制。最近遇到这样一种情形:一个描述线性时变系统的ode方程,要对它进行数值积分,其中包含较多的参数,还有几个300kHz采样率的输入信号。最开始没有考虑并行的事,我不想在ode45等函数每次调用ode方程时都传入如此大量的参数(一秒传入个到
次,顶不住),于是使用了最简单的global传递输入信号,用persistent避免每次都初始化系统参数。而后出现的一些问题和新的需求使我开始思考(折腾)。
问题
我有时会用几个不同的参数来做些简单对比,但是由于函数已经运行过,persistent声明的静态变量已经被初始化,这导致每次都要clear一些变量,而我又是个粗心的人,这样的做法存在隐患,而且我确实遇到了很多和它相关的错误。既然提到参数扫描,那自然想到parfor来提高效率,这时矛盾又进一步发展,parfor是不支持global和persistent声明的变量的。
解决思路
前些天学习Python,并把这个ode方程改成了Python代码,当时就发现Python没有global、persistent声明。逛逛搜索引擎,有人提到可以定义类来实现类似静态变量的作用。于是在Python中为这个线性时变系统定义了类,并将ode方程作为类的方法。MATLAB也是可以面向对象的,那就在MATLAB中也做同样的事不就ok?(其实也可以尝试嵌套函数,应该也可以达成目的,但我就想面向对象)。
一个简单的例子(CSDN的编辑器好像没有MATLAB的语法高亮,随便选一个)
% 整一个匀速直线运动的玩具车
classdef CAR
properties
% 车的动力(加速度)
accelerate
% 初始的速度
init_velocity
% 初始的位置
int_position
end
methods
function obj = CAR(accelerate, init_velocity, init_position)
obj.accelerate = accelerate;
obj.init_velocity = init_velocity;
obj.init_position = init_position;
end
function dydt = sys_odes(obj, t, y)
if y(2) > 5
% 做法过于粗糙
% 咱这玩具车速度最大只能到5m/s
dydt = [5; 0];
else
% 全力前进
dydt = [y(2); obj.accelerate];
end
end
end
end
坑
没了global和persistent,这下应该可以了,但是坑这就出现了。。。正常for循环可以这么写的(注意:直接用{}取元胞数组的元素,然后用dot notation似乎是取不到句柄的,原因我暂时不明白):
T_span = (0:0.2:30)';% 30秒定时赛
a0 = 1:7;
a1 = 2:8;
for ind = 1:7
scan = my_cars{ind};% 我有很多不同马力的车
[~,y] = ode45(@scan.sys_odes, T_span, [scan.init_position; scan.init_velocity]);
result{ind} = y;
end
但是如果只是把for改成parfor,那么MATLAB提示:透明度违例错误。查看文档,寥寥数语,几个简单例子,愣是没看明白。。但我觉得其中最重要的一句话是
In general, the requirement for transparency restricts all dynamic access to variables, because the entire variable might not be present in any given worker. In a transparent workspace, you cannot create, delete, modify, access, or query variables if you do not explicitly specify these variables in the code.
worker的工作空间已经不是我们能看到的那个工作空间了,它具有特殊性,我猜是为了避免几个worker之间的干扰。于是再度尝试,终于明白:
[~,y] = ode45(@(t,y)scan.sys_odes(t,y), T_span, [scan.init_position; scan.init_velocity]);
至此柳暗花明,parfor顺利启动。
缺点
虽然创建类的实例避免了大量传入参数以及易错的persistent参数重复初始化过程,但是不同的实例之间由于不再共享原来global声明的变量,所以占用内存会大几倍,也无法对global变量进行修改。但是在内存不是主要考虑因素时,内存占用是可以接受的。另外parfor本身要求各次循环间相对独立,各个worker对切片类变量的访问有规则制约,所以变量不共享也是parfor需要的。
补充
当初我用global的目的之一是减少参数传递的次数,后来为了使用parfor将原来的global和persistent参数都作为了类的properties,而类的methods总是要求传入对象实例(除非是静态类),这就又让我感到对大量传入(复制)参数的忧虑。实际上,如果不仔细考量这个问题,这个担心确实会变成现实。下面提供两个参考信息:
- MATLAB有一套关于内存使用的优化策略,其中一些旨在减少函数调用时的内存操作,如果你遵循这些规则,并据此优化类的methods,那函数调用时即使传入大量参数,也不会成为问题。这也是我选择的方法。
- MATLAB可以定义值类(Value Class)也可以定义句柄类(Handle Class),句柄类实现了类似C++的引用的能力。这也许可以解决大量传参的内存复制问题。但我没有在parfor中试过。