使用 parfor 节省时间和内存?
Saving time and memory using parfor?
考虑prova.mat
在MATLAB中通过以下方式得到
for w=1:100
for p=1:9
A{p}=randn(100,1);
end
baseA_.A=A;
eval(['baseA.A' num2str(w) '= baseA_;'])
end
save(sprintf('prova.mat'),'-v7.3', 'baseA')
为了了解我的数据的实际维度,A1
中的 1x9 cell
由以下 9
数组组成:904x5, 913x5, 1722x5, 4136x5, 9180x5, 3174x5, 5970x5, 4455x5, 340068x5
。其他 Aj
的构成相似。
考虑以下代码
clear all
load prova
tic
parfor w=1:100
indA=sprintf('A%d', w);
Aarr=baseA.(indA).A;
Boot=[];
for p=1:9
C=randn(100,1).*Aarr{p};
Boot=[Boot; C];
end
D{w}=Boot;
end
toc
如果我在我的 Macbook Pro 中 运行 parfor
与 4
本地工作人员的循环需要 1.2 秒。用 for
替换 parfor
需要 0.01 秒。
根据我的实际数据,时间差是 31 秒和 7 秒 [矩阵的创建 C
也更复杂]。
如果理解正确,问题是计算机必须向每个本地工作人员发送 baseA
,这需要时间和内存。
您能否提出一个使 parfor
比 for
更方便的解决方案?我认为保存 baseA
中的所有单元格是一种通过在开始时加载一次来节省时间的方法,但也许我错了。
一般信息
很多函数都有 implicit multi-threading built-in,使得 parfor
循环在使用这些函数时效率不如串行 for
循环,因为所有内核都已被使用. parfor
在这种情况下实际上是不利的,因为它有分配开销,同时与您尝试使用的函数一样并行。
当不使用隐式多线程函数之一时 parfor
is basically recommended in two cases: lots of iterations in your loop (i.e., like 1e10
), or if each iteration takes a very long time (e.g., eig(magic(1e4))
). In the second case you might want to consider using spmd
(slower than parfor
in my experience). The reason parfor
is slower than a for
短循环 运行ges 或快速迭代是正确管理所有工作人员所需的开销,而不是仅仅进行计算。
查看 以了解有关在不同工作人员之间拆分数据的信息。
基准测试
代码
考虑以下示例以了解 for
的行为与 parfor
的行为相反。如果您还没有打开并行池,请先打开它:
gcp; % Opens a parallel pool using your current settings
然后执行几个大循环:
n = 1000; % Iteration number
EigenValues = cell(n,1); % Prepare to store the data
Time = zeros(n,1);
for ii = 1:n
tic
EigenValues{ii,1} = eig(magic(1e3)); % Might want to lower the magic if it takes too long
Time(ii,1) = toc; % Collect time after each iteration
end
figure; % Create a plot of results
plot(1:n,t)
title 'Time per iteration'
ylabel 'Time [s]'
xlabel 'Iteration number[-]';
然后用 parfor
而不是 for
做同样的事情。您会注意到每次迭代的平均时间增加了(我的情况是 0.27 秒到 0.39 秒)。但是请注意,parfor
使用了所有可用的工作线程,因此总时间 (sum(Time)
) 必须除以计算机的核心数。所以对于我来说,总时间从大约 270 秒减少到 49 秒,因为我有一个八核处理器。
因此,虽然使用 parfor
进行每个单独迭代的时间比使用 for
增加了,但总时间却大大减少了。
结果
这张图片显示了测试结果,因为我只是 运行 在我的家用电脑上。我使用了 n=1000
和 eig(500)
;我的电脑有一个四核 I5-750 2.66GHz 处理器,运行 MATLAB R2012a。正如您所看到的,并行测试的平均值徘徊在 0.29 秒左右,分布很大,而串行代码在 0.24 秒左右相当稳定。然而,总时间从 234 秒下降到 72 秒,加速了 3.25 倍。这不正好是 4 的原因是内存开销,如每次迭代花费的额外时间所示。内存开销是由于 MATLAB 必须检查每个核心在做什么,并确保每个循环迭代仅执行一次,并将数据放入正确的存储位置。
将广播数据切片到元胞数组中
以下方法适用于按组循环的数据。分组变量是什么并不重要,只要在循环之前确定即可。速度优势是巨大的。
此类 data
的简化示例如下,第一列包含分组变量:
ngroups = 1000;
nrows = 1e6;
data = [randi(ngroups,[nrows,1]), randn(nrows,1)];
data(1:5,:)
ans =
620 -0.10696
586 -1.1771
625 2.2021
858 0.86064
78 1.7456
现在,为简单起见,假设我对第二列中的 sum()
按组值感兴趣。我可以按组循环,索引感兴趣的元素并总结它们。我将使用 for
循环、普通 parfor
和带有 sliced 数据的 parfor
来执行此任务,并将比较时间。
请记住,这是一个玩具示例,我对 替代 解决方案不感兴趣,例如 bsxfun()
,这不是分析的重点。
结果
从 Adriaan 借用相同类型的图,我首先确认关于普通 parfor
与 for
的相同发现。其次,这两种方法在切片数据上 完全优于 parfor
,在具有 1000 万行的数据集上完成切片操作需要 2 秒多一点(包括切片操作)在时间上)。普通的 parfor
需要 24 秒才能完成,而 for
几乎是这个时间的两倍(我在 Win7 64、R2016a 和 i5-3570 上有 4 个内核)。
在开始parfor
之前对数据进行切片的要点是避免:
- 将整个数据广播给工作人员的开销,
- 将操作索引到不断增长的数据集中。
密码
ngroups = 1000;
nrows = 1e7;
data = [randi(ngroups,[nrows,1]), randn(nrows,1)];
% Simple for
[out,t] = deal(NaN(ngroups,1));
overall = tic;
for ii = 1:ngroups
tic
idx = data(:,1) == ii;
out(ii) = sum(data(idx,2));
t(ii) = toc;
end
s.OverallFor = toc(overall);
s.TimeFor = t;
s.OutFor = out;
% Parfor
try parpool(4); catch, end
[out,t] = deal(NaN(ngroups,1));
overall = tic;
parfor ii = 1:ngroups
tic
idx = data(:,1) == ii;
out(ii) = sum(data(idx,2));
t(ii) = toc;
end
s.OverallParfor = toc(overall);
s.TimeParfor = t;
s.OutParfor = out;
% Sliced parfor
[out,t] = deal(NaN(ngroups,1));
overall = tic;
c = cache2cell(data,data(:,1));
s.TimeDataSlicing = toc(overall);
parfor ii = 1:ngroups
tic
out(ii) = sum(c{ii}(:,2));
t(ii) = toc;
end
s.OverallParforSliced = toc(overall);
s.TimeParforSliced = t;
s.OutParforSliced = out;
x = 1:ngroups;
h = plot(x, s.TimeFor,'xb',x,s.TimeParfor,'+r',x,s.TimeParforSliced,'.g');
set(h,'MarkerSize',1)
title 'Time per iteration'
ylabel 'Time [s]'
xlabel 'Iteration number[-]';
legend({sprintf('for : %5.2fs',s.OverallFor),...
sprintf('parfor : %5.2fs',s.OverallParfor),...
sprintf('parfor_sliced: %5.2fs',s.OverallParforSliced)},...
'interpreter', 'none','fontname','courier')
你可以在我的 github repo 上找到 cache2cell()
。
切片数据简单
您可能想知道如果我们对切片数据 运行 简单 for
会发生什么?对于这个简单的玩具示例,如果我们通过切片数据取消索引操作,我们就消除了代码的唯一瓶颈,for
实际上 比 快一点 parfor
.
但是,这是一个玩具示例,其中内部循环的成本完全由索引操作承担。因此,为了让 parfor
有价值,内部循环应该 更复杂 and/or 展开。
使用切片 parfor 节省内存
现在,假设您的内部循环更复杂,而简单的 for
循环更慢,让我们看看通过避免在具有 4 个 worker 的 parfor 和具有 50 个 worker 的数据集中广播数据,我们节省了多少内存百万行(约 760 MB 内存)。
如您所见,将近 3 GB 的额外内存发送给了工作人员。切片操作需要一些内存才能完成,但仍然比广播操作少得多,原则上可以覆盖初始数据集,因此一旦完成,RAM 成本可以忽略不计。最后,切片数据上的 parfor
将仅使用 一小部分 内存,即对应于正在使用的切片的数量。
切入一个单元格
原始数据按组切片,每个切片存储到一个单元格中。由于元胞数组是引用数组,我们基本上将内存中的连续 data
划分为独立的块。
虽然我们的样本 data
看起来像这样
data(1:5,:)
ans =
620 -0.10696
586 -1.1771
625 2.2021
858 0.86064
78 1.7456
切片后 c
看起来像
c(1:5)
ans =
[ 969x2 double]
[ 970x2 double]
[ 949x2 double]
[ 986x2 double]
[1013x2 double]
其中 c{1}
是
c{1}(1:5,:)
ans =
1 0.58205
1 0.80183
1 -0.73783
1 0.79723
1 1.0414
考虑prova.mat
在MATLAB中通过以下方式得到
for w=1:100
for p=1:9
A{p}=randn(100,1);
end
baseA_.A=A;
eval(['baseA.A' num2str(w) '= baseA_;'])
end
save(sprintf('prova.mat'),'-v7.3', 'baseA')
为了了解我的数据的实际维度,A1
中的 1x9 cell
由以下 9
数组组成:904x5, 913x5, 1722x5, 4136x5, 9180x5, 3174x5, 5970x5, 4455x5, 340068x5
。其他 Aj
的构成相似。
考虑以下代码
clear all
load prova
tic
parfor w=1:100
indA=sprintf('A%d', w);
Aarr=baseA.(indA).A;
Boot=[];
for p=1:9
C=randn(100,1).*Aarr{p};
Boot=[Boot; C];
end
D{w}=Boot;
end
toc
如果我在我的 Macbook Pro 中 运行 parfor
与 4
本地工作人员的循环需要 1.2 秒。用 for
替换 parfor
需要 0.01 秒。
根据我的实际数据,时间差是 31 秒和 7 秒 [矩阵的创建 C
也更复杂]。
如果理解正确,问题是计算机必须向每个本地工作人员发送 baseA
,这需要时间和内存。
您能否提出一个使 parfor
比 for
更方便的解决方案?我认为保存 baseA
中的所有单元格是一种通过在开始时加载一次来节省时间的方法,但也许我错了。
一般信息
很多函数都有 implicit multi-threading built-in,使得 parfor
循环在使用这些函数时效率不如串行 for
循环,因为所有内核都已被使用. parfor
在这种情况下实际上是不利的,因为它有分配开销,同时与您尝试使用的函数一样并行。
当不使用隐式多线程函数之一时 parfor
is basically recommended in two cases: lots of iterations in your loop (i.e., like 1e10
), or if each iteration takes a very long time (e.g., eig(magic(1e4))
). In the second case you might want to consider using spmd
(slower than parfor
in my experience). The reason parfor
is slower than a for
短循环 运行ges 或快速迭代是正确管理所有工作人员所需的开销,而不是仅仅进行计算。
查看
基准测试
代码
考虑以下示例以了解 for
的行为与 parfor
的行为相反。如果您还没有打开并行池,请先打开它:
gcp; % Opens a parallel pool using your current settings
然后执行几个大循环:
n = 1000; % Iteration number
EigenValues = cell(n,1); % Prepare to store the data
Time = zeros(n,1);
for ii = 1:n
tic
EigenValues{ii,1} = eig(magic(1e3)); % Might want to lower the magic if it takes too long
Time(ii,1) = toc; % Collect time after each iteration
end
figure; % Create a plot of results
plot(1:n,t)
title 'Time per iteration'
ylabel 'Time [s]'
xlabel 'Iteration number[-]';
然后用 parfor
而不是 for
做同样的事情。您会注意到每次迭代的平均时间增加了(我的情况是 0.27 秒到 0.39 秒)。但是请注意,parfor
使用了所有可用的工作线程,因此总时间 (sum(Time)
) 必须除以计算机的核心数。所以对于我来说,总时间从大约 270 秒减少到 49 秒,因为我有一个八核处理器。
因此,虽然使用 parfor
进行每个单独迭代的时间比使用 for
增加了,但总时间却大大减少了。
结果
这张图片显示了测试结果,因为我只是 运行 在我的家用电脑上。我使用了 n=1000
和 eig(500)
;我的电脑有一个四核 I5-750 2.66GHz 处理器,运行 MATLAB R2012a。正如您所看到的,并行测试的平均值徘徊在 0.29 秒左右,分布很大,而串行代码在 0.24 秒左右相当稳定。然而,总时间从 234 秒下降到 72 秒,加速了 3.25 倍。这不正好是 4 的原因是内存开销,如每次迭代花费的额外时间所示。内存开销是由于 MATLAB 必须检查每个核心在做什么,并确保每个循环迭代仅执行一次,并将数据放入正确的存储位置。
将广播数据切片到元胞数组中
以下方法适用于按组循环的数据。分组变量是什么并不重要,只要在循环之前确定即可。速度优势是巨大的。
此类 data
的简化示例如下,第一列包含分组变量:
ngroups = 1000;
nrows = 1e6;
data = [randi(ngroups,[nrows,1]), randn(nrows,1)];
data(1:5,:)
ans =
620 -0.10696
586 -1.1771
625 2.2021
858 0.86064
78 1.7456
现在,为简单起见,假设我对第二列中的 sum()
按组值感兴趣。我可以按组循环,索引感兴趣的元素并总结它们。我将使用 for
循环、普通 parfor
和带有 sliced 数据的 parfor
来执行此任务,并将比较时间。
请记住,这是一个玩具示例,我对 替代 解决方案不感兴趣,例如 bsxfun()
,这不是分析的重点。
结果
从 Adriaan 借用相同类型的图,我首先确认关于普通 parfor
与 for
的相同发现。其次,这两种方法在切片数据上 完全优于 parfor
,在具有 1000 万行的数据集上完成切片操作需要 2 秒多一点(包括切片操作)在时间上)。普通的 parfor
需要 24 秒才能完成,而 for
几乎是这个时间的两倍(我在 Win7 64、R2016a 和 i5-3570 上有 4 个内核)。
在开始parfor
之前对数据进行切片的要点是避免:
- 将整个数据广播给工作人员的开销,
- 将操作索引到不断增长的数据集中。
密码
ngroups = 1000;
nrows = 1e7;
data = [randi(ngroups,[nrows,1]), randn(nrows,1)];
% Simple for
[out,t] = deal(NaN(ngroups,1));
overall = tic;
for ii = 1:ngroups
tic
idx = data(:,1) == ii;
out(ii) = sum(data(idx,2));
t(ii) = toc;
end
s.OverallFor = toc(overall);
s.TimeFor = t;
s.OutFor = out;
% Parfor
try parpool(4); catch, end
[out,t] = deal(NaN(ngroups,1));
overall = tic;
parfor ii = 1:ngroups
tic
idx = data(:,1) == ii;
out(ii) = sum(data(idx,2));
t(ii) = toc;
end
s.OverallParfor = toc(overall);
s.TimeParfor = t;
s.OutParfor = out;
% Sliced parfor
[out,t] = deal(NaN(ngroups,1));
overall = tic;
c = cache2cell(data,data(:,1));
s.TimeDataSlicing = toc(overall);
parfor ii = 1:ngroups
tic
out(ii) = sum(c{ii}(:,2));
t(ii) = toc;
end
s.OverallParforSliced = toc(overall);
s.TimeParforSliced = t;
s.OutParforSliced = out;
x = 1:ngroups;
h = plot(x, s.TimeFor,'xb',x,s.TimeParfor,'+r',x,s.TimeParforSliced,'.g');
set(h,'MarkerSize',1)
title 'Time per iteration'
ylabel 'Time [s]'
xlabel 'Iteration number[-]';
legend({sprintf('for : %5.2fs',s.OverallFor),...
sprintf('parfor : %5.2fs',s.OverallParfor),...
sprintf('parfor_sliced: %5.2fs',s.OverallParforSliced)},...
'interpreter', 'none','fontname','courier')
你可以在我的 github repo 上找到 cache2cell()
。
切片数据简单
您可能想知道如果我们对切片数据 运行 简单 for
会发生什么?对于这个简单的玩具示例,如果我们通过切片数据取消索引操作,我们就消除了代码的唯一瓶颈,for
实际上 比 快一点 parfor
.
但是,这是一个玩具示例,其中内部循环的成本完全由索引操作承担。因此,为了让 parfor
有价值,内部循环应该 更复杂 and/or 展开。
使用切片 parfor 节省内存
现在,假设您的内部循环更复杂,而简单的 for
循环更慢,让我们看看通过避免在具有 4 个 worker 的 parfor 和具有 50 个 worker 的数据集中广播数据,我们节省了多少内存百万行(约 760 MB 内存)。
如您所见,将近 3 GB 的额外内存发送给了工作人员。切片操作需要一些内存才能完成,但仍然比广播操作少得多,原则上可以覆盖初始数据集,因此一旦完成,RAM 成本可以忽略不计。最后,切片数据上的 parfor
将仅使用 一小部分 内存,即对应于正在使用的切片的数量。
切入一个单元格
原始数据按组切片,每个切片存储到一个单元格中。由于元胞数组是引用数组,我们基本上将内存中的连续 data
划分为独立的块。
虽然我们的样本 data
看起来像这样
data(1:5,:)
ans =
620 -0.10696
586 -1.1771
625 2.2021
858 0.86064
78 1.7456
切片后 c
看起来像
c(1:5)
ans =
[ 969x2 double]
[ 970x2 double]
[ 949x2 double]
[ 986x2 double]
[1013x2 double]
其中 c{1}
是
c{1}(1:5,:)
ans =
1 0.58205
1 0.80183
1 -0.73783
1 0.79723
1 1.0414