问题 MATLAB:将几个变量保存到“-v7.3”(HDF5)。使用“-append”标志时,.mat文件似乎更快。怎么来的?


注意:  这个问题涉及2011年使用旧的MATLAB版本(R2009a)观察到的问题。根据2016年7月以下的更新,MATLAB中的问题/错误似乎不再存在(使用R2016a测试;向下滚动到问题末尾以查看更新)。

我正在使用MATLAB R2009b,我需要编写一个更大的脚本,将更大的.zip文件集的内容转换为v7.3 mat文件(带有底层的HDF5-datamodel)。读书还可以。问题在于储蓄。实际上没有问题。我的文件很好地使用了 保存 命令。

从某种意义上说,我的问题更多:为什么我在MATLAB中观察到以下令人惊讶的(对我来说)行为?

让我们来看看我的问题。在当前的测试场景中,我将生成一个输出:A -v7.3 mat-file。此.mat文件将包含40个  作为个体变量。每个变量将从1到40命名为“block_NNN”,并包含带字段的结构  和 块编号。领域  包含一个480x240x65的uint8 imagedata序列(这里只是使用生成的随机数据) 兰迪)。领域 块编号 包含块编号。

备注: 在真实的脚本(我还没有完成)中,我将完成上述总共370次,转换总共108GB的原始数据。这就是为什么我关注以下内容。

无论如何,首先我定义一些通用变量:

%虚拟数据和循环的大小:
num_blockCount = 40;
num_blockLength = 65;
num_frameHeight = 480;
num_frameWidth = 240;

然后我生成一些形状和大小与实际原始数据相同的虚拟代码:

%生成空结构:
stu_data2disk = struct();

块循环%循环:
对于num_k = 1:num_blockCount

   %生成块名称:
   temp_str_blockName = sprintf('block_%03u',num_k);

   %为当前块生成临时结构:
   temp_stu_value = struct();
   temp_stu_value.frames = randi(...
      [0 255],...
      [num_frameHeight num_frameWidth num_blockLength],...
      'uint8'......
   );
   temp_stu_value.blockNo = num_k;

   %使用动态字段名称:
   stu_data2disk。(sprintf('block_%03u',num_k))= temp_stu_value;

结束

我现在在结构中拥有所有随机测试数据 stu_data2disk。现在我想使用两种可能的方法之一保存数据。

我们先来试试这个简单的:

%保存数据(简单):
disp('以简单的方式保存数据:')
抽动;
save converted.mat -struct stu_data2disk -v7.3;
TOC;

该文件写得没有问题(286MB)。输出是:

以简单的方式保存数据:
经过的时间是14.004449秒。

好的 - 然后我记得我想遵循40个街区的保存程序。因此,代替上面的I循环遍历块并按顺序附加它们:

%保存到文件,使用追加:
disp('使用-append:'保存数据)
抽动;
对于num_k = 1:num_blockCount

   %生成块名称:
   temp_str_blockName = sprintf('block_%03u',num_k);

   temp_str_appendToggle ='';
   if(num_k> 1)
      temp_str_appendToggle =' -  append';
   结束

   %generate save命令:
   temp_str_saveCommand = [...
      '保存 ', ...
      'converted_append.mat',...
      '-struct stu_data2disk',temp_str_blockName,''...
      temp_str_appendToggle,'',...
      '-v7.3',......
      ';' ...
   ]。

   %evaluate save命令:
   的eval(temp_str_saveCommand);

结束
TOC;

再次文件保存得很好(286MB)。输出是:

使用-append保存数据:
经过的时间是0.956968秒。

有趣的是,append-method更快? 我的问题是为什么?

输出来自 dir converted*.mat

09-02-2011 20:38 300,236,392 converted.mat
09-02-2011 20:37 300,264,316 converted_append.mat
               2个文件600,500,708字节

文件大小不同。和一个测试 FC 在Windows 7中透露......很多二进制差异。也许数据有点偏移 - 因此这没有告诉我们什么。

