web-dev-qa-db-ja.com

最速のMatlabファイル読み込み?

私のMATLABプログラムは、長さ約7mのファイルを読み込んでおり、I/Oに多くの時間を浪費しています。各行が2つの整数としてフォーマットされていることは知っていますが、それらが占める文字数を正確には知りません。 str2numは非常に遅いですが、代わりにどのmatlab関数を使用する必要がありますか?

キャッチ:ファイルメモリ全体を保存せずに、各行を一度に1つずつ操作する必要があるため、マトリックス全体を読み取るコマンドはテーブルにありません。

fid = fopen('file.txt');
tline = fgetl(fid);
while ischar(tline)
    nums = str2num(tline);    
    %do stuff with nums
    tline = fgetl(fid);
end
fclose(fid);
39
user714403

問題文

これはよくある闘争であり、答えるテストのようなものはありません。私の仮定は次のとおりです。

  1. 適切にフォーマットされたASCIIファイル、2列の数値を含む。ヘッダーなし、矛盾した行なしなど).

  2. メソッドは、メモリに格納するには大きすぎるファイルの読み取りに合わせて拡張する必要があります(ただし、忍耐が制限されているため、テストファイルは500,000行のみです)。

  3. 実際の操作(OPが「numを使用して行うこと」と呼ぶもの)は一度に1行ずつ実行する必要があり、ベクトル化することはできません。

討論

それを念頭に置いて、回答とコメントは次の3つの領域で効率を促進しているようです。

  • より大きなバッチでファイルを読み取る
  • 文字列から数値への変換をより効率的に実行します(バッチ処理またはより優れた機能を使用して)
  • 実際の処理をより効率的にします(上記のルール3で除外しました)。

結果

これらのテーマの6つのバリエーションの取り込み速度(および結果の一貫性)をテストする簡単なスクリプトをまとめました。結果は次のとおりです。

  • 初期コード。 68.23秒。 582582チェック
  • Sscanfを使用して、1行に1回。 27.20 秒582582チェック
  • 大きなバッチでfscanfを使用します。 8.93秒。 582582チェック
  • 大規模なバッチでtextscanを使用します。 8.79秒。 582582チェック
  • 大きなバッチをメモリに読み込んでから、sscanf。 8.15 秒582582チェック
  • Java単一行ファイルリーダーと単一行でのsscanfの使用。 63.56 秒582582チェック
  • Java単一アイテムトークンスキャナーを使用します。 81.19 秒582582チェック
  • 完全にバッチ化された操作(非準拠)。 1.02 秒508680チェック (ルール3に違反)

概要

元の時間の半分以上(68-> 27秒)がstr2num呼び出しで非効率的に消費されましたが、sscanfを切り替えることで削除できます。

残り時間の約2/3(27-> 8秒)は、ファイルの読み取りと文字列から数値への変換の両方に大きなバッチを使用することで削減できます。

元の投稿のルール番号3に違反する場合は、完全に数値処理に切り替えることで、さらに7/8の時間を短縮できます。ただし、一部のアルゴリズムはこれに適していないため、そのままにします。 (「チェック」値が最後のエントリと一致しません。)

最後に、このレスポンス内での私の以前の編集との直接的な矛盾において、利用可能なキャッシュされたJavaの単一行リーダーを切り替えることによる節約はありません。実際、ソリューションは、ネイティブリーダーを使用した同等の単一行の結果よりも2〜3倍遅くなります。 (63対27秒)。

上記のすべてのソリューションのサンプルコードを以下に示します。


サンプルコード

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Create a test file
cd(tempdir);
fName = 'demo_file.txt';
fid = fopen(fName,'w');
for ixLoop = 1:5
    d = randi(1e6, 1e5,2);
    fprintf(fid, '%d, %d \n',d);
end
fclose(fid);


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Initial code
CHECK = 0;
tic;
fid = fopen('demo_file.txt');
tline = fgetl(fid);
while ischar(tline)
    nums = str2num(tline);
    CHECK = round((CHECK + mean(nums) ) /2);
    tline = fgetl(fid);
