Самое быстрое чтение файла Matlab?

36

Моя программа 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);
  • 3
    Откуда вы знаете, что узким местом является именно I / O? Я склонен думать, что узким местом является более вероятная операция, которую вы делаете над числами. Если бы вы могли векторизовать эту операцию, обрабатывая данные порциями, вы можете увидеть более высокую производительность.
  • 0
    В настоящее время используется sscanf (tline, '% d% d', 2), и он работает немного быстрее, но это все же не очень хорошо.
Показать ещё 4 комментария
Теги:
file-io

4 ответа

57
Лучший ответ

Описание проблемы

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

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

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

  • Фактическая операция (то, что OP вызывает "делать с nums" ), должна выполняться по одной строке за раз, не может быть векторизованной.

Обсуждение

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

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

Результаты

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

  • Исходный код. 68,23 с. 582582 проверить
  • Использование sscanf, один раз в строке. 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 check (нарушает правило 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);

(исходный ответ)

Чтобы расширить точку, сделанную Ben... ваше узкое место всегда будет файловым вводом/выводом, если вы будете читать эти файлы по строкам.

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

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

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


(ужасная идея, использующая Java-чипы, удаленные от стыда)

  • 2
    Вы тестировали эту вещь Java? Fopen I / O Matlab уже буферизован, как и stdio C; переключение на вызовы классов Java просто добавляет накладных расходов. Это в 4 раза медленнее, чем у оригинального fgetl OP. Скорее всего, это не дисковый ввод-вывод как таковой, а издержки операций в цикле, работающих с небольшими порциями данных.
  • 0
    Я проверил его на базовую функциональность, но не скорость. Вы правы, это ужасная идея. Основное редактирование грядет.
Показать ещё 6 комментариев
3

У меня были хорошие результаты (скорость) с помощью 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(), чтобы получить именно то, что вы хотите - см. онлайн-документы.

  • 0
    Я не думаю, что memmapfile дает преимущество, когда он просто используется для последовательного удаления всего файла. Вы можете просто сделать тот же textscan() непосредственно в файле и получить тот же результат, используя меньше памяти. Memmapfile больше подходит для рассеянного (не последовательного) доступа к большим файлам.
  • 2
    Преимущество memmapfile заключается в том, что он сохраняет служебную копию копии данных файла из памяти адресного пространства ядра в адресное пространство пользователя - ядро просто выделяет страницы в пространстве пользователя, поддерживаемые непосредственно дисковыми блоками, составляющими файл. Однако - как всегда, не угадайте, эталон!
Показать ещё 3 комментария
3

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

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

  • 0
    +1 Использование fscanf для чтения его такими fscanf было бы близко к замене исходного кода в исходном коде и намного быстрее, чем повторные вызовы num2str или sscanf.
1

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

  • 0
    Это хороший совет, но, похоже, он вызывает новую проблему.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню