Самое быстрое чтение файла Matlab? - PullRequest
39 голосов
/ 25 февраля 2012

Моя программа MATLAB читает файл длиной около 7 м и тратит слишком много времени на ввод-вывод.Я знаю, что каждая строка отформатирована как два целых числа, но я не знаю точно, сколько символов они занимают.str2num смертельно медленный, какую функцию Matlab я должен использовать вместо этого?

Catch: мне приходится работать с каждой строкой по одному без сохранения всей файловой памяти, поэтому ни одна из команд, которые читают целые матрицы, не являетсяна столе.

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

Ответы [ 4 ]

60 голосов
/ 25 февраля 2012

Постановка задачи

Это обычная борьба, и нет ничего лучше теста, чтобы ответить. Вот мои предположения:

  1. Хорошо отформатированный файл ASCII, содержащий два столбца чисел. Без заголовков, непоследовательных строк и т. Д.

  2. Метод должен масштабироваться для чтения файлов, которые слишком велики для того, чтобы помещаться в память (хотя мое терпение ограничено, поэтому мой тестовый файл занимает всего 500 000 строк).

  3. Фактическая операция (которую ОП называет «делать вещи с числами») должна выполняться по одной строке за раз, не может быть векторизована.

Обсуждение

Имея это в виду, ответы и комментарии, кажется, обнадеживают эффективность в трех областях:

  • чтение файла большими партиями
  • более эффективное выполнение преобразования строки в число (с помощью пакетной обработки или с использованием более качественных функций)
  • делает фактическую обработку более эффективной (что я исключил из правила 3 ​​выше).

Результаты

Я собрал быстрый скрипт для проверки скорости приема (и согласованности результата) 6 вариаций на эти темы. Результаты:

  • Исходный код. 68,23 с . 582582 чек
  • Использование sscanf, один раз в строке. 27,20 сек. 582582 чек
  • Использование fscanf в больших партиях. 8,93 с . 582582 чек
  • Использование текстового сканирования большими партиями. 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 секунд) можно уменьшить, используя более крупные пакеты как для чтения файлов, так и для преобразования строк в числа.

Если мы хотим нарушить правило номер три в исходном посте, еще 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);

(оригинальный ответ)

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

Я понимаю, что иногда вы не можете поместить целый файл в память. Обычно я читаю большой набор символов (1e5, 1e6 или около того, в зависимости от памяти вашей системы). Затем я либо читаю дополнительные одиночные символы (или отступаю от одиночных символов), чтобы получить округленное число строк, а затем запускаю анализ вашей строки (например, sscanf).

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

Это немного утомительно, но не так сложно. Обычно я вижу улучшение скорости на 90% по сравнению с однострочными считывателями.


(ужасная идея с использованием считывателей с прерывистой линией Java, удаленных от стыда)

3 голосов
/ 27 февраля 2012

У меня были хорошие результаты (по скорости) с использованием memmapfile().Это минимизирует объем копирования данных в памяти и использует буферизацию ввода-вывода ядра.Вам нужно достаточно свободного адресного пространства (но не фактической свободной памяти) для отображения всего файла и достаточно свободной памяти для хранения выходной переменной (очевидно!)

В приведенном ниже примере кода текстовый файл считывается в двухсторонний файл.матрица столбцов data типа 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')

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

3 голосов
/ 25 февраля 2012

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

Возможно, вы даже можете использовать векторные операции для обработки некоторых данных, что ускорит процесс.

1 голос
/ 25 февраля 2012

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...