end
fclose(fid);
t = toc;
fprintf(1,'Initial code.  %3.2f sec.  %d check \n', t, CHECK);


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Using sscanf, once per line
CHECK = 0;
tic;
fid = fopen('demo_file.txt');
tline = fgetl(fid);
while ischar(tline)
    nums = sscanf(tline,'%d, %d');
    CHECK = round((CHECK + mean(nums) ) /2);
    tline = fgetl(fid);
end
fclose(fid);
t = toc;
fprintf(1,'Using sscanf, once per line.  %3.2f sec.  %d check \n', t, CHECK);


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Using fscanf in large batches
CHECK = 0;
tic;
bufferSize = 1e4;
fid = fopen('demo_file.txt');
scannedData = reshape(fscanf(fid, '%d, %d', bufferSize),2,[])' ;
while ~isempty(scannedData)
    for ix = 1:size(scannedData,1)
        nums = scannedData(ix,:);
        CHECK = round((CHECK + mean(nums) ) /2);
    end
    scannedData = reshape(fscanf(fid, '%d, %d', bufferSize),2,[])' ;
end
fclose(fid);
t = toc;
fprintf(1,'Using fscanf in large batches.  %3.2f sec.  %d check \n', t, CHECK);


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Using textscan in large batches
CHECK = 0;
tic;
bufferSize = 1e4;
fid = fopen('demo_file.txt');
scannedData = textscan(fid, '%d, %d \n', bufferSize) ;
while ~isempty(scannedData{1})
    for ix = 1:size(scannedData{1},1)
        nums = [scannedData{1}(ix) scannedData{2}(ix)];
        CHECK = round((CHECK + mean(nums) ) /2);
    end
    scannedData = textscan(fid, '%d, %d \n', bufferSize) ;
end
fclose(fid);
t = toc;
fprintf(1,'Using textscan in large batches.  %3.2f sec.  %d check \n', t, CHECK);



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Reading in large batches into memory, incrementing to end-of-line, sscanf
CHECK = 0;
tic;
fid = fopen('demo_file.txt');
bufferSize = 1e4;
eol = sprintf('\n');

dataBatch = fread(fid,bufferSize,'uint8=>char')';
dataIncrement = fread(fid,1,'uint8=>char');
while ~isempty(dataIncrement) && (dataIncrement(end) ~= eol) && ~feof(fid)
    dataIncrement(end+1) = fread(fid,1,'uint8=>char');  %This can be slightly optimized
end
data = [dataBatch dataIncrement];

while ~isempty(data)
    scannedData = reshape(sscanf(data,'%d, %d'),2,[])';
    for ix = 1:size(scannedData,1)
        nums = scannedData(ix,:);
        CHECK = round((CHECK + mean(nums) ) /2);
    end

    dataBatch = fread(fid,bufferSize,'uint8=>char')';
    dataIncrement = fread(fid,1,'uint8=>char');
    while ~isempty(dataIncrement) && (dataIncrement(end) ~= eol) && ~feof(fid)
        dataIncrement(end+1) = fread(fid,1,'uint8=>char');%This can be slightly optimized
    end
    data = [dataBatch dataIncrement];
end
fclose(fid);
t = toc;
fprintf(1,'Reading large batches into memory, then sscanf.  %3.2f sec.  %d check \n', t, CHECK);


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Using Java single line readers + sscanf
CHECK = 0;
tic;
bufferSize = 1e4;
reader =  Java.io.LineNumberReader(Java.io.FileReader('demo_file.txt'),bufferSize );
tline = char(reader.readLine());
while ~isempty(tline)
    nums = sscanf(tline,'%d, %d');
    CHECK = round((CHECK + mean(nums) ) /2);
    tline = char(reader.readLine());
end
reader.close();
t = toc;
fprintf(1,'Using Java single line file reader and sscanf on single lines.  %3.2f sec.  %d check \n', t, CHECK);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Using Java scanner for file reading and string conversion
CHECK = 0;
tic;
jFile = Java.io.File('demo_file.txt');
scanner = Java.util.Scanner(jFile);
scanner.useDelimiter('[\s\,\n\r]+');
while scanner.hasNextInt()
    nums = [scanner.nextInt() scanner.nextInt()];
    CHECK = round((CHECK + mean(nums) ) /2);
