在MATLAB中直接将大数组写入磁盘时,是不是需要预先分配?

Posted

技术标签:

【中文标题】在MATLAB中直接将大数组写入磁盘时,是不是需要预先分配?【英文标题】:When writing a large array directly to disk in MATLAB, is there any need to preallocate?在MATLAB中直接将大数组写入磁盘时,是否需要预先分配? 【发布时间】:2014-11-26 05:07:18 【问题描述】:

我需要将一个太大而无法放入内存的数组写入 .mat 二进制文件。这可以通过matfile 函数实现,该函数允许随机访问磁盘上的 .mat 文件。

通常,公认的建议是预先分配数组,因为在循环的每次迭代中扩展它们很慢。但是,当我是 asking how to do this 时,我突然想到,在写入磁盘而不是 RAM 时,这可能不是一个好建议。

增长阵列是否会产生相同的性能影响,并且如果如此,它会显着与写入磁盘所需的时间相比

(假设整个文件将在一个会话中写入,那么文件碎片严重的风险。) p>

【问题讨论】:

我只是很好奇您为什么要采用这种设计而不是将文件拆分成多个文件。由于 Matlab 中的 RAM 限制,您已经在进行块处理,因此您也可以将此逻辑扩展到存储任务。 我怀疑循环给定点的时间步长(从 .NET 接口)可能比批量检索 + 保存在磁盘上 + 读回 + 编写整个代码来处理块处理更快(尤其是最后一点非常耗时且容易出错)。 您确定要对 .mat 文件执行此操作吗? Memory mapped files 仅适用于此类情况。 @horchler 好吧,那是一组毫无意义的编辑!我已经恢复了唯一一个实际上是错误的(MATLAB 应该大写,根据供应商的风格指南),对于磁盘/磁盘的事情,只需查找 english.stackexchange.com/questions/8474/… 并轻笑 ;-) 嗯。这个问题似乎正在遭受功能蠕变;-) 【参考方案1】:

问:增加阵列是否会产生相同的性能影响,如果是这样,与写入磁盘所需的时间相比,它是否会显着?

答:是的,如果您在没有预分配的情况下显着增加磁盘上的文件,性能会受到影响。性能下降将是碎片化的结果。正如您所提到的,如果文件在一个会话中写入,碎片风险较小,但如果文件显着增长,则会导致问题。

在 MathWorks 网站上提出了related question,接受的答案是尽可能预先分配。

如果您不预先分配,那么您的性能问题的程度将取决于:

您的文件系统(数据在磁盘上的存储方式,集群大小), 您的硬件(HDD 寻道时间或 SSD 访问时间), mat 文件的大小(是否移动到非连续空间), 以及您的存储的当前状态(现有碎片/可用空间)。

假设您正在运行最新的 Windows 操作系统,因此正在使用NTFS file-system。让我们进一步假设它已设置为默认的 4 kB 集群大小。因此,磁盘上的空间在4 kB chunks 中分配,并且这些位置被索引到主文件表。如果文件增长并且连续空间不可用,那么只有两种选择:

    将整个文件重新写入磁盘的新部分,那里有足够的可用空间。 对文件进行分段,将附加数据存储在磁盘上的不同物理位置。

文件系统选择执行最不坏的选项#2,并更新 MFT 记录以指示新集群在磁盘上的位置。

现在,为了读取或写入新的簇,硬盘需要物理移动读取头,这是一个(相对)缓慢的过程。就移动磁头而言,并等待磁盘的正确区域在其下方旋转……您可能会看到about 10ms 的寻道时间。因此,每次遇到片段时,都会有 10 毫秒的额外延迟,同时 HDD 会移动以访问新数据。 SSD 的寻道时间要短得多(没有移动部件)。为简单起见,我们将忽略多盘系统和 RAID 阵列!

如果您在不同时间不断增长文件,那么您可能会遇到很多碎片。这实际上取决于文件何时/多少增长,以及您如何使用硬盘。您遇到的性能损失还取决于您读取文件的频率以及遇到片段的频率。