有人知道这里发生了什么吗?附加文件是否使用了更加优化的数据结构?或者Windows可能会缓存该文件并更快地访问它?

我也努力从两个文件中读取测试。如果没有在这里显示数字,附加的版本会更快一点(虽然从长远来看可能意味着什么)。

[编辑]:我只是尝试使用无格式标志(在我的系统上默认为-v7)并且没有太大的区别了:

以简单的方式保存数据(-v7):
经过的时间是13.092084秒。
使用-append(-v7)保存数据:
经过的时间是14.345314秒。

[编辑]:我纠正了上面的错误。以前我提到过-v6的统计数据,但我错了。我刚刚删除了格式标志,并假设默认值为-v6,但实际上它是-v7。

我使用安德鲁的精细框架为我的系统上的所有格式创建了新的测试统计数据(所有格式都是针对相同的随机测试数据,现在从文件中读取):

15:15:51.422:在PCWIN上测试速度,格式= -v6,R2009b,arch = x86,os = Microsoft Windows 7 Professional 6.1.7600 N / A Build 7600
15:16:00.829:保存简单方法:0.358秒
15:16:01.188:使用多个追加保存:7.432秒
15:16:08.614:使用一个大的附加物保存:1.161秒

15:16:24.659:在PCWIN上测试速度,格式= -v7,R2009b,arch = x86,os = Microsoft Windows 7 Professional 6.1.7600 N / A Build 7600
15:16:33.442:保存简单的方法:12.884秒
15:16:46.329:使用多个追加保存:14.442秒
15:17:00.775:使用一个大附加物保存:13.390秒

15:17:31.579:在PCWIN上测试速度,格式= -v7.3,R2009b,arch = x86,os = Microsoft Windows 7 Professional 6.1.7600 N / A Build 7600
15:17:40.690:保存简单的方法:13.751秒
15:17:54.434:使用多个追加保存:3.970秒
15:17:58.412:使用一个大的附加物保存:6.138秒

以及文件的大小:

10-02-2011 15:16 299,528,768 converted_format-v6.mat
10-02-2011 15:16 299,528,768 converted_append_format-v6.mat
10-02-2011 15:16 299,528,832 converted_append_batch_format-v6.mat
10-02-2011 15:16 299,894,027 converted_format-v7.mat
10-02-2011 15:17 299,894,027 converted_append_format-v7.mat
10-02-2011 15:17 299,894,075 converted_append_batch_format-v7.mat
10-02-2011 15:17 300,236,392 converted_format-v7.3.mat
10-02-2011 15:17 300,264,316 converted_append_format-v7.3.mat
10-02-2011 15:18 300,101,800 converted_append_batch_format-v7.3.mat
               9个文件2,698,871,005个字节

因此-v6似乎是最快的写作。文件大小也没有任何大的差异。据我所知,HDF5确实内置了一些基本的inflate-method。

嗯,可能是基础HDF5写入功能的一些优化?

目前我仍然认为一些基础HDF5写入功能已针对添加进行了优化 数据集 到HDF5文件(这是在向-7.3文件添加新变量时发生的情况)。我相信我已经读过HDF5应该以这种方式进行优化的地方......虽然不能确定。

其他细节需要注意:

我们在安德鲁的答案中看到,这种行为非常系统化。关于你是否在函数的局部范围或m脚本的“全局”中运行这些东西似乎也很重要。我的第一个结果是来自m脚本,其中文件被写入当前目录。我仍然只能在m脚本中重现-7.3的1秒写入。函数调用显然增加了一些开销。

2016年7月更新

我再次发现了这一点,并认为我可以使用目前最新的MATLAB测试它。使用Windows 7 x64上的MATLAB R2016a,问题似乎已得到解决:

