11

注: この質問は、2011 年に古い MATLAB バージョン (R2009a) で観察された問題を扱っています。以下の 2016 年 7 月からの更新によると、MATLAB の問題/バグはもはや存在しないようです (R2016a でテスト済み。質問の最後までスクロールして更新を確認してください)。

私は MATLAB R2009b を使用しており、.zip ファイルのより大きなセットの内容を v7.3 mat ファイル (基礎となる HDF5 データモデルを使用) に変換する、より大きなスクリプトを作成する必要があります。読み取りOKです。問題は保存にあります。そして、実際には問題はありません。私のファイルは、 saveコマンドを使用してうまく保存されます。

私の質問はもっと意味があります: MATLAB で次の驚くべき (私にとって) 動作が見られるのはなぜですか?

一般的に私の問題を見てみましょう。この現在のテスト シナリオでは、1 つの出力 (-v7.3 mat ファイル) を生成します。この .mat ファイルには、個別の変数として40 個のブロックが含まれます。各変数には、1 から 40 までの「block_NNN」という名前が付けられ、フィールドframesおよびblockNoを持つ構造体が含まれます。フィールドフレームには、uint8 画像データの 480x240x65 シーケンスが含まれます (ここでは、 randi を使用して生成されたランダム データのみ)。フィールドblockNoにはブロック番号が含まれます。

注:実際のスクリプト (まだ完成していません) では、上記を合計 370 回実行し、合計 108 GB の生データを変換します。ということで、以下が気になります。

とにかく、最初にいくつかの一般変数を定義します。

% ダミー データとループのサイズ:
num_blockCount = 40;
num_blockLength = 65;
num_frameHeight = 480;
num_frameWidth = 240;

次に、実際の生データと同じ形状とサイズを持つダミー コードを生成します。

% 空の構造体を生成:
stu_data2disk = struct();