MATLAB 将数据存储在 Column-major order 中,并且从 cmets 看来,您似乎有兴趣对数据集执行按列操作(求和、平均值)。如果列在磁盘上变得不连续,那么您将在每次操作中遇到大量碎片!

如 cmets 中所述,读取和写入操作都将通过缓冲区执行。正如@user3666197 指出的那样,操作系统可以推测性地预读磁盘上的当前数据,前提是您接下来可能需要该数据。如果硬盘有时处于空闲状态,则此行为特别有用 - 使其以最大容量运行并使用缓冲存储器中的小部分数据可以大大提高读写性能。但是,从您的问题来看,您似乎想对一个巨大的(对内存而言太大).mat 文件执行大型操作。鉴于您的用例,硬盘将在容量无论如何下工作,并且数据文件太大而无法放入缓冲区 - 所以这些特殊技巧无法解决您的问题。

所以...是的,您应该预先分配。是的,在磁盘上增加阵列会影响性能。是的,它可能会很重要(这取决于增长量、碎片化等细节)。如果您要真的融入 HPC 精神,那就停下手头的工作,扔掉 MATLAB,对数据进行分片,然后尝试 Apache Spark 之类的东西!但那是另一回事了。

这能回答你的问题吗?

附:欢迎指正/修改!我是在 POSIX inode 上长大的,所以如果这里有任何不准确之处,我们深表歉意......

【讨论】:

恕我直言,这个故事近似于 write-through、非缓冲、哑 HDD-io 硬件链。故事的读取部分丢失了,由于预读缓冲、将 HDD 物理磁头/圆柱/盘几何形状高级重新计算为抽象的、更多具有类似电梯的优化的有效轨迹,用于具有平滑几何的实际读取顺序。 Additionaly O/S 执行额外的预读缓存层。所以读取端的结果数据会变得更好 +1,关于预读缓冲和优化硬件抽象的非常好的观点。正如您所说,这些技术将有助于减轻碎片对读取端性能的影响,但它们不会完全修复它 - 特别是因为 OP 的文件太大而无法在内存中使用,因此无法完全缓冲。对 OP 的建议仍然是“预分配空间”,对吧? 谢谢,这是有道理的。我想,主要的一点是我错误地认为在一个会话中写作可以避免碎片化。 @user3666197 已经完成。请参阅我最近对该问题的评论。 您好 GnomeDePlume,您的回答非常有道理,但正如我实际发现的那样(在此 answer 中有详细说明),至少我试图在那里大量进行的那种预分配 减慢速度 i> 过程。你知道这里有什么区别吗?【参考方案2】:

在 RAM 中预分配变量和在磁盘上预分配不能解决相同的问题。

在内存中

要在 RAM 中扩展矩阵,MATLAB 会创建一个具有新大小的新矩阵,并将旧矩阵的值复制到新矩阵中并删除旧矩阵。这会消耗很多性能。

如果您预先分配了矩阵,它的大小不会改变。所以 MATLAB 没有理由再做这种矩阵复制了。

在硬盘上

正如 GnomeDePlume 所说,硬盘上的问题是碎片。即使文件是在一个会话中写入的,碎片仍然是一个问题。

原因如下:硬盘通常会有点碎片。想象一下

#是硬盘上已满的内存块 M 是硬盘上的内存块,用于保存矩阵数据 - 成为硬盘上的空闲内存块

现在,在您将矩阵写入硬盘之前,硬盘可能看起来像这样:

###--##----#--#---#--------------------##-#---------#---#----#------

当你编写矩阵的一部分(例如MMM 块)时,你可以想象这个过程看起来像这样>!(我举了一个例子,文件系统将只是从左到右并使用第一个空闲空间这已经足够大了——真实的文件系统是不同的):

    第一个矩阵部分:###--##MMM-#--#---#--------------------##-#---------#---#----#------ 第二个矩阵部分: ###--##MMM-#--#MMM#--------------------##-#---------#---#----#------ 第三矩阵部分: ###--##MMM-#--#MMM#MMM-----------------##-#---------#---#----#------ 等等……

