MATLAB: Сохранение нескольких переменных в «-v7.3» (HDF5) .mat-файлах, кажется, быстрее при использовании флага «-append». Как так? - PullRequest
11 голосов
/ 09 февраля 2011

ПРИМЕЧАНИЕ: Этот вопрос касается проблемы, замеченной еще в 2011 году со старой версией MATLAB (R2009a). Согласно приведенному ниже обновлению от июля 2016 года, проблема / ошибка в MATLAB, по-видимому, больше не существует (протестировано с R2016a; прокрутите вниз до конца вопроса, чтобы увидеть обновление).

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

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

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

Замечание: В реальном скрипте (который я еще не закончил) я буду выполнять вышеизложенное в общей сложности 370 раз, преобразуя в общей сложности 108 ГБ необработанных данных. Вот почему меня волнует следующее.

Во всяком случае, сначала я определю некоторые общие переменные:

% 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;

Файл записан без проблем (286МБ). Выход:

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

ОК - тогда я вспомнил, что хотел бы выполнить процедуру сохранения в течение 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;

И снова файл прекрасно сохраняется (286 МБ). Выход:

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?

В настоящее время я все еще думаю, что некоторая базовая фундаментальная функция записи HDF5 оптимизирована для добавления наборов данных в файл 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 ]

8 голосов
/ 10 февраля 2011

Святая корова. Я могу воспроизвести. Пробовал вариант с одним дополнением тоже; это даже быстрее. Похоже, "-append" просто волшебным образом делает функцию save () на основе HDF5 в 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 имеет меньше оперативной памяти, чем остальные. Какую ОС и архитектуру вы используете? Можете ли вы попробовать это repro тоже?

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, добавив несколько итераций и параметризовав ее для сохранения стилей, форматов файлов и imax для генератора randi.

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

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

3 голосов
/ 24 октября 2012

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

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

2 голосов
/ 10 февраля 2011

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

Что касается сравнения save-all-vars-at-Once против append-one-var-at-time , я тожеудивлен результатами ...

...