% ブロックのループ:
num_k = 1:num_blockCount の場合

   % ブロック名を生成:
   temp_str_blockName = sprintf('block_%03u', num_k);

   % 現在のブロックの一時構造体を生成:
   temp_stu_value = struct();
   temp_stu_value.frames = randi( ...
      [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に含まれるようになりました。ここで、2 つの可能な方法のいずれかを使用してデータを保存したいと思います。

最初に簡単なものを試してみましょう:

% セーブ データ (シンプル):
disp('データを簡単な方法で保存:')
チック;
Converted.mat -struct stu_data2disk -v7.3; を保存します。
目次;

ファイルは問題なく書き込まれます (286MB)。出力は次のとおりです。

簡単な方法でデータを保存します。
経過時間は 14.004449 秒です。

OK - それから、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 = '-append';
   終わり

   % 生成保存コマンド:
   temp_str_saveCommand = [ ...
      '保存 '、 ...
      「converted_append.mat」、...
      '-struct stu_data2disk', temp_str_blockName, ' '...
      temp_str_appendToggle、' '、...
      「-v7.3」、...
      ';' ...
   ];

   % 評価保存コマンド:
   eval(temp_str_saveCommand);

終わり
目次;

また、ファイルはうまく保存されます (286MB)。出力は次のとおりです。

-append を使用してデータを保存します。
経過時間は 0.956968 秒です。

興味深いことに、append-method ははるかに高速ですか? 私の質問はなぜですか?

からの出力dir converted*.mat:

09-02-2011 20:38 300,236,392 変換された.mat
2011 年 9 月 2 日 20:37 300,264,316 変換された_append.mat
               2 ファイル 600,500,708 バイト

ファイルのサイズが同じではありません。そして、Windows 7 でfcを使用したテストでは、多くのバイナリの違いが明らかになりました。おそらく、データが少しシフトされたので、何もわかりません。

誰かがここで何が起こっているのか知っていますか? 追加されたファイルは、おそらくより最適化されたデータ構造を使用していますか? それとも、Windows がファイルをキャッシュして、ファイルへのアクセスを大幅に高速化したのでしょうか。

2つのファイルからも試し読みをしてみました。ここに数値を提示しなければ、追加されたバージョンは少し高速でした (ただし、長期的には何かを意味する可能性があります)。

[編集] : フォーマット フラグを使用せずに試してみたところ (私のシステムではデフォルトで -v7 になっています)、あまり違いはありません:

簡単な方法でデータを保存します (-v7):
経過時間は 13.092084 秒です。
-append (-v7) を使用してデータを保存します。
経過時間は 14.345314 秒です。

[編集] : 上記の間違いを修正しました。以前、統計は -v6 のものであると述べましたが、私は間違っていました。フォーマット フラグを削除したばかりで、デフォルトが -v6 であると想定していましたが、実際には -v7 です。

Andrew の優れたフレームワークを使用して、システム上のすべての形式の新しいテスト統計を作成しました (すべての形式は、ファイルから読み取られた同じランダム テスト データ用です)。

15:15:51.422: テスト速度、フォーマット=-v6、PCWIN の R2009b、アーチ=x86、os=Microsoft Windows 7 Professional 6.1.7600 N/A ビルド 7600
15:16:00.829: 簡単な方法で保存: 0.358 秒
15:16:01.188: 複数の追加を使用して保存: 7.432 秒
15:16:08.614: 1 つの大きな追加を使用して保存: 1.161 秒

15:16:24.659: テスト速度、フォーマット=-v7、PCWIN の R2009b、アーチ=x86、os=Microsoft Windows 7 Professional 6.1.7600 N/A ビルド 7600
15:16:33.442: シンプルな方法で保存: 12.884 秒
15:16:46.329: 複数の追加を使用して保存: 14.442 秒
15:17:00.775: 1 つの大きな追加を使用して保存: 13.390 秒

15:17:31.579: テスト速度、フォーマット=-v7.3、PCWIN の R2009b、アーチ=x86、os=Microsoft Windows 7 Professional 6.1.7600 N/A ビルド 7600
15:17:40.690: シンプルな方法で保存: 13.751 秒
15:17:54.434: 複数の追加を使用して保存: 3.970 秒
15:17:58.412: 1 つの大きな追加を使用して保存: 6.138 秒

そして、ファイルのサイズ:

10-02-2011 15:16 299,528,768 変換された_format-v6.mat
10-02-2011 15:16 299,528,768 変換された_append_format-v6.mat
10-02-2011 15:16 299,528,832 変換された_append_batch_format-v6.mat
10-02-2011 15:16 299,894,027 変換された_format-v7.mat
10-02-2011 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 変換された_format-v7.3.mat
10-02-2011 15:17 300,264,316 変換された_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 にはいくつかの基本的な inflate メソッドが組み込まれています。

うーん、おそらく基礎となる HDF5 書き込み関数の最適化ですか?

現在、基礎となるいくつかの基本的な HDF5 書き込み関数は、データセットを HDF5 ファイルに追加するために最適化されていると思います (これは、新しい変数を -7.3 ファイルに追加するときに発生します)。HDF5がまさにこの方法で最適化されるべきであるとどこかで読んだと思います...確かではありませんが。

その他の注意事項:

以下のアンドリューの回答に見られるように、動作は非常に体系的です。また、これらを関数のローカル スコープで実行するか、m-script の「グローバル」で実行するかについても非常に重要なようです。私の最初の結果は、ファイルが現在のディレクトリに書き込まれた 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 バージョン 6.1 (ビルド 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 秒 5.868 GB 使用 286 MB ファイル 平均 8.099 秒
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 秒 5.871 GB 使用 286 MB ファイル 平均 7.746 秒

これは、以下の受け入れられた回答の Andrew Janke のreproMatfileAppendSpeedup関数でテストされました (形式 7.3 で 5 パス)。現在-appendは、1 回の保存が同じように、または遅くなりました。おそらく、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 は他のマシンよりも少なくなっています。実行している OS とアーキテクチャは何ですか? このリプロも試してみませんか?

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 にも持ち込むべきものかもしれません。通常の保存や他の OS バージョンでも高速追加技術を使用できますか?

これが自己完結型の 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);

編集: repro 関数を変更し、複数の反復を追加して、保存スタイル、ファイル形式、および randi ジェネレーターの imax 用にパラメーター化しました。

ファイルシステムのキャッシュは、高速追加動作の大きな要因だと思います。reproMatfileAppendSpeedup(20) を使用して一連の実行を連続して実行し、Process Explorer でシステム情報を確認すると、そのほとんどは 1 秒未満であり、物理メモリの使用量は数 GB ずつ急速に増加します。その後、数十回のパスごとに書き込みが停止し、20 秒または 30 秒かかり、物理 RAM の使用量は開始時のあたりまでゆっくりと減少します。これは、Windows が RAM に多くの書き込みをキャッシュしていることを意味していると思います。しかし、私にとっては、これらの失速を含む償却時間は、基本的な保存よりもはるかに高速です.

ところで、数時間にわたって複数のパスを実行した後、元のタイミングを再現するのに苦労しています。

于 2011-02-10T01:22:47.300 に答える
3

他の人に役立つ場合に備えての更新です。私は Matlab バグ784028を見つけました。これは、動作の圧縮なし-appendが 2012a から修正されたことを示しています。私のシステムでのいくつかのテストから、これは実際に当てはまります.appendの使用の有無にかかわらず、10000バイトを超える変数に対して圧縮が行われ、より小さい変数に対しては決して行われません。

残念ながら、これの裏側は、-v7.3ファイルでの圧縮の使用を制御する方法がまったくないように見えることです。

于 2012-10-24T14:28:19.530 に答える
2

@AndrewJankeが行った実験は非常に興味深いものです。覚えておくべきことの 1 つは、比較する 3 つの MAT ファイル形式がまったく異なることです。v6 は非圧縮、v7 は圧縮されていますが、v7.3 も圧縮されていますが、まったく異なる実装を使用しています (HDF5 標準形式とカスタム MATLAB 最適化形式)。 )。

すべての変数を一度に保存する場合と、一度に 1 つの変数を追加する場合の比較については、結果にも驚かされます...

于 2011-02-10T16:33:30.370 に答える