11

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

我正在使用 MATLAB R2009b,我需要编写一个更大的脚本,将一组更大的 .zip 文件的内容转换为 v7.3 mat 文件(具有底层 HDF5 数据模型)。读书没问题。问题在于储蓄。而且实际上没有问题。我的文件使用save命令保存得很好。

我的问题更有意义:为什么我在 MATLAB 中观察到以下令人惊讶的(对我而言)行为?

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

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

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

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

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

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

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

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

   % 为当前块生成临时结构:
   temp_stu_value = 结构();
   temp_stu_value.frames = 兰迪(...
      [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;

结尾

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

让我们先尝试一个简单的:

% 保存数据(简单):
disp('保存数据的简单方法:')
抽动;
保存转换后的.mat -struct stu_data2disk -v7.3;
目录;

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

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

好的 - 然后我记得我想在 40 个块上遵循保存程序。因此,我不是上面的,而是遍历块并按顺序附加它们:

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

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

   temp_str_appendToggle = '';
   如果 (num_k > 1)
      temp_str_appendToggle = '-追加';
   结尾

   % 生成保存命令:
   temp_str_saveCommand = [ ...
      '节省 ', ...
      'converted_append.mat', ...
      '-struct stu_data2disk',temp_str_blockName,''...
      temp_str_appendToggle, ' ', ...
      '-v7.3', ...
      ';' ...
   ];

   % 评估保存命令:
   评估(temp_str_saveCommand);

结尾
目录;

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

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

有趣的是,附加方法要快得多?我的问题是为什么?

输出dir converted*.mat

09-02-2011 20:38 300,236,392 转换.mat
2011 年 9 月 2 日 20:37 300,264,316 convert_append.mat
               2 个文件 600,500,708 字节

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

有人知道这里发生了什么吗?附加文件是否使用了更优化的数据结构?或者,也许 Windows 已经缓存了文件并使其访问速度更快?

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

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

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

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

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

15:15:51.422:测试速度,格式=-v6,PCWIN 上的 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:测试速度,格式=-v7,PCWIN 上的 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:测试速度,格式=-v7.3,PCWIN 上的 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 秒

以及文件的大小:

2011 年 2 月 10 日 15:16 299,528,768 转换格式-v6.mat
2011 年 2 月 10 日 15:16 299,528,768 转换_append_format-v6.mat
10-02-2011 15:16 299,528,832 转换_append_batch_format-v6.mat
2011 年 2 月 10 日 15:16 299,894,027 转换格式-v7.mat
2011 年 2 月 10 日 15:17 299,894,027 转换的_append_format-v7.mat
10-02-2011 15:17 299,894,075 转换_append_batch_format-v7.mat
10-02-2011 15:17 300,236,392 convert_format-v7.3.mat
10-02-2011 15:17 300,264,316 convert_append_format-v7.3.mat
10-02-2011 15:18 300,101,800 转换_append_batch_format-v7.3.mat
               9 个文件 2,698,871,005 字节

因此 -v6 似乎是最快的编写方式。文件大小也没有太大差异。据我所知,HDF5 确实内置了一些基本的膨胀方法。

嗯,可能是对底层 HDF5 写入函数的一些优化?

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

其他需要注意的细节:

正如我们在下面安德鲁的回答中看到的那样,这种行为非常系统化。至于您是在函数的本地范围内还是在 m 脚本的“全局”范围内运行这些东西,这似乎也很重要。我的第一个结果来自将文件写入当前目录的 m 脚本。我仍然只能在 m 脚本中重现 -7.3 的 1 秒写入。函数调用显然增加了一些开销。

2016 年 7 月更新

我再次找到了这个,并认为我可以使用目前可用的最新 MATLAB 对其进行测试。在 Windows 7 x64 上使用 MATLAB R2016a,问题似乎已得到解决:

14:04:06.277: 测试速度, 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:基本 -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:基本 -v7.3:7.919 秒 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 秒 5.819 GB 已使用
14:04:58.544:multiappend -v7.3:7.905 秒 5.834 GB 已使用
14:05:06.485:multiappend -v7.3:8.013 秒 5.844 GB 已使用
14:05:14.542:multiappend -v7.3:8.591 秒 5.860 GB 已使用
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 秒 5.837 GB 已使用
14:05:39.676: bigappend -v7.3: 7.740 秒 5.879 GB 已使用
14:05:47.453: bigappend -v7.3: 7.645 秒 5.884 GB 已使用
14:05:55.133: bigappend -v7.3: 7.656 秒 5.877 GB 已使用
14:06:02.824: bigappend -v7.3: 7.963 sec 5.871 GB used 286 MB file 7.746 sec mean

这在下面接受的答案中使用 Andrew Janke 的reproMatfileAppendSpeedup函数进行了测试(5 次通过,格式为 7.3)。现在,-append对于单次保存来说同样慢,或者更慢 - 应该是这样。可能是 R2009a 中使用的 HDF5 驱动程序的早期版本存在问题。

4

3 回答 3

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

这看起来很大。如果它适用于其他数据集,我自己可能会在很多地方使用这个技巧。MathWorks 也可能会带来一些问题。他们是否也可以在正常保存或其他操作系统版本中使用快速附加技术?

这是独立的复制功能。

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 的一些东西使它更愿意这样做。但对我来说,包括这些摊位在内的摊销时间仍然比基本保存快得多。

顺便说一句,在进行了几个小时的多次传递后,我很难重现原始时间。

于 2011-02-10T01:22:47.300 回答
3

只是一个更新,以防它对其他人有用。我发现了 Matlab 错误784028,它表明-append从 2012a 开始,对行为的无压缩已修复。从我的系统上的一些测试来看,情况确实如此,压缩发生在变量 >10000 字节的情况下,无论是否使用附加,而较小的变量永远不会发生。

不幸的是,另一方面,似乎根本没有办法控制-v7.3文件压缩的​​使用。

于 2012-10-24T14:28:19.530 回答
2

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

至于一次保存所有变量与一次追加一个变量的比较,我也对结果感到惊讶......

于 2011-02-10T16:33:30.370 回答