MATLAB:将几个变量保存到"-v7.3"(HDF5).使用"-append"标志时,.mat文件似乎更快.怎么会?

Ole*_*uus 11 file-io matlab file hdf5 mat-file

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

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

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

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

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

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

% some sizes for dummy data and loops:
num_blockCount = 40;
num_blockLength = 65;
num_frameHeight = 480;
num_frameWidth = 240;

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

% generate empty struct:
stu_data2disk = struct();

% loop over blocks:
for num_k = 1:num_blockCount

   % generate block-name:
   temp_str_blockName = sprintf('block_%03u', num_k);

   % generate temp struct for current block:
   temp_stu_value = struct();
   temp_stu_value.frames = randi( ...
      [0 255], ...
      [num_frameHeight num_frameWidth num_blockLength], ...
      'uint8' ...
   );
   temp_stu_value.blockNo = num_k;

   % using dynamic field names:
   stu_data2disk.(sprintf('block_%03u', num_k)) = temp_stu_value;

end

我现在将所有随机测试数据放在struct stu_data2disk中.现在我想使用两种可能的方法之一保存数据.

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

% save data (simple):
disp('Save data the simple way:')
tic;
save converted.mat -struct stu_data2disk -v7.3;
toc;

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

Save data the simple way:
Elapsed time is 14.004449 seconds.

好的 - 然后我记得我想跟随40个街区的保存程序.因此,而不是上面的I循环块并按顺序附加它们:

% save to file, using append:
disp('Save data using -append:')
tic;
for num_k = 1:num_blockCount

   % generate block-name:
   temp_str_blockName = sprintf('block_%03u', num_k);

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

   % generate save command:
   temp_str_saveCommand = [ ...
      'save ', ...
      'converted_append.mat ', ...
      '-struct stu_data2disk ', temp_str_blockName, ' '...
      temp_str_appendToggle, ' ', ...
      '-v7.3', ...
      ';' ...
   ];

   % evaluate save command:
   eval(temp_str_saveCommand);

end
toc;

而且文件保存得很好(286MB).输出是:

Save data using -append:
Elapsed time is 0.956968 seconds.

有趣的是,追加方法更快?我的问题是为什么?

输出来自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 File(s)    600,500,708 bytes

文件大小不同.在Windows 7中使用fc进行的测试显示......很多二进制差异.也许数据有点偏移 - 因此这没有告诉我们什么.

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

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

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

Save data the simple way (-v7):
Elapsed time is 13.092084 seconds.
Save data using -append (-v7):
Elapsed time is 14.345314 seconds.

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

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

15:15:51.422: Testing speed, format=-v6, R2009b on PCWIN, arch=x86, os=Microsoft Windows 7 Professional  6.1.7600 N/A Build 7600
15:16:00.829: Save the simple way:            0.358 sec
15:16:01.188: Save using multiple append:     7.432 sec
15:16:08.614: Save using one big append:      1.161 sec

15:16:24.659: Testing speed, format=-v7, R2009b on PCWIN, arch=x86, os=Microsoft Windows 7 Professional  6.1.7600 N/A Build 7600
15:16:33.442: Save the simple way:           12.884 sec
15:16:46.329: Save using multiple append:    14.442 sec
15:17:00.775: Save using one big append:     13.390 sec

15:17:31.579: Testing speed, format=-v7.3, R2009b on PCWIN, arch=x86, os=Microsoft Windows 7 Professional  6.1.7600 N/A Build 7600
15:17:40.690: Save the simple way:           13.751 sec
15:17:54.434: Save using multiple append:     3.970 sec
15:17:58.412: Save using one big append:      6.138 sec

以及文件的大小:

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 File(s)  2,698,871,005 bytes

因此-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: Testing speed, imax=255, R2016a on PCWIN64, arch=AMD64, 16 GB, os=Microsoft Windows 7 Enterprise  Version 6.1 (Build 7601: Service Pack 1)
14:04:10.600: basic -v7.3:                    7.599 sec      5.261 GB used
14:04:18.229: basic -v7.3:                    7.894 sec      5.383 GB used
14:04:26.154: basic -v7.3:                    7.909 sec      5.457 GB used
14:04:34.096: basic -v7.3:                    7.919 sec      5.498 GB used
14:04:42.048: basic -v7.3:                    7.886 sec      5.516 GB used     286 MB file   7.841 sec mean
14:04:50.581: multiappend -v7.3:              7.928 sec      5.819 GB used
14:04:58.544: multiappend -v7.3:              7.905 sec      5.834 GB used
14:05:06.485: multiappend -v7.3:              8.013 sec      5.844 GB used
14:05:14.542: multiappend -v7.3:              8.591 sec      5.860 GB used
14:05:23.168: multiappend -v7.3:              8.059 sec      5.868 GB used     286 MB file   8.099 sec mean
14:05:31.913: bigappend -v7.3:                7.727 sec      5.837 GB used
14:05:39.676: bigappend -v7.3:                7.740 sec      5.879 GB used
14:05:47.453: bigappend -v7.3:                7.645 sec      5.884 GB used
14:05:55.133: bigappend -v7.3:                7.656 sec      5.877 GB used
14:06:02.824: bigappend -v7.3:                7.963 sec      5.871 GB used     286 MB file   7.746 sec mean

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

And*_*nke 8

天啊.我可以重现.尝试了单附加变化; 它甚至更快.看起来像"-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
Run Code Online (Sandbox Code Playgroud)

这看起来很大.如果它能够支持其他数据集,我可能会在很多地方使用这个技巧.这也可能是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);
Run Code Online (Sandbox Code Playgroud)

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

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

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