14:04:06.277:PCWIN64上的测试速度,imax = 255,R2016a,arch = AMD64,16 GB,os = Microsoft Windows 7企业版6.1(Build 7601:Service Pack 1)
14:04:10.600:基本-v7.3:7.599秒使用5.261 GB
14:04:18.229:基本-v7.3:7.894秒5.383 GB使用
14:04:26.154:基本-v7.3:7.909秒5.457 GB使用
14:04:34.096:basic -v7.3:7.919 sec 5.498 GB使用
14:04:42.048:基本-v7.3:7.886秒5.516 GB使用286 MB文件7.841秒均值
14:04:50.581:multiappend -v7.3:7.928 sec 5.819 GB使用
14:04:58.544:multiappend -v7.3:7.905 sec使用5.834 GB
14:05:06.485:multiappend -v7.3:8.013 sec使用5.844 GB
14:05:14.542:multiappend -v7.3:8.591 sec使用5.860 GB
14:05:23.168:multiappend -v7.3:8.059 sec 5.868 GB使用286 MB文件8.099秒均值
14:05:31.913:bigappend -v7.3:7.727秒5.837 GB使用
14:05:39.676:bigappend -v7.3:7.740 sec使用5.879 GB
14:05:47.453:bigappend -v7.3:7.645 sec 5.884 GB使用
14:05:55.133:bigappend -v7.3:7.656 sec 5.877 GB使用
14:06:02.824:bigappend -v7.3:7.963 sec 5.871 GB使用286 MB文件7.746秒意味着

这是与Andrew Janke的测试 reproMatfileAppendSpeedup 函数在下面接受的答案中(格式7.3的5次传递)。现在, -append 对于单个保存来说同样缓慢或缓慢 - 应该如此。也许这是R2009a中使用的HDF5驱动程序的早期版本的问题。


12328
2018-02-09 20:13


起源

奇怪。我认为-append会慢一点(见 stackoverflow.com/questions/4268044/...)。本地或网络文件系统?尝试在Process Explorer或Process Monitor中观察保存操作,以查看每个IO(字节和操作的进出)。 - Andrew Janke
很可能是缓存。或者 - -pendpend使Matlab以不同的方式打开RW的文件(例如mem-mapped),这在你的情况下更快。或者可能在一个大块中写入300MB文件与缓冲区或其他东西进行交互并使其变慢。作为另一个测试,如果您将一个简单的小变量保存到一个文件,然后在一次调用中将所有块附加到“save -append”,会发生什么?将每个块保存到单独的文件有多快?这可能有助于区分-append模式和块大小。 - Andrew Janke
@Andrew:谢谢你的评论。令人惊讶的是:)但是我又很高兴我花时间编写了更快的循环。我会看看我是否有时间去做那些测试。虽然睡觉时间:) - Ole Thomsen Buus
我的猜测是,附加时压缩阶段会减少。当附加时,Matlab可能会独立地压缩每个块,并且可能比同样总大小的1个大块压缩40个小块更快。 - grantnz
@grantnz:单独的保存不应该影响压缩。即使不附加,Matlab也会单独压缩每个变量(而“变量”我认为它们实际上意味着每个单独的原始数组)。请参阅MAT文件格式参考文档 mathworks.com/help/pdf_doc/matlab/matfile_format.pdf,第1-11节。但是“-v6”选项关闭压缩,他的-v6测试速度较慢,所以可能会。 @Ole,-v6文件有多大? - Andrew Janke


答案:


天啊。我可以重现。尝试了单附加变化;它甚至更快。看起来像“-append”只是神奇地使基于HDF5的save()快30倍。我没有解释,但我想分享我发现的东西。

我将测试代码包装在一个函数中,重构它以使保存逻辑与测试数据结构无关,这样您就可以在其他数据集上运行它,并添加了一些更多的诊断输出。

到处都看不到大的加速。它在我的64位XP盒子和32位Server 2003盒子上是巨大的,在我的64位Windows 7盒子上很大,在32位XP盒子上不存在。 (虽然在Server 2003上有多个附加功能是一个巨大的损失。)在许多情况下R2010b速度较慢。也许HDF5附加或保存使用它只是摇滚新的Windows版本。 (XP x64实际上是Server 2003内核。)或者它可能只是一个机器配置差异。 XP x64机器上有一个快速的RAID,而32位XP的RAM比其余的少。你在运行什么操作系统和架构?你也可以试试这个复制品吗?

