Mais rápida leitura de arquivos Matlab?

Meu programa MATLAB está lendo um arquivo com cerca de 7m de comprimento e perdendo muito tempo com E / S. Eu sei que cada linha é formatada como dois inteiros, mas não sei exatamente quantos caracteres eles ocupam. str2num é mortalmente lento, qual function matlab devo usar em vez disso?

Catch: Eu tenho que operar em cada linha, uma de cada vez, sem armazenar toda a memory do arquivo, então nenhum dos comandos que leem matrizes inteiras está na tabela.

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

Declaração do problema

Esta é uma luta comum, e não há nada como um teste para responder. Aqui estão minhas suposições:

  1. Um arquivo ASCII bem formatado, contendo duas colunas de números. Sem headers, sem linhas inconsistentes, etc.

  2. O método deve ser dimensionado para a leitura de arquivos muito grandes para serem contidos na memory (embora minha paciência seja limitada, portanto, meu arquivo de teste é de apenas 500.000 linhas).

  3. A operação real (o que o OP chama de “fazer coisas com nums”) deve ser executada uma linha por vez, não pode ser vetorizada.

Discussão

Com isso em mente, as respostas e comentários parecem encorajar a eficiência em três áreas:

  • lendo o arquivo em lotes maiores
  • executar a string para numerar a conversão de forma mais eficiente (seja por meio de lotes ou usando funções melhores)
  • tornando o processamento real mais eficiente (o que excluí pela regra 3, acima).

Resultados

Eu montei um script rápido para testar a velocidade de ingestão (e consistência de resultado) de 6 variações nesses temas. Os resultados são:

  • Código inicial. 68,23 seg . 582582 cheque
  • Usando sscanf, uma vez por linha. 27,20 seg. 582582 cheque
  • Usando o fscanf em grandes lotes. 8,93 seg . 582582 cheque
  • Usando textoscan em grandes lotes. 8,79 seg . 582582 cheque
  • Lendo grandes lotes na memory, então sscanf. 8,15 seg. 582582 cheque
  • Usando o leitor de arquivos de linha única java e sscanf em linhas simples. 63,56 seg. 582582 cheque
  • Usando o scanner de token de item único do java. 81,19 seg. 582582 cheque
  • Operações totalmente em lote (não compatíveis). 1,02 seg. 508680 cheque (viola a regra 3)

Resumo

Mais da metade do tempo original (68 -> 27 seg) foi consumido com ineficiências na chamada str2num, que pode ser removida trocando o sscanf.

Cerca de 2/3 do tempo restante (27 -> 8 seg.) Pode ser reduzido com o uso de lotes maiores para leitura de arquivos e conversão de string para número.

Se estivermos dispostos a violar a regra número três no post original, outros 7/8 do tempo podem ser reduzidos mudando para um processamento totalmente numérico. No entanto, alguns algoritmos não se prestam a isso, então deixamos isso de lado. (Não o valor “check” não corresponde à última input.)

Finalmente, em contradição direta com uma edição anterior minha dentro dessa resposta, nenhuma economia está disponível trocando os leitores de linha única Java armazenados em cache. Na verdade, essa solução é 2 a 3 vezes mais lenta do que o resultado de linha única comparável usando leitores nativos. (63 vs. 27 segundos).

O código de amostra para todas as soluções descritas acima está incluído abaixo.


Código de amostra

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

(resposta original)

Para expandir o ponto feito por Ben … seu gargalo sempre será E / S de arquivo se você estiver lendo esses arquivos linha por linha.

Eu entendo que às vezes você não pode colocar um arquivo inteiro na memory. Eu normalmente leio em um grande lote de caracteres (1e5, 1e6 ou por aí, dependendo da memory do seu sistema). Então eu leio caracteres únicos adicionais (ou retrocedo caracteres únicos) para obter um número redondo de linhas, e então executo sua análise de string (por exemplo, sscanf).

Então, se você quiser, pode processar a matriz grande resultante de uma linha de cada vez, antes de repetir o processo até ler o final do arquivo.

É um pouco tedioso, mas não tão difícil. Eu normalmente vejo 90% mais melhoria na velocidade em relação aos leitores de linha única.


(idéia terrível usando leitores de linha em lote de Java removidos na vergonha)

Mesmo que você não consiga ajustar todo o arquivo na memory, você deve ler um lote grande usando as funções de leitura de matriz.

Talvez você possa até usar operações vetoriais para algum processamento de dados, o que aceleraria ainda mais as coisas.

Eu tive bons resultados (speedwise) usando memmapfile() . Isso minimiza a quantidade de cópia de dados da memory e faz uso do buffer de E / S do kernel. Você precisa de espaço de endereçamento livre suficiente (embora não seja memory livre real) para mapear o arquivo inteiro, e memory livre suficiente para manter a variável de saída (obviamente!)

O código de exemplo abaixo lê um arquivo de texto em uma matriz de data de duas colunas do tipo 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') 

Você pode precisar mexer com os parâmetros para textscan() para obter exatamente o que deseja – veja os documentos on-line.

Descobri que o MATLAB lê arquivos csv significativamente mais rápido do que arquivos de texto, portanto, se for possível converter seu arquivo de texto para csv usando algum outro software, isso pode acelerar significativamente as operações do Matlab.