我需要计算
sum(x(:)*y,2)
,其中x
是一维向量(1x1000),y
是一维向量(1x1e8),两者都不稀疏。为了提高效率,我将向量设为单精度数组。另一个可能重要也可能不重要的信息是x
只是一个单调递增的向量,例如x=linspace(0,1,1000)
.
试图
A=sum(x(:)*y,2)
我遇到了记忆问题。所以我想我会做一个for循环。
for i=1:length(y)
A=A+sum(x(:)*y(i),2);
end
这是低效的并且需要很长时间,所以我想将总和分解成几个大小相似的块(除了最后一个大小可能略有不同):
nn = 10 ;% # of chunks
idN=length(y);
splitn = floor(idN/nn);
split = [1:splitn:idN];
for i=1:nn-2
A=A+sum(x(:)*y(split(ni): split(ni)+splitn-1),2)
end
A = A +sum(x(:)*y(split(nn-1):end),2);
这需要很多时间,但不会永远(~45 分钟)。除了购买新电脑之外,我还能做些什么来加快速度?我不认为
parfor
会有帮助,因为无论如何只有一定数量的内存。只要每次迭代都包含在内存中,块的数量是否重要?
任何改进这种乘法的策略或建议都会很棒。
您使用的是哪个版本的 MATLAB,在什么机器上使用什么?我构造了和你一样大小的向量
x = linspace(0,1,1000);
y = rand(1,1e8);
果然,如果我尝试直接计算,我会得到一个内存错误
>> A=sum(x(:)*y,2)
Error using *
Requested 1000x100000000 (745.1GB) array exceeds maximum array size preference (31.7GB). This might cause MATLAB to
become unresponsive.
Related documentation
所以我试试你的循环
tic
Asplit=0;
for i=1:length(y)
Asplit=Asplit+sum(x(:)*y(i),2);
end
toc
这在 74 秒内完成。 比您引用的 45 分钟快得多。 我在第 11 代英特尔上使用 MATLAB 2023a beta。 Core(TM) i7-11700 @ 2.50GHz。
我还可以通过使用基于线程的 parpool 和 parfor 循环来进一步降低速度
parpool('threads');
tic
Asplit=0;
parfor i=1:length(y)
Asplit=Asplit+sum(x(:)*y(i),2);
end
toc
在我的 8 核系统上减少到 15.5 秒。 8 个 CPU 加速 5 倍。不是线性缩放,但我会接受它。
另请注意,我在这里使用双精度。你说你使用的是单精度。让我们看看它长什么样
%try again using single precision
x = single(linspace(0,1,1000));
y = rand(1,1e8,'single');
parpool('threads');
tic
Asplit=0;
parfor i=1:length(y)
Asplit=Asplit+sum(x(:)*y(i),2);
end
toc
12.8 秒。更快但值得损失精度吗?我想这取决于你的应用程序。
最后,让我们使用@beaker 和其他人在评论中讨论的技巧
A=x(:)*sum(y)
。将结果与我们到目前为止一直在看的 split 方法进行比较。
x = linspace(0,1,1000);
y = rand(1,1e8);
parpool('threads');
tic
Asplit=0;
parfor i=1:length(y)
Asplit=Asplit+sum(x(:)*y(i),2);
end
toc
tic
Aalt = x(:)*sum(y);
toc
max(abs(Aalt-Asplit))
x(:)*sum(y)
耗时 0.026 秒 两种方法之间的最大绝对差异约为 1e-6.
1.-内存瓶颈问题
无论您对完整的表格
x
和y
做什么:如果您推送问题中显示的此类数据和操作,45分钟等待或内存不足警告肯定会出现。
2.- 选项 1:增加 RAM
一个人可以走到口袋深处或钱包里装满。祝你好运。
3.- 选项 2:并行池
也祝你好运。
4.- 我的建议:将大变量分解成可处理的块
所以这是:
分解堵塞记忆的变量
一次操作/处理每个块
将结果存储在磁盘中
使用我最喜欢的 MATLAB 命令之一执行此操作的一种方法:全天候,强大且经过多次战斗强化
evalin
(此处为喇叭)
我试着分解
y
,直到我发现100个它都有效。可能更大的碎片也可能起作用,但只有 4 或 10 块甚至可能无法启动。
close all;clear all;clc
x=randi([-1e3 1e3],1,1e3);
y=randi([-1e3 1e3],1,1e8);
N=1e2
A1=zeros(numel(x),numel(y)/N);
A2=zeros(numel(x),numel(y)/N);
A3=zeros(numel(x),numel(y)/N);
A4=zeros(numel(x),numel(y)/N);
k1=2;
L1=['A' num2str(k1) '=zeros(numel(x),numel(y)/N);'];
evalin('base',L1)
tic
for k1=1:N
for k2=1:length(x)/N
a1=x(k2);
a2=a1*y(k2);
L1=['A' num2str(k1) '=zeros(numel(x),numel(y)/N);'];
evalin('base',L1);
L2=['A' num2str(k1) '(k2,:)=sum(a2);'];
evalin('base',L2);
% WRITE Ak1 to FILE
end
end
将
% WRITE Ak1 to FILE
行替换为类似的内容
L3=[''] % build command line into string
evalin('base',L3); % invoke evalin
MATLAB 有一系列不同的命令来写入和读取文件,格式多种多样,例如 .txt 和 Excel,或者使用命令直接写入二进制文件
fwrite
.
要从文件中读取,您还必须构建字符串并使用
evalin
调用它们,但如上所示,构建如此简洁的字符串很容易,因为所做的只是用代码构建变量,并且必须在变量中嵌入可变索引此类变量的声明,它已被分解成的 y 部分。
简而言之:使用可以合理地进出 RAM 的变量,而不会像最近的苏伊士运河交通堵塞那样导致船只阻塞其他船只。