19:36:40.289: Testing speed, format=-v7.3, R2009b on PCWIN64, arch=AMD64, os=Microsoft(R) Windows(R) XP Professional x64 Edition 5.2.3790 Service Pack 2 Build 3790
19:36:55.930: Save the simple way:           11.493 sec
19:37:07.415: Save using multiple append:     1.594 sec
19:37:09.009: Save using one big append:      0.424 sec


19:39:21.681: Testing speed, format=-v7.3, R2009b on PCWIN, arch=x86, os=Microsoft Windows XP Professional 5.1.2600 Service Pack 3 Build 2600
19:39:37.493: Save the simple way:           10.881 sec
19:39:48.368: Save using multiple append:    10.187 sec
19:39:58.556: Save using one big append:     11.956 sec


19:44:33.410: Testing speed, format=-v7.3, R2009b on PCWIN64, arch=AMD64, os=Microsoft Windows 7 Professional  6.1.7600 N/A Build 7600
19:44:50.789: Save the simple way:           14.354 sec
19:45:05.156: Save using multiple append:     6.321 sec
19:45:11.474: Save using one big append:      2.143 sec


20:03:37.907: Testing speed, format=-v7.3, R2009b on PCWIN, arch=x86, os=Microsoft(R) Windows(R) Server 2003, Enterprise Edition 5.2.3790 Service Pack 2 Build 3790
20:03:58.532: Save the simple way:           19.730 sec
20:04:18.252: Save using multiple append:    77.897 sec
20:05:36.160: Save using one big append:      0.630 sec

这看起来很大。如果它支持其他数据集,我可能会在很多地方使用这个技巧。这也可能是MathWorks的一部分。他们是否可以在普通保存或其他操作系统版本中使用快速附加技术?

这是独立的repro功能。

function out = reproMatfileAppendSpeedup(nPasses, tests, imax, formats)
%REPROMATFILEAPPENDSPEEDUP Show how -append makes v7.3 saves much faster
%
% Examples:
% reproMatfileAppendSpeedup()
% reproMatfileAppendSpeedup(2, [], 0, {'7.3','7','6'}); % low-entropy test

if nargin < 1 || isempty(nPasses);  nPasses = 1;  end
if nargin < 2 || isempty(tests);    tests = {'basic','multiappend','bigappend'}; end
if nargin < 3 || isempty(imax);     imax = 255; end
if nargin < 4 || isempty(formats);  formats = '7.3'; end % -v7 and -v6 do not show the speedup
tests = cellstr(tests);
formats = cellstr(formats);

fprintf('%s: Testing speed, imax=%d, R%s on %s\n',...
    timestamp, imax, version('-release'), systemDescription());

tempDir = setupTempDir();
testData = generateTestData(imax);

testMap = struct('basic','saveSimple', 'multiappend','saveMultiAppend', 'bigappend','saveBigAppend');

for iFormat = 1:numel(formats)
    format = formats{iFormat};
    formatFlag = ['-v' format];
    %fprintf('%s: Format %s\n', timestamp, formatFlag);
    for iTest = 1:numel(tests)
        testName = tests{iTest};
        saveFcn = testMap.(testName);
        te = NaN(1, nPasses);
        for iPass = 1:nPasses
            fprintf('%s: %-30s', timestamp, [testName ' ' formatFlag ':']);
            t0 = tic;
            matFile = fullfile(tempDir, sprintf('converted-%s-%s-%d.mat', testName, format, i));
            feval(saveFcn, matFile, testData, formatFlag);
            te(iPass) = toc(t0);
            if iPass == nPasses
                fprintf('%7.3f sec      %5.3f GB used   %5.0f MB file   %5.3f sec mean\n',...
                    te(iPass), physicalMemoryUsed/(2^30), getfield(dir(matFile),'bytes')/(2^20), mean(te));
            else
                fprintf('%7.3f sec      %5.3f GB used\n', te(iPass), physicalMemoryUsed/(2^30));
            end
        end
        % Verify data to make sure we are sane
        gotBack = load(matFile);
        gotBack = rmfield(gotBack, intersect({'dummy'}, fieldnames(gotBack)));
        if ~isequal(gotBack, testData)
            fprintf('ERROR: Loaded data differs from original for %s %s\n', formatFlag, testName);
        end
    end
