MATLAB: сохранение нескольких переменных в " - v7.3" (HDF5).mat-файлы, кажется, быстрее при использовании флага "- append". Почему?

Примечание: этот вопрос касается проблемы, наблюдавшейся еще в 2011 году со старой версией MATLAB (R2009a). В соответствии с обновлением ниже с июля 2016 года проблема / ошибка в MATLAB, похоже, больше не существует (протестировано с R2016a; прокрутите вниз до конца вопроса, чтобы увидеть обновление).

я использую MATLAB R2009b, и мне нужно написать больший скрипт, который преобразует содержимое большего набора .zip-файлы для v7.3 файла mat (с базовой моделью HDF5-datamodel). Чтение ОК. Проблема в экономии. И на самом деле нет никаких проблем. Мои файлы хорошо сохраняются с помощью сохранить.

мой вопрос больше в том смысле: Почему я наблюдаю следующее удивительное (для меня) поведение в MATLAB?

давайте посмотрим на мою проблему в целом. В этом текущем тестовом сценарии я буду генерировать один выход: A-v7.3 мат-файл. Этот.mat-файл будет содержать 40 блоки как отдельные переменные. Каждая переменная будет называться "block_NNN" от 1 до 40 и будет содержать структуру с полями кадры и blockNo. Поле кадры содержит последовательность 480x240x65 uint8 imagedata (здесь просто случайные данные, генерируемые с помощью Ранди). Поле blockNo содержит номер блока.

замечание: в реальном скрипте (который я еще не закончил) я буду делать выше в общей сложности 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

теперь у меня есть все мои случайные тестовые данные в структуре 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.

OK-тогда я вспомнил, что хотел бы следовать процедуре сохранения через 40 блоков. Таким образом, вместо вышеизложенного я перебираю блоки и добавляю их последовательно:

% 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

файлы не идентичны по размеру. И тест с fc в windows 7 обнаружено ... ну много бинарных различий. Возможно, данные были немного сдвинуты - таким образом, это ничего нам не говорит.

есть ли у кого-нибудь идея, что здесь происходит? Возможно, добавленный файл использует гораздо более оптимизированную структуру данных? Или, может быть, windows кэширует файл и делает доступ к нему намного быстрее?

я сделал усилие тестового чтения из двух файлов, а также. Без представления номеров здесь добавленная версия была немного быстрее (может означать что-то в долгосрочной перспективе).

[EDIT]: я просто попытался использовать флаг формата (по умолчанию-v7 в моей системе), и больше нет большой разницы:

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

[EDIT]: я исправил вышеуказанную ошибку. Ранее я упоминал, что статистика были для-v6, но я ошибся. Я только что удалил флаг формата и предположил, что по умолчанию-v6, но на самом деле это-v7.

я создал новую тестовую статистику для всех форматов в моей системе, используя прекрасную структуру Эндрю (все форматы для тех же случайных тестовых данных, теперь читаются из файла):

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 есть какой-то базовый встроенный метод надувания, насколько я знаю.

Хм, вероятно, некоторая оптимизация в базовых функциях HDF5-write?

в настоящее время я все еще думаю, что некоторая базовая фундаментальная функция HDF5-write оптимизирована для добавления наборы в HDF5-файл (что происходит при добавлении новых переменных в файл -7.3). Я считаю, что я где-то читал, что HDF5 должен оптимизироваться именно таким образом... хотя не может быть конечно.

другие детали для Примечание:

поведение очень системное, как мы видим в ответе Андрея ниже. Также кажется довольно важным, запускаете ли вы эти вещи в локальной области функции или в" глобальном " M-скрипте. Мои первые результаты были из M-скрипта, где файлы были записаны в текущий каталог. Я все еще могу воспроизвести только 1-секундную запись для -7.3 в M-скрипте. Вызовы функций добавляют некоторые накладные расходы по-видимому.

Обновление Июль 2016:

я снова нашел это и подумал, что могу проверить его с новейшим MATLAB, доступным мне на данный момент. С MATLAB R2016a на Windows 7 x64 проблема, похоже, была исправлена:

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 функция в принятом ответе ниже (5 проходов с форматом 7.3). Теперь,-append одинаково медленно или медленнее для одного сохранения-как и должно быть. Возможно, это была проблема с ранней сборкой драйвера HDF5, используемого в R2009a.

3 ответов


Святая корова. Я могу размножаться. Попробовал вариант с одним добавлением; это еще быстрее. Похоже, что "- append " просто волшебным образом делает HDF5-based save() 30x быстрее. У меня нет объяснений, но я хотел поделиться тем, что нашел.

я завернул ваш тестовый код в функцию, рефакторинг его, чтобы сделать логику сохранения агностической о структуре тестовых данных, чтобы вы могли запускать ее на других наборах данных, и добавил еще несколько диагностических выходных данных.

не вижу большого ускорения повсюду. Он огромен на моей 64-битной коробке XP и 32-битной коробке Server 2003, большой на моей 64-битной коробке Windows 7, несуществующей на 32-битной коробке XP. (Хотя несколько добавлений являются огромной потерей на сервере 2003.) R2010b во многих случаях медленнее. Возможно, HDF5 добавляет или сохраняет его использование только на новых сборках Windows. (XP x64 на самом деле является ядром Server 2003.) Или, может быть, это просто разница в конфигурации машины. На машине XP x64 есть быстрый рейд, а 32-разрядная XP имеет меньше ОЗУ, чем остальные. Какая ОС и архитектура вы работаете? Вы можете попробовать этот репро тоже?

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

это выглядит огромным. Если он держится на других наборах данных, я мог бы использовать этот трюк во многих местах. Это может быть что-то, чтобы поднять с математикой, тоже. Могут ли они использовать технику быстрого добавления в обычных сохранениях или других версиях ОС?

вот автономная функция 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);

EDIT: я изменил функцию repro, добавив несколько итераций и параметризовав ее для сохранения стилей, форматов файлов и imax для генератора randi.

Я думаю, что кэширование файловой системы является большим фактором для быстрого добавления. Когда я делаю кучу запусков подряд с reproMatfileAppendSpeedup (20) и наблюдаю за системной информацией в Process Explorer, большинство из них находятся под вторым, а использование физической памяти быстро увеличивается на пару ГБ. Затем каждый десяток проходит, запись останавливается и занимает 20 или 30 секунд, а физическое использование ОЗУ медленно спускается примерно туда, где оно начатый. Я думаю, это означает, что Windows кэширует много записей в ОЗУ, и что-то о-append делает его более готовым сделать это. Но амортизированное время, включая эти киоски, по-прежнему намного быстрее, чем основное спасение, для меня.

кстати, после нескольких проходов в течение нескольких часов мне трудно воспроизводить исходные тайминги.


просто обновление на случай, если это полезно для других. Я нашел ошибку Matlab 784028 что показывает, что нет сжатия для -append поведение исправлено с 2012a. Из некоторых тестов в моей системе это действительно так, сжатие происходит для переменных >10000 байт с или без использования append и никогда не происходит для меньших переменных.

к сожалению, обратная сторона этого заключается в том, что нет никакого способа вообще контролировать использование сжатия с -v7.3 файлы.


эксперименты, проведенные @AndrewJanke очень интересно. Следует помнить, что три формата MAT-файлов, которые вы сравниваете, совершенно разные: v6 несжат, V7 сжат, в то время как v7.3 также сжимается, но использует совершенно другую реализацию (стандартный формат HDF5 и пользовательский формат, оптимизированный MATLAB).

как к save-all-vars-at-once и добавить-one-var-at-a-time сравнение, я также удивлен результаты...