如何“复制”矩阵而不在内存中创建导致内存溢出的临时矩阵?

Posted

技术标签:

【中文标题】如何“复制”矩阵而不在内存中创建导致内存溢出的临时矩阵?【英文标题】:How to 'copy' matrix without creating a temporary matrix in memory that caused memory overflow? 【发布时间】:2020-04-09 02:31:40 【问题描述】:

通过将矩阵分配到更大的分配内存中,matlab 在“复制”它时会以某种方式复制它,如果要复制的矩阵足够大,就会出现内存溢出。这是示例代码:

main_mat=zeros(500,500,2000);
n=500;
slice_matrix=zeros(500,500,n);
for k=1:4
    parfor i=1:n
        slice_matrix(:,:,i)=gather(gpuArray(rand(500,500)));
    end
    main_mat(:,:,1+(k-1)*n:1+(k-1)*n+n-1)=slice_matrix; %This is where the memory will likely overflow
end

有什么方法可以在没有开销的情况下将slice_matrix“粉碎”到main_mat 上?提前致谢。

编辑:

在预先分配main_mat 时发生溢出。如果main_matmain_mat=zeros(500,500,1); (较小的大小)初始化,则不会发生溢出,但会减慢,因为在分配矩阵之前没有完成分配。随着k 范围的增加,这将显着降低性能。

【问题讨论】:

至于你的循环:it's recommended to set the outer loop to a parfor loop for optimisation purposes。此外,parfor 将您的数据复制到每个单独的工作人员,因此假设有 4 个工作人员,它会将您的数据在 RAM 中复制四次。 你有什么迹象表明Matlab实际上是在复制内存?你在使用memory 函数吗?任务管理器? Matlab的内存错误?它发生在哪一行代码? 如您所见,我在代码中注释的位置,main_mat(:,:,1+(k-1)*n:1+(k-1)*n+n-1) 是发生内存溢出问题的位置。当我预先分配main_mat时验证过,它会溢出,如果我不这样做,它就不会。 Matlab 将返回“内存不足错误”。 您的 500x500x2000 矩阵是否适合内存?它约为 4 Gb。请参阅***.com/q/51987892/7328782,了解为什么只有在写入数组时才会发生内存不足错误。 为了更好地理解您的问题,您能否在main_mat(:,:,1+(k-1)*n:1+(k-1)*n+n-1)=slice_matrix; 之前插入h=h+slice_matrix(end)(并将h 初始化为0)?我怀疑这条新添加的行已经导致您的内存问题。 【参考方案1】:

您可以使用以下代码。你实际上不需要 slice_matrix

main_mat=zeros(500,500,2000);
n=500;
slice_matrix=zeros(500,500,n);
for k=1:4
   parfor i=1:n
       main_mat(:,:,1+(k-1)*n + i - 1) = gather(gpuArray(rand(500,500)));
   end
   %% now you don't need this main_mat(:,:,1+(k-1)*n:1+(k-1)*n+n-1)=slice_matrix; %This is where the memory will likely overflow
end

【讨论】:

你不能在 parfor 循环中这样做 你试过了吗? 我将它移出 parfoor 循环是有原因的。我没有尝试完全相同的代码,但我知道它不会因为索引而工作。【参考方案2】:

如果我理解正确,您的主要问题是parfor 不允许共享内存。将每个 parfor 工作人员视为几乎一个单独的 matlab 实例。

我知道基本上只有一种解决方法(我从未尝试过),即 Fileexchange 上的“共享矩阵”:https://ch.mathworks.com/matlabcentral/fileexchange/28572-sharedmatrix

更多解决方案:正如其他人建议的那样:删除 parfor 肯定是一种解决方案,获得更多内存,使用 tall 数组(当内存满时使用硬盘驱动器,read here),将操作划分为更小的块,最后但并非最不重要的一点,考虑Matlab 以外的替代方法。

【讨论】:

【参考方案3】:

主要问题是数字比零占用更多空间。 main_mat=zeros(500,500,2000); 占用很少的 RAM 而 main_mat = rand(500,500,2000); 占用很多,无论您使用 GPU 还是 parfor(事实上,parfor 会让您使用更多 RAM)。所以这不是不自然的记忆膨胀。按照下面丹尼尔的链接,零的分配似乎只创建指向内存的指针,并且只有当您将矩阵用于“数字”时才会填充物理内存。这是由操作系统管理的。它适用于 Windows、Mac 和 Linux,您可以使用 Matlab 或其他语言(如 C)来完成。

【讨论】:

现在我不再了解 MATLAB。一旦我输入带有zeros 的命令,整个虚拟内存就被实际分配了,但没有使用任何内存。 whos 显示两个矩阵的大小相同,而我的操作系统显示不同的内存消耗。我删除了我的评论,因为你的回答绝对没有错。 我找到了一些解释:***.com/questions/51987892/… 很好的答案!谢谢。 @Gregor:我想确认一下,尝试使用ones 而不是zeros,这样可以确保在调用相应函数时实际分配了内存。 当我理解一切正确时,结论是:没有临时副本。出现内存不足异常是因为main_mat 被分配了非零值。以前只分配虚拟内存(地址空间),现在分配给物理内存。【参考方案4】:

我假设您的代码只是一个示例代码,rand() 代表您的 MVE 中的自定义。所以对于matlab中的内存使用有一些提示和技巧。

The MathWorks 培训手册中有一个 sn-p:

在 MATLAB 中将一个变量分配给另一个变量时,就像将参数传递给函数时一样,MATLAB 会透明地创建对该变量的引用。仅当代码修改一个或多个值时,MATLAB 才会中断引用并创建该变量的副本。这种行为,称为 copy-on-writelazy-copying,将复制大型数据集的成本推迟到代码修改值之前。因此,如果代码不进行任何修改,就不需要额外的内存空间和执行时间来复制变量。

要做的第一件事是检查代码的(内存)效率。即使是优秀程序员的代码也可以通过(一点)脑力进一步优化。这里有一些关于内存效率的提示

利用 matlab 的原生矢量化,例如sum(X,2), mean(X,2), std(X,[],2) 确保 matlab 不必扩展矩阵(隐式扩展最近已更改)。使用bsxfun 可能更有效 使用就地操作,例如x = 2*x+3 而不是 x = 2*x+3 ...

请注意,关于内存使用的优化与您希望减少计算时间不同。因此,您可能需要考虑减少工作人员的数量或避免使用parfor-loop。 (由于parfor 无法使用共享内存,因此使用 Parallel Toolbox 没有 copy-on-write 功能。

如果您想更深入地了解您的记忆,哪些是可用的以及哪些可以被 Matlab 使用,请查看 feature('memstats')。对您来说有趣的是 虚拟内存,即

与整个 MATLAB 进程相关的总内存和可用内存。它受处理器架构和操作系统的限制。 或者使用这个命令[user,sys] = memory

快速侧节点:Matlab 将矩阵一致地存储在内存中。您需要为大型矩阵提供大量可用 RAM。这也是为什么要分配变量的原因,因为动态更改它们会强制 Matlab 将整个矩阵复制到 RAM 中的一个更大的位置,每次它超出当前位置。

如果您确实有内存问题,您可能只想深入研究数据类型的艺术——这在低级语言中是必需的。例如。您可以从一开始就直接使用单精度 main_mat=zeros(500,500,2000,'single'); 将内存使用量减少一半——顺便说一句,这也适用于 rand(...,'single') 和更多本机函数——尽管一些更复杂的 matlab 函数需要输入类型双倍,你可以再次向上。

【讨论】:

【参考方案5】:

删除parfor 可能会解决您的问题。

parfor 在那里没有用。 MATLAB 的parfor 不使用共享内存并行(即它不启动新线程),而是使用分布式内存并行(它启动新进程)。它旨在将工作分配到一组或工作节点上。虽然它也可以在一个节点(或单个台式计算机)内工作以将工作分配到多个内核上,但这并不是在一个节点内进行并行处理的最佳方式。

这意味着parfor启动的每个进程都需要有自己的slice_matrix副本,这就是你的程序占用大量内存的原因。

请参阅 MATLAB 文档中的 "Decide When to Use parfor" 以了解有关 parfor 的更多信息以及何时使用它。

【讨论】:

删除parfor 是唯一的方法?当我这样设计时,处理效果最好,因为parfor 中的所有内容都是 CPU 和 GPU 密集型的,因此它显着提高了性能。 @GregorIsack:我使用了您的示例代码,不知道您实际上在parfor 中做了很多工作。如果是这样,那么是的,它可能很有用。 -- 如果slice_matrix 不是gpuarray,它可能不会被复制到作业中。 嗯,即使slice_matrix 不是gpuArray,我仍然会出现溢出症状。我会让这个问题打开,让我们看看是否有任何替代解决方案。不过感谢您的回答!

以上是关于如何“复制”矩阵而不在内存中创建导致内存溢出的临时矩阵?的主要内容,如果未能解决你的问题,请参考以下文章

sqlite插入一万多条数据会报内存溢出 该怎么解决

sqlite插入一万多条数据会报内存溢出 该怎么解决

如何在内存中创建一个新的 java.io.File? [复制]

一日一技:在Python中创建临时文件用于记录临时数据

附加到 R 中的列表会导致复制吗?

InMemory:在内存中创建临时表和表变量