end

% Clean up
rmdir(tempDir, 's');

%%
function saveSimple(file, data, formatFlag)
save(file, '-struct', 'data', formatFlag);

%%
function out = physicalMemoryUsed()
if ~ispc
    out = NaN;
    return; % memory() only works on Windows
end
[u,s] = memory();
out = s.PhysicalMemory.Total - s.PhysicalMemory.Available;

%%
function saveBigAppend(file, data, formatFlag)
dummy = 0;
save(file, 'dummy', formatFlag);
fieldNames = fieldnames(data);
save(file, '-struct', 'data', fieldNames{:}, '-append', formatFlag);

%%
function saveMultiAppend(file, data, formatFlag)
fieldNames = fieldnames(data);
for i = 1:numel(fieldNames)
    if (i > 1); appendFlag = '-append'; else; appendFlag = ''; end
    save(file, '-struct', 'data', fieldNames{i}, appendFlag, formatFlag);
end


%%
function testData = generateTestData(imax)
nBlocks = 40;
blockSize = [65 480 240];
for i = 1:nBlocks
    testData.(sprintf('block_%03u', i)) = struct('blockNo',i,...
        'frames', randi([0 imax], blockSize, 'uint8'));
end

%%
function out = timestamp()
%TIMESTAMP Showing timestamps to make sure it is not a tic/toc problem
out = datestr(now, 'HH:MM:SS.FFF');

%%
function out = systemDescription()
if ispc
    platform = [system_dependent('getos'),' ',system_dependent('getwinsys')];
elseif ismac
    [fail, input] = unix('sw_vers');
    if ~fail
        platform = strrep(input, 'ProductName:', '');
        platform = strrep(platform, sprintf('\t'), '');
        platform = strrep(platform, sprintf('\n'), ' ');
        platform = strrep(platform, 'ProductVersion:', ' Version: ');
        platform = strrep(platform, 'BuildVersion:', 'Build: ');
    else
        platform = system_dependent('getos');
    end
else
    platform = system_dependent('getos');
end
arch = getenv('PROCESSOR_ARCHITEW6432');
if isempty(arch)
    arch = getenv('PROCESSOR_ARCHITECTURE');
end
try
    [~,sysMem] = memory();
catch
    sysMem.PhysicalMemory.Total = NaN;
end
out = sprintf('%s, arch=%s, %.0f GB, os=%s',...
    computer, arch, sysMem.PhysicalMemory.Total/(2^30), platform);

%%
function out = setupTempDir()
out = fullfile(tempdir, sprintf('%s - %s', mfilename, datestr(now, 'yyyymmdd-HHMMSS-FFF')));
mkdir(out);

编辑:我修改了repro函数,添加了多次迭代并将其参数化为randi生成器的保存样式,文件格式和imax。

我认为文件系统缓存是快速重叠行为的一个重要因素。当我使用reproMatfileAppendSpeedup(20)连续运行一堆并在Process Explorer中查看系统信息时,大多数都在一秒之内,并且物理内存使用量会快速增加几GB。然后每十几次传递,写入停顿并花费20或30秒,并且物理RAM使用率缓慢下降到大约开始的位置。我认为这意味着Windows在RAM中缓存了大量写入,并且有关-append的内容使其更愿意这样做。但对于我来说,包括那些摊位在内的摊还时间仍比基本保存快得多。

顺便说一下,经过几个小时的多次通过后,我很难再现原始时间。


8
2018-02-10 01:22