很明显,硬盘上的矩阵文件是碎片化的,虽然我们在此期间没有做任何其他事情就写了它。

如果矩阵文件是预先分配的,这会更好。换句话说,我们告诉文件系统我们的文件有多大,或者在这个例子中,我们要为它保留多少内存块。

想象一下矩阵需要 12 个块:MMMMMMMMMMMM。我们通过预分配告诉文件系统我们需要这么多,它会尽力满足我们的需求。在这个例子中,我们很幸运:有 >= 12 个内存块的空闲空间。

    预分配(我们需要 12 个内存块):###--##----#--#---# (------------) --------##-#---------#---#----#------ 文件系统为我们的矩阵保留括号之间的空间,并将写入那里。 第一个矩阵部分:###--##----#--#---# (MMM---------) --------##-#---------#---#----#------ 第二个矩阵部分:###--##----#--#---# (MMMMMM------) --------##-#---------#---#----#------ 矩阵第三部分:###--##----#--#---# (MMMMMMMMM---) --------##-#---------#---#----#------ 矩阵的第四部分和最后一部分:###--##----#--#---# (MMMMMMMMMMMM) --------##-#---------#---#----#------

瞧,没有碎片!


类比

通常,您可以将此过程想象为为一大群人购买电影票。你想作为一个团队团结在一起,但剧院里已经有一些座位被其他人预订了。为了让收银员能够满足您的要求(大型团体想要团结在一起),他/她需要了解您的团体有多大(预分配)。

【讨论】:

【参考方案3】:

序幕

此答案基于作者在最近一周提供的原始帖子和澄清(两者)。

不良性能问题一个低级的、依赖物理媒体的、“碎片化”引入,由文件系统和文件访问层引入 在 TimeDOMAIN 量级和 ComputingDOMAIN 重复性方面进一步面临这些这种方法的实际使用问题 .

最后提出了一种最先进的、主要是最快可能解决给定任务的解决方案,以最大程度地减少浪费的努力和误解造成的损失来自理想化或其他无效的假设,例如由于假设整个文件将在一个会话中写入(这基本上不可能在当代 O/S 的许多多核/多进程操作在创建时间和对 TB 大小的 BLOB 文件的一系列广泛修改(参考 MATLAB 大小限制)上实时进行-当代COTS文件系统中的对象)。


人们可能讨厌事实,但事实仍然存在,直到出现更快更好的方法


首先,在考虑性能之前,先意识到概念上的差距

    真正的性能不利影响不是由 HDD-IO 引起的或与文件碎片有关

    RAM 不是替代方法用于 .mat 文件的半永久存储

    额外的操作系统限制和干预 + 额外的驱动程序和基于硬件的抽象在不可避免的开销假设中被忽略了 在对结果性能产生最大影响/影响的审查中省略了上述计算方案

鉴于:

整个处理旨在只运行一次,没有优化/迭代,没有连续处理

数据有 1E6 double 浮点值 x 1E5 列 = 关于 @987654327 @(+HDF5 开销)

尽管有原始帖子,但没有与处理相关的随机 IO

数据获取阶段与 .NET 通信以接收DataELEMENTs 进入 MATLAB

这意味着,从 v7.4 开始,

1.6 GB limit 在 32 位 Win 中的 MATLAB WorkSpace(2.7 GB,带 3GB 开关)

a 1.1 GB limit on MATLAB最大矩阵 in wXP / 1.4 GB wV / 1.5 GB

在 MATLAB WorkSpace 上有点“发布”2.6 GB limit + 32 位 Linux O/S 中最大矩阵的 2.3 GB 限制。

拥有 64 位操作系统不会帮助任何类型的 32 位 MATLAB 7.4 实现,并且由于另一个限制而无法工作,即数组中的最大单元数,这不会涵盖此处请求 1E12。

唯一的机会是同时拥有

两者都是 64 位操作系统(wXP、Linux、Solaris)

64 位 MATLAB 7.5+

