本文主要以并行语句parfor为例进行探索。
(1)每次循环之间是相互独立的;
(2)循环执行完之后的结果和循环执行的先后次序无关;
(3)不适用于频繁读写内存的算法。
%% 设置并行计算环境
poolobj = gcp('nocreate');
if isempty(poolobj)
poolsize = 0;
CoreNum = 4; % 设置CPU核心数量
parpool('local', CoreNum);
else
poolsize = poolobj.NumWorkers;
disp('Already initialized'); % 并行环境已启动
end
%% 关闭并行计算环境
delete(gcp('nocreate'));
如果长时间不运行并行计算,计算机会在30min后关闭并行环境。
原始程序:
clc;clear all;
tic
for i = 1:10000
for j = 1:10000
a(i,j) = i*j;
end
disp(i)
end
toc
改进程序:
clc;clear all;
tic
% a = zeros(10000,10000); % 如果预先分配内存,运行速度将会大大提升
core_number = 4; % 想要调用的处理器个数
parpool('local',core_number);
parfor i = 1:10000
for j = 1:10000
a(i,j) = i*j;
end
disp(i)
end
toc
delete(gcp('nocreate'));
在我的笔记本上运行原始程序大约需要390s;运行改进程序大约需要23s。
以下是三种反例:
parfor ii = 0:0.2:1 % 不是整数
parfor jj = 1:2:11 % 不是连续整数
parfor kk = 10:-1:1 % 不是递增整数
如果需要引用上述变量,可以参考如下方式进行修改:
ii = 0:0.2:1;
parfor id_ii = 1:numel(ii)
iii = ii(id_ii);
...
end
parfor-Loop中的变量包括如下5个分类:
分类 | 描述 |
---|---|
循环变量 |
parfor的循环索引 |
切片变量 | 需要读取或写入的parfor之外的变量,读取或写入位置与循环变量相关,且位置必须是固定的、不重合的,每次循环中只能读取由同一个索引值索引的切片。 如果 x[i] 和 x[i+1] 同时出现,则x不被识别为切片变量 。 如果切片变量x是输出变量(即在循环内被赋值),访问还必须是连续的(此时只能是Loop变量再加固定的平移量)。 切片变量不能在循环内动态变换大小。 |
广播变量 | 外部变量,在循环内部未被重新赋值,只需读取即可 |
简约变量 | 可在多次循环内对一个变量操作,该变量与迭代顺序无关 |
临时变量 | 在循环内部创建,循环结束后会清除调,但不在循环外部访问 |
a = 0;
c = pi;
z = 0;
r = rand(1,10)
parfor k = 1 : 100 % k为循环变量
a = k; % a为临时变量
z = z + k; % z为简约变量
b(k) = r(k); % r为切片变量(输入),b为切片变量(输出)
if k <= c % c为广播变量
d = 2 * a; % d为临时变量
end
end
切片变量举例
parfor i = 1:n
x(i) = a(2*i); % allowed
y(i+2) = a(i) + b(i+1); % allowed
c(i+1) = c(i) + 1; % not allowed,存在迭代
z(2*i) = i; % not allowed,访问不是连续的,不是加固定平移量
a(i) = []; % not allowed,变换了大小
a(end+1) = 1; % not allowed,变换了大小
end
(1)在parfor-Loop中,如果使用嵌套的for循环来索引切片数组,则不能在parfor循环的其他地方再次使用该数组。举例如下:
%% 无效代码,因为数组A是在嵌套的for循环中被切片和索引的
A = zeros(4,10);
parfor i = 1:4
for j = 1:10
A(i,j) = i + j;
end
disp(A(i,1))
end
%% 有效代码,v被分配到了嵌套循环的外部
A = zeros(4,10);
parfor i = 1:4
v = zeros(1,10);
for j = 1:10
v(j) = i + j;
end
disp(v(1))
A(i,:) = v;
end
(2)在parfor-Loop中,不能对变量进行分类赋值,举例如下:
%% 无效代码,因为变量x的不同部位有多个赋值,parfor不支持
parfor idx = 1:10
x(1) = 7;
x(2) = 8;
out(idx) = sum(x);
end
%% 有效代码,parfor明确地识别变量x为一个临时变量
parfor idx = 1:10
x = [7, 8];
out(idx) = sum(x);
end
(3)通常不能使用结构体用作parfor中的切片输入或输出变量。
%% 无效代码,因为结构体a不能被分类
a.x = [];
parfor idx = 1:10
a.x(idx) = 7;
end
%% 有效代码,将一个单独的变量tmpx赋值给结构体a.x
tmpx = [];
parfor idx = 1:10
tmpx(idx) = 7;
end
a.x = tmpx;
(1)创建临时结构体
在parfor-Loop中,不能使用点号创建结构体新内容,易产生分类错误,可使用struct函数创建结构体。
%% 无效代码,结构体temp形成了一个分类错误
parfor i = 1:4
temp.myfield1 = rand();
temp.myfield2 = i;
end
%% 有效代码,将一个单独的变量tmpx赋值给结构体a.x
parfor i = 1:4
temp = struct();
temp.myfield1 = rand();
temp.myfield2 = i;
end
%% 有效代码
parfor i = 1:4
temp = struct('myfield1',rand(),'myfield2',i);
end
(2)切片结构体
在parfor-Loop中,不能使用循环变量来索引结构体中的元素。
%% 无效代码,因为使用循环变量索引结构体而形成了分类错误
parfor i = 1:4
outputData.outArray1(i) = 1/i;
outputData.outArray2(i) = i^2;
end
%% 有效代码,在循环中使用了单独的切片数组避免了分类错误
parfor i = 1:4
outArray1(i) = 1/i;
outArray2(i) = i^2;
end
outputData = struct('outArray1',outArray1,'outArray2',outArray2);
【1】 MATLAB官方帮助文档。