@Andrew:谢谢你。这非常有帮助。我在我的系统上复制了你的功能输出(win7 pro,32位)。查看原始问题的编辑。注意:我禁用了随机数据生成器 - 而是从内存中为所有格式写入相同的随机数据。听到在Mathworks工作的人的消息可能非常有趣 - 也许在stackoverflow的任何人?也许直接负责的人 保存 命令:) - Ole Thomsen Buus
@AndrewJanke:+1非常有趣的比较 - Amro
注意 memory() 在Mac上不可用。我在你的脚本上运行了 MACI64 系统(10.6)并看到类似的结果。结果:基本/多重/重叠 - > 12.7 / 3.6 / 3.1。 - Matt B.
@Andrew:我刚刚在你的精彩作品中编辑过一点(非常感谢你的努力和兴趣)。 (希望它没关系,编辑。我仍在研究stackexchange站点模型背后的思维方式,但我希望得到这个想法)。无论如何,你可以理解地在“bigappend”版本中添加一个“虚拟”变量。正如您在上面的编辑中看到的那样(在函数“saveBigAppend”中),这不是严格必要的。否则,如果在参数中没有使用“bigappend”,那么您的脚本将在读取测试中崩溃 测试。顺便说一句,如果脚本只对“bigappend”的结果进行读取测试? - Ole Thomsen Buus
我已经使系统描述平台独立。此外,我已经使用默认设置在OSX 64位上重现了测试,发现R2011a比2010b快一点,但是有一致的加速(16/6/5有3次运行和默认值 - 嗯,也许我需要一台更快的电脑)。所有文件都是286MB,所以我假设没有压缩。另外:+1用于编写测试功能。 - Jonas


只是一个更新,以防其他人有用。我找到了Matlab的bug 784028 这表明没有压缩 -append 行为是从2012a修复的。从我的系统上的一些测试确实是这种情况,对于变量> 10000字节进行压缩,使用或不使用append,并且对于较小的变量永远不会发生。

不幸的是,另一方面是没有任何方法可以控制压缩的使用 -v7.3文件。


3
2017-10-24 14:28





完成的实验 @AndrewJanke 非常有趣。需要记住的一点是,您比较的三种MAT文件格式完全不同:v6是未压缩的,v7是压缩的,而v7.3也是压缩的,但使用完全不同的实现(HDF5标准格式与自定义MATLAB优化格式)。

至于 保存 - 全瓦尔在一次性 与 追加一-VAR-AT-A-时间 比较,我也对结果感到惊讶......


2
2018-02-10 16:33



@Amro:好的 - v6是未压缩的。错过了那个细节。好的,那么如果你看看我提到的文件大小 编辑 问题,你可以看到他们都在285-286 MiB左右。这告诉我,我需要找到真实的图像数据并尝试一下(参见我对上面关于压缩均匀分布的随机数据的问题的评论)。 - Ole Thomsen Buus
@OleThomsenBuus:为了说明压缩的效果,用@AndrewJanke代码替换RANDI调用: ones(sz,'uint8') (在 generateTestData() 功能)。结果(就文件大小而言)是v6不受影响,v7是最小的,后面是HDF5格式的v7.3,因为它有更大的存储开销(参考这个问题: stackoverflow.com/questions/4950630/...) - Amro
@OleThomsenBuus:事实上,如果你尝试我为“append-cases”建议的修改,我们可以观察到对于v7.3格式,每个变量本身都会进行压缩(因为它没有检测到变量之间的重复) ,在v7的情况下,由于在写入时压缩了所有内容,文件明显变小了... - Amro
@Amro - 好点。我修改了repro函数来参数化randi;如果你将imax设置为0,你将获得全零,这相当于那些调用。奇怪的是,即使使用全0,HDF5文件似乎也没有得到任何压缩;它仍然是286 MB,HDF5标题在h5dump中显示压缩比为1.000:1。 - Andrew Janke
@Andrew:我尝试了Amro的想法,分别获得了1 MiB,279 MiB和286 MiB的简单,附加和批量追加。时间:0.72,0.68和4.41s。我希望我的图像数据的“随机性”介于所有0和均匀伪随机噪声之间。似乎只有“简单”方法才能给出全零的预期压缩。也许从附加块中无法获得任何好处:我最终会使用比必要更大的.mat文件。嗯... - Ole Thomsen Buus