web-dev-qa-db-fra.com

La lecture de fichiers Matlab la plus rapide?

Mon programme MATLAB lit un fichier d'environ 7 m de lignes et perd trop de temps sur les E/S. Je sais que chaque ligne est formatée en deux entiers, mais je ne sais pas exactement combien de caractères ils prennent. str2num est extrêmement lent, quelle fonction matlab dois-je utiliser à la place?

Catch: Je dois opérer sur chaque ligne une à la fois sans stocker toute la mémoire du fichier, donc aucune des commandes qui lisent des matrices entières n'est sur la table.

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

Énoncé du problème

C'est une lutte commune, et il n'y a rien de tel qu'un test pour répondre. Voici mes hypothèses:

  1. Un fichier ASCII bien formaté, contenant deux colonnes de chiffres. Aucun en-tête, aucune ligne incohérente, etc.

  2. La méthode doit évoluer pour lire des fichiers trop volumineux pour être contenus en mémoire (bien que ma patience soit limitée, mon fichier de test ne fait que 500 000 lignes).

  3. L'opération réelle (ce que l'OP appelle "faire des trucs avec des nombres") doit être effectuée une ligne à la fois, ne peut pas être vectorisée.

Discussion

Dans cet esprit, les réponses et les commentaires semblent encourager l'efficacité dans trois domaines:

  • lecture du fichier par lots plus importants
  • effectuer la conversion de chaîne en nombre plus efficacement (via le traitement par lots ou en utilisant de meilleures fonctions)
  • rendre le traitement réel plus efficace (ce que j'ai exclu via la règle 3 ci-dessus).

Résultats

J'ai mis en place un script rapide pour tester la vitesse d'ingestion (et la cohérence du résultat) de 6 variations sur ces thèmes. Les résultats sont:

  • Code initial. 68,23 s. 582582 chèque
  • En utilisant sscanf, une fois par ligne. 27.20 seconde. 582582 chèque
  • Utilisation de fscanf en grands lots. 8,93 s. 582582 chèque
  • Utilisation de TextScan en grands lots. 8.79 sec. 582582 chèque
  • Lecture de grands lots en mémoire, puis sscanf. 8.15 seconde. 582582 chèque
  • Utilisation de Java lecteur de fichiers sur une seule ligne et sscanf sur des lignes simples. 63,56 seconde. 582582 chèque
  • Utilisation de Java scanner de jetons d'élément unique. 81,19 seconde. 582582 chèque
  • Opérations entièrement par lots (non conformes). 1.02 seconde. 508680 chèque (viole la règle 3)

Sommaire

Plus de la moitié du temps d'origine (68 -> 27 sec) a été consommée avec des inefficacités dans l'appel str2num, qui peuvent être supprimées en commutant le sscanf.

Environ 2/3 du temps restant (27 -> 8 sec) peut être réduit en utilisant des lots plus importants pour la lecture de fichiers et les conversions de chaîne en nombre.

Si nous sommes prêts à violer la règle numéro trois dans le message d'origine, 7/8 du temps supplémentaires peuvent être réduits en passant à un traitement entièrement numérique. Cependant, certains algorithmes ne s'y prêtent pas, alors nous laissons cela de côté. (La valeur "vérifier" ne correspond pas à la dernière entrée.)

Enfin, en contradiction directe avec une édition précédente de la mienne dans cette réponse, aucune économie n'est disponible en commutant les lecteurs monolignes Java mis en cache disponibles. En fait, cette solution est 2 à 3 fois plus lente que le résultat d'une seule ligne comparable utilisant des lecteurs natifs. (63 contre 27 secondes).

Un exemple de code pour toutes les solutions décrites ci-dessus est inclus ci-dessous.


Exemple de code

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

(réponse originale)

Pour développer l'argument avancé par Ben ... votre goulot d'étranglement sera toujours l'E/S de fichier si vous lisez ces fichiers ligne par ligne.

Je comprends que parfois vous ne pouvez pas mettre un fichier entier en mémoire. Je lis généralement dans un grand lot de caractères (1e5, 1e6 ou environ, en fonction de la mémoire de votre système). Ensuite, je lis des caractères simples supplémentaires (ou je recule des caractères simples) pour obtenir un nombre rond de lignes, puis j'exécute l'analyse de chaîne (par exemple sscanf).

Ensuite, si vous le souhaitez, vous pouvez traiter la grande matrice résultante une ligne à la fois, avant de répéter le processus jusqu'à ce que vous lisiez la fin du fichier.

C'est un peu fastidieux, mais pas si difficile. Je constate généralement une amélioration de 90% et de la vitesse par rapport aux lecteurs à ligne unique.


(idée terrible en utilisant Java ont été retirés de honte)

61
Pursuit

Même si vous ne pouvez pas tenir le fichier entier en mémoire, vous devez lire un grand lot en utilisant les fonctions de lecture de la matrice.

Vous pouvez peut-être même utiliser des opérations vectorielles pour certains traitements de données, ce qui accélérerait encore les choses.

3
Ben Voigt

J'ai eu de bons résultats (speedwise) en utilisant memmapfile(). Cela minimise la quantité de copie de données de la mémoire et utilise la mise en mémoire tampon du noyau IO. Vous avez besoin d'assez d'espace d'adressage libre (mais pas de mémoire libre réelle) pour mapper le fichier entier, et assez de mémoire libre pour contenir la variable de sortie (évidemment!)

L'exemple de code ci-dessous lit un fichier texte dans une matrice à deux colonnes data de type int32.

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')

Vous devrez peut-être jouer avec les paramètres de textscan() pour obtenir exactement ce que vous voulez - voir la documentation en ligne.

3
Max