end
scanner.close();
t = toc;
fprintf(1,'Using Java single item token scanner.  %3.2f sec.  %d check \n', t, CHECK);


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Reading in large batches into memory, vectorized operations (non-compliant solution)
CHECK = 0;
tic;
fid = fopen('demo_file.txt');
bufferSize = 1e4;
eol = sprintf('\n');

dataBatch = fread(fid,bufferSize,'uint8=>char')';
dataIncrement = fread(fid,1,'uint8=>char');
while ~isempty(dataIncrement) && (dataIncrement(end) ~= eol) && ~feof(fid)
    dataIncrement(end+1) = fread(fid,1,'uint8=>char');  %This can be slightly optimized
end
data = [dataBatch dataIncrement];

while ~isempty(data)
    scannedData = reshape(sscanf(data,'%d, %d'),2,[])';
    CHECK = round((CHECK + mean(scannedData(:)) ) /2);

    dataBatch = fread(fid,bufferSize,'uint8=>char')';
    dataIncrement = fread(fid,1,'uint8=>char');
    while ~isempty(dataIncrement) && (dataIncrement(end) ~= eol) && ~feof(fid)
        dataIncrement(end+1) = fread(fid,1,'uint8=>char');%This can be slightly optimized
    end
    data = [dataBatch dataIncrement];
end
fclose(fid);
t = toc;
fprintf(1,'Fully batched operations.  %3.2f sec.  %d check \n', t, CHECK);

(元の答え)

これらのファイルを1行ずつ読み込んでいる場合、Benの主張を拡張するために、ボトルネックは常にファイルI/Oになります。

ファイル全体をメモリに収めることができない場合があることを理解しています。私は通常、大量の文字を読み取ります(システムのメモリに応じて、1e5、1e6またはその周辺)。次に、追加の単一文字を読み取って(または単一文字を取り消して)丸い行数を取得し、文字列解析(たとえば、sscanf)を実行します。

次に、必要に応じて、結果の大きなマトリックスを一度に1行ずつ処理してから、ファイルの最後まで読み取る処理を繰り返します。

少し面倒ですが、それほど難しくはありません。通常、90%に加えて、単一行リーダーよりも速度が向上します。


(Javaバッチラインリーダーを使用したひどいアイデアは残念ながら削除されました))

61
Pursuit

ファイル全体をメモリに収めることができない場合でも、行列読み取り関数を使用して大きなバッチを読み取る必要があります。

データ処理の一部にベクトル演算を使用することもできます。これにより、処理がさらに高速化されます。

3
Ben Voigt

memmapfile()を使用して(速度的に)良い結果が得られました。これにより、メモリデータのコピー量が最小限に抑えられ、カーネルのIO=バッファリングが使用されます。ファイル全体をマップするには十分な空きアドレススペース(実​​際の空きメモリではありません)と十分な空きメモリが必要です。出力変数を保持する(明らかに!)

以下のサンプルコードは、テキストファイルをint32型の2列のマトリックスdataに読み取ります。

_fname = 'file.txt';
fstats = dir(fname);
% Map the file as one long character string
m = memmapfile(fname, 'Format', {'uint8' [ 1 fstats.bytes] 'asUint8'});
textdata = char(m.Data(1).asUint8);
% Use textscan() to parse the string and convert to an int32 matrix
data = textscan(textdata, '%d %d', 'CollectOutput', 1);
data = data{:};
% Tidy up!
clear('m')
_

必要なものを正確に取得するには、textscan()のパラメーターをいじる必要がある場合があります。オンラインドキュメントを参照してください。

3
Max

MATLABはcsvファイルをテキストファイルよりもはるかに高速で読み取るため、他のソフトウェアを使用してテキストファイルをcsvに変換できる場合、Matlabの操作が大幅に高速化される可能性があります。

1
prototoast