MathWorks' source for R2007a cited above, for newer MATLAB R2013a you need a User Account there

数据存储阶段假设将按行排序的数据块(按行排序的数据块的集合)写入 HDD 设备上的MAT-file

数据处理阶段假定在获取所有输入并将其编组到基于文件的非 RAM 之后,在 HDD 设备上重新处理 MAT-file 中的数据- 存储,但以列顺序的方式

只需要按列计算mean()-s / max()-es(没有更复杂的

事实:

MATLAB 对二进制文件使用 HDF5 文件结构的“受限”实现。

Review performance measurements on real-data & real-hardware ( HDD + SSD ) to get feeling of scales of the un-avoidable weaknesses thereof

分层数据格式 (HDF) 于大约 20 年前于 1987 年在国家超级计算应用中心 (NCSA) 诞生。是的,那么老。目标是开发一种结合灵活性效率的文件格式来处理超大型数据集。不知何故,HDF 文件并没有被主流使用,因为只有少数行业确实能够真正利用它的可怕的能力,或者根本不需要它们。

FLEXIBILITY 意味着文件结构承担了一些开销,如果数组的内容没有变化,则不需要使用(您付出成本而不消耗任何好处使用它)和一个假设,即HDF5 限制它可以包含的数据的整体大小有助于并保存问题的 MATLAB 方面是不正确的。

MAT-files 原则上是好的,因为它们避免了将整个文件加载到 RAM 中以便能够使用它的持续需要。

尽管如此,MAT-files 并不能很好地完成此处定义和阐明的简单任务。尝试这样做只会导致性能不佳和 HDD-IO 文件碎片化(在 write-through-s 期间增加几十毫秒,而在 @987654343 @-s 在计算期间)对判断整体性能不佳的核心原因毫无帮助。


专业的解决方案

而不是将整个巨大的 1E12 DataELEMENTs 集合移动到一个 MATLAB 内存代理数据数组中,这只是为下一个即将到来的HDF5/ MAT-file HDD-device IO-s(write-throughs 和 O/S 与硬件设备链冲突/次优化 read-aheads),以便让所有巨大的工作“刚刚 [结婚] 准备好”对于 mean() / max() MATLAB 函数的一些简单的调用(这将尽最大努力改进每个 1E12强> DataELEMENTs 只是另一个顺序(甚至两次 -- 是的 -- 在第一次工作处理噩梦之后,另一个马戏团一直在下降,通过所有的 HDD- IO 瓶颈 ) 回到 MATLAB in-RAM-objects,重新设计这一步从一开始就进入管道 BigDATA 处理。

while true                                          % ref. comment Simon W Oct 1 at 11:29
   [ isStillProcessingDotNET,   ...                 %      a FLAG from .NET reader function
     aDotNET_RowOfVALUEs ...                        %      a ROW  from .NET reader function
     ] = GetDataFromDotNET( aDtPT )                 %                  .NET reader
   if ( isStillProcessingDotNET )                   % Yes, more rows are still to come ...
      aRowCOUNT = aRowCOUNT + 1;                    %      keep .INC for aRowCOUNT ( mean() )
      for i = 1:size( aDotNET_RowOfVALUEs )(2)      %      stepping across each column
         aValue     = aDotNET_RowOfVALUEs(i);       %      
         anIncrementalSumInCOLUMN(i) = ...
         anIncrementalSumInCOLUMN(i) + aValue;      %      keep .SUM for each column ( mean() )
         if ( aMaxInCOLUMN(i) < aValue )            %      retest for a "max.update()"
              aMaxInCOLUMN(i) = aValue;             %      .STO a just found "new" max
         end
      endfor
      continue                                      %      force re-loop
   else
      break
   endif
end
%-------------------------------------------------------------------------------------------
% FINALLY:
% all results are pre-calculated right at the end of .NET reading phase:
%
% -------------------------------
% BILL OF ALL COMPUTATIONAL COSTS ( for given scales of 1E5 columns x 1E6 rows ):
% -------------------------------
% HDD.IO:          **ZERO**
% IN-RAM STORAGE:
%                  Attr Name                       Size                     Bytes  Class
%                  ==== ====                       ====                     =====  =====
%                       aMaxInCOLUMNs              1x100000                800000  double
%                       anIncrementalSumInCOLUMNs  1x100000                800000  double
%                       aRowCOUNT                  1x1                          8  double
%
% DATA PROCESSING:
%
% 1.000.000x .NET row-oriented reads ( same for both the OP and this, smarter BigDATA approach )
%         1x   INT   in aRowCOUNT,                 %%       1E6 .INC-s
%   100.000x FLOATs  in aMaxInCOLUMN[]             %% 1E5 * 1E6 .CMP-s
%   100.000x FLOATs  in anIncrementalSumInCOLUMN[] %% 1E5 * 1E6 .ADD-s
% -----------------
% about 15 sec per COLUMN of 1E6 rows
% -----------------
%                  --> mean()s are anIncrementalSumInCOLUMN./aRowCOUNT
%-------------------------------------------------------------------------------------------
% PIPE-LINE-d processing takes in TimeDOMAIN "nothing" more than the .NET-reader process
%-------------------------------------------------------------------------------------------

您的 管道d BigDATA 计算策略将以一种智能的方式原则上避免 MATLAB 中的临时存储缓冲,因为它将逐步计算结果不超过大约 3 x 1E6 ADD/CMP-registers,全部采用静态布局,避免代理存储到HDF5/MAT-file绝对避免所有与 HDD-IO 相关的瓶颈和低 BigDATA 持续读取速度(根本不谈论临时/BigDATA 持续写入......),并且将 还将避免 表现不佳的内存映射使用仅用于计算均值和最大值。


结语

管道处理在阳光下并不是什么新鲜事。

它重用了以速度为导向的 HPC 解决方案已经使用了几十年

[ BigDATA 标签在营销部门的“发明”之前的几代人。 ]

忘掉无数 HDD-IO 阻塞操作,进入流水线分布式进程到进程解决方案。


没有比这更快的了


如果是,那么所有外汇业务和高频交易对冲基金怪兽都已经存在了......

【讨论】:

虽然这篇长篇大论似乎付出了很多努力,但它并没有回答这个问题。相反,它解决了给出的示例,以便说“你做错了”。 是的西蒙,没错。 改进“方向不正确”的努力无助于您实现一般和受人尊重的目标,即在合理的时间内处理数据。 我没有要求任何人解决我的问题(您对此做了很多假设)。我问了一个关于最佳实践的简单问题,这可能与我自己或将来的其他人有关。你忽略了我问的问题。 这个特殊的计算是近两个月前的结果,现在完全没有实际意义。我同意,鉴于这里的信息有限,给出的方法不是最佳方法。那是无关紧要的。问题是关于一般最佳实践:“鉴于我正在做 A,我应该做 B 吗?”。你已经回答了“你不应该做 A”。这是没有用的。 以前的,有点发誓的回应,已删除。就说我不会再在这里回复了,你也没有为 *** 的声誉做任何事情。【参考方案4】:

对整个讨论的快速回答(以防您没有时间关注或技术理解):

Matlab 中的预分配与 RAM 中的操作相关。 Matlab 不提供对 I/O 操作的低级访问,因此我们不能谈论在磁盘上预分配某些东西。 当大量数据写入磁盘时,观察到写入次数越少,任务执行速度越快,磁盘碎片越小。

因此,如果您不能一口气写入,将写入分成大块

【讨论】:

以上是关于在MATLAB中直接将大数组写入磁盘时,是不是需要预先分配?的主要内容,如果未能解决你的问题,请参考以下文章

在 PHP 中将大文件写入磁盘的最佳方法是啥?

在 Matlab 中读取和写入二进制文件

将大双数写入txt文件C ++ [重复]

将大 RDD 写入 Hive - 将展开内存传输到存储内存失败

PHP fwrite() 用于将大字符串写入文件

写系统调用直接将数据写入磁盘?