2011-02-09 2 views
11

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

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

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

давайте посмотрим на мою проблему в целом. В текущем тестовом сценарии я буду генерировать один результат: матовый файл A -v7.3. Этот .mat-файл будет содержать 40 блоков как отдельные переменные. Каждая переменная будет называться «block_NNN» от 1 до 40 и будет содержать структуру с полями кадрами и blockNo. Поле кадров содержит 480x240x65 последовательность uint8 imagedata (здесь только случайные данные, сгенерированные с использованием 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; 

Файл записывается без проблем (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. 

Интересно, что метод append намного быстрее? Почему мой вопрос?

Выход из 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 показал ... хорошо много двоичных различий. Возможно, данные немного сдвинуты - таким образом, это ничего не говорит.

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

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

[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-запись оптимизирована для добавления наборов данных к 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.

+0

Удивительно. Я думаю, что -append будет медленнее (см. Http://stackoverflow.com/questions/4268044/deleting-variables-from-a-mat-file/4270936). Локальная или сетевая файловая система? Попробуйте просмотреть как операции сохранения в Process Explorer, так и Process Monitor, чтобы узнать, сколько операций ввода/вывода (байтов и операций в каждом из них). –

+0

Может быть, кэширование. Или, возможно, -append заставляет Matlab открывать файл для RW другим способом (например, mem-mapped), который быстрее в вашем случае. Или, может быть, запись файла размером 300 МБ в одном большом фрагменте взаимодействует с буферами или чем-то еще и делает его медленнее. В качестве другого теста, что произойдет, если вы сохраните тривиально маленькую переменную в файле, а затем добавите все свои куски в один вызов «save -append»? И насколько быстро сохраняется каждый кусок в отдельный файл? Это может помочь различать режим -append и размеры блоков. –

+0

@Andrew: Спасибо за ваши комментарии. Удивительно :) Но опять же я счастлив, что потратил время на создание цикла, который делал это быстрее. Я посмотрю, получу ли я время, чтобы сделать эти тесты. Кровать для меня, хотя :) –

ответ

8

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

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

Не вижу большого ускорения во всем мире. Он огромный на моем 64-битном поле XP и 32-битном сервере 2003, большой в моем 64-битном окне Windows 7, не существует в 32-разрядной версии XP. (Хотя несколько приложений являются огромными потерями на Server 2003.) R2010b во многих случаях медленнее. Может быть, HDF5 добавит или сохранит его использование только на новых версиях Windows. (XP x64 на самом деле является ядром Server 2003.) Или, может быть, это просто разница в конфигурации машины. На компьютере XP x64 есть быстрый RAID, а 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 

Это выглядит огромным. Если он держится на других наборах данных, я могу использовать этот трюк во многих местах. Возможно, что-то и с 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); 

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

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

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

+0

@Andrew: Спасибо за это. Это очень полезно. Я воспроизвел вывод из вашей функции в своей системе (win7 pro, 32-bit). См. Редактирование моего исходного вопроса. Примечание: я отключил генератор случайных данных - вместо этого я записываю одни и те же случайные данные из памяти для всех форматов. Может быть очень интересно услышать от кого-то, работающего в Mathworks, - возможно, кто-нибудь здесь, в stackoverflow? Возможно, человек, непосредственно ответственный за ** save ** command :) –

+0

@AndrewJanke: +1 очень интересное сравнение – Amro

+0

Обратите внимание, что 'memory()' недоступно на Mac. Я запустил ваш скрипт в системе «MACI64» (10.6) и увидел похожие результаты. Результаты: basic/multiappend/bigappend -> 12.7/3.6/3.1. –

2

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

Как скряга-Перо-на-один раз VS. конкатенирующих один-вар-в-время сравнения, я также удивлен результатами ...

+0

@Amro: OK - v6 несжатый. Пропустили эту деталь. Хорошо, тогда, если вы посмотрите на размеры файлов, о которых я упоминаю в вопросе [edit], вы можете видеть, что они вокруг 285-286 MiB. Это говорит мне, что мне нужно найти реальные данные изображения и попробовать его на этом (см. Мой комментарий по вопросу выше относительно сжатия равномерно распределенных случайных данных). –

+1

@OleThomsenBuus: чтобы проиллюстрировать эффект сжатия, замените вызов RANDI в коде @AndrewJanke: 'ones (sz, 'uint8')' (в функции 'generateTestData()'). Результат (с точки зрения размера файла) заключается в том, что v6 не затронут, v7 является самым маленьким, за ним следует HDF5-формат v7.3, поскольку он имеет большие накладные расходы на хранение (см. Этот вопрос: http://stackoverflow.com/ вопросы/4950630/matlab-difference-between-mat-versions/4959325 # 4959325) – Amro

+1

@OleThomsenBuus: На самом деле, если вы попробуете модификацию, предложенную мной для «append-cases», мы можем заметить, что для формата v7.3, сжатие происходит для каждой переменной само по себе (поскольку оно не обнаруживало повторений по переменным), в то время как в случае v7 файл значительно меньше благодаря сжатию всего его содержимого при записи ... – Amro

3

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

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