在MATLAB matfile中预分配一个大数组,且数组不是用零来填充的方法。

5
我需要将一个太大以至于无法放入内存的数组写入.mat二进制文件。您可以使用matfile命令来完成此操作,该命令允许在磁盘上对.mat文件进行随机访问。
我试图在该文件中预分配数组,并且一位MathWorks博客推荐的方法是:
matObj = matfile('myBigData.mat','Writable',true); 
matObj.X(10000,10000) = 0;

这样做是可行的,但会产生一个含有大量零的数组,这是有风险的,因为我将用一些真实值来填充它,而这些值可能也为零。对于较小的数组,我通常会执行:
smallarray = nan(20,20);

但如果我尝试对大数组使用此方法,我会收到一个“内存不足”的错误;可能是nan()函数先在内存中生成了大量的NaN数组。

如何使用非零值预分配大数组?


1
嗯,我想相关的问题是在这种情况下是否有必要进行预分配。通常的性能优势可能与编写内容到磁盘所需的时间相比微不足道...我猜它可以避免文件被分段? - Flyto
4个回答

4
我发现sclarke81和Sam Robert的回答实际上都不起作用,而且我怀疑预分配的概念是否适用于matfile。下面报告的结果是在i7-3770 CPU @ 3.4 GHz、16.8 GB主存储器上运行Matlab R2013a,Linux 3.16系统下获得的。
代码:
mf = matfile(fn, 'Writable', true);
mf.x(5000, 200000) = 0;
clear mf

理论上,此代码在磁盘上“分配”了8 GB的内存,并初始化为0。然而,生成的文件大小只有4726字节,整个过程不到0.01秒。我可以将大小增加10倍或100倍,但并没有太大变化。很奇怪。顺便说一句,结尾处的clear是为了确保Matlab写入和关闭文件。

通常,我们希望预分配时将NaN初始化而不是0。用这种方法接收更好。

mf = matfile(fn, 'Writable', true);
mf.x = nan(5000, 200000);
clear mf

该方法需要11秒钟,并生成57 MB的文件。但正如OP所指出的,这种方法没有意义,因为它首先在内存中生成整个8 GB矩阵,然后再将其写出,这违背了matfile的目的。如果矩阵可以放入内存,则在处理数据时没有理由将数据保留在文件中。
Sam Roberts建议首先分配/初始化为0,然后将值更改为NaN:
mf = matfile(fn, 'Writable', true);
mf.x(5000, 200000) = 0;
mf.x = mf.x * nan;
clear mf

这需要16秒,生成的文件大小相同。然而,这种方法并不比上面的朴素方法更好,因为在第三行中,整个矩阵被读入内存,与标量NaN在内存中相乘,然后再次写出,导致峰值内存消耗为8GB。(这不仅符合文档中解释的matfile变量的语义,我还使用了内存使用监视器进行了检查。)
sclarke81提议通过以下方式避免在内存中生成矩阵:
mf = matfile(fn, 'Writable', true);
mf.x(1 : 5000, 1 : 200000) = nan;
clear mf

这个想法可能是只在内存中生成一个标量NaN,然后将其复制到磁盘矩阵的每个元素中。然而,事实并非如此。实际上,这种方法似乎会在峰值时消耗约8.38 GB的内存,比朴素方法多出12%!
现在更多地谈论使用matfile进行预分配的优点。如果不进行预分配,但是按行用NaN填充数组。
mf = matfile(fn, 'Writable', true);
for i = 1 : 5000
    mf.x(i, 1 : 200000) = nan(1, 200000);
end
clear mf

这需要27秒。但是,如果预先分配并初始化为0,然后逐行用NaN进行覆盖。
mf = matfile(fn, 'Writable', true);
mf.x(5000, 200000) = 0;
for i = 1 : 5000
    mf.x(i, 1 : 200000) = nan(1, 200000);
end
clear mf

它需要很长时间:当我在45分钟后中止进程时,进程仅完成约3%,推算总运行时间大约为一天! matlab.io.MatFile的行为是黑暗而神秘的,目前似乎只有广泛测试才能找到有效使用此功能的方法。然而,人们可以得出结论,在处理matfile时,预分配是一个不好的想法。

@A Donda:感谢您提供的已识别方法的定量结果。MATLAB/RAM/OS 的限制是已知的,但 HDD.IO 是一个致命问题。仅仅花费几天时间在NaN的预分配上纯粹是一种奢侈浪费资源。正如 >>> https://dev59.com/HV8e5IYBdhLWcg3wEG_A#27083554 所述,matfile/HDF5 和真正的大数据问题需要比预分配更加谨慎的数据处理策略。[注:HDF5格式的优势在于其支持对大规模数据元素进行动态变化的高效实现,而不是用于静态内容] - user3666197
非常有趣!关于您的最后一个发现...我想知道从1(一个整数)转换为nan(一个浮点数)是否可能部分地导致了减速。我目前没有安装MATLAB,所以无法自行检查。您能否重复上次的实验,但将 mf.x 初始化为nan(或类似0.1的浮点数)? - GnomeDePlume
我只是想要查证一下,在MATLAB中NaN是否属于float类型。如果有误,敬请谅解。我已经太习惯Python/Numpy了! - GnomeDePlume
@GnomeDePlume,Matlab中的数字默认都是8字节浮点数(“双精度”),要获取其他类型需要特殊处理。据我所知,NaN甚至在除了浮点数格式之外的任何地方都没有定义。也没有转换,nan(1, 200000)的意思是“给我一个大小为1 x 200000的数组,并用NaN进行初始化”。 - A. Donda
我怀疑减速是因为 mat 文件被大量压缩,而更改任何内容都意味着需要读取、解压缩、更新内容、压缩、写入。我试图找到一种禁用压缩的方法,但没有找到。我怀疑使用 memmapfile 而不是 matfile 对 OP 的目的(以及我的目的)更有用。 - A. Donda

0

这个方法对我很有效。请注意,您必须为矩阵索引指定范围(X(1:10000,1:10000)),否则您只会将单个元素设置为NaN,而不是整个矩阵。

matObj = matfile('myBigData.mat','Writable',true); 
matObj.X(1:10000,1:10000) = NaN;

0

你能做类似这样的事情吗:

matObj = matfile('myBigData.mat','Writable',true); 
matObj.X(10000,10000) = 0;

然后

matObj.X = matObj.X + 1;

或者

matObj.X = matObj.X * NaN;

?


这是一个好想法,而且它有效。我会接受另一个答案,因为它涉及的步骤更少,但还是谢谢你。 - Flyto
我同意 - 另一个答案更好。 - Sam Roberts

0

这可以通过将二进制文件进行内存映射来实现,使用MappedTensor(自引用)。

% - Create and map a large 'double' tensor to a temporary file on disk
mt = MappedTensor(100, 100, 100);

% - % Write 'nan' to every element of 'mt', without allocating entire tensor
mt(:) = nan;

您可以尝试使用memmapfile的类似方法,但是当写入到映射文件时,memmapfile会为整个张量分配空间。

您可以使用PC上的fsutil或Mac或Linux机器上的fallocate进行预分配,然后映射特定的二进制文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接