MATLAB fwrite с пропуском медленно - PullRequest
3 голосов
/ 22 января 2020

Я пишу несколько больших (~ 500MB - 3GB) кусочков двоичных данных в MATLAB с помощью команды fwrite.

Я хочу, чтобы данные записывались в табличном формате, поэтому я использую параметр пропуска. Например, у меня есть 2 вектора значений uint8 a = [ 1 2 3 4]; b = [5 6 7 8]. Я хочу, чтобы бинарный файл выглядел следующим образом 1 5 2 6 3 7 4 8

Итак, в моем коде я делаю что-то похожее на это (мои данные более сложные)

fwrite(f,a,'1*uint8',1);
fseek(f,2)
fwrite(f,b,'1*uint8',1);

Но записи мучительно медленные (2 МБ / с).

Я запустил следующий блок кода, и когда я установил значение «пропущено» с счетом пропусков 1, запись примерно в 300 раз медленнее.

>> f = fopen('testfile.bin', 'w');
>> d = uint8(1:500e6);
>> tic; fwrite(f,d,'1*uint8',1); toc
Elapsed time is 58.759686 seconds.
>> tic; fwrite(f,d,'1*uint8',0); toc
Elapsed time is 0.200684 seconds.
>> 58.759686/0.200684

ans =

  292.7971

Я мог понять замедление в 2 или 4 раза, так как Вы должны пройти в два раза больше байтов с параметром пропуска, установленным в 1, но 300x заставляет меня думать, что я делаю что-то не так. Есть ли способ ускорить эту запись?

Спасибо!

ОБНОВЛЕНИЕ

Я написал следующую функцию для форматирования произвольных наборов данных. Скорость записи значительно улучшена (~ 300 МБ / с) для больших наборов данных.

%
%  data: A cell array of matrices. Matrices can be composed of any
%        non-complex numeric data. Each entry in data is considered
%        to be an independent column in the data file. Rows are indexed
%        by the last column in the numeric matrix hence the count of elements
%        in the last dimension of the matrix must match. 
%
%   e.g. 
%   size(data{1}) == [1,5]
%   size(data{2}) == [4,5]
%   size(data{3}) == [3,2,5]
%
%   The data variable has 3 columns and 5 rows. Column 1 is made of scalar values
%   Column 2 is made of vectors of length 4. And column 3 is made of 3 x 2 
%   matrices
%
% 
%  returns buffer: a N x M matrix of bytes where N is the number of bytes
%  of each row of data, and M is the number of rows of data. 

function [buffer] = makeTabularDataBuffer(data)
    dataTypes = {};
    dataTypesLengthBytes = [];
    rowElementCounts = []; %the number of elements in each "row"

    rowCount = [];

    %figure out properties of tabular data
    for idx = 1:length(data)

        cDat = data{idx};
        dimSize = size(cDat);

        %ensure each column has the same number of rows.
        if isempty(rowCount)
            rowCount = dimSize(end);
        else
            if dimSize(end) ~= rowCount
                throw(MException('e:e', sprintf('data column %d does not have the required number of rows (%d)\n',idx,rowCount)));
            end
        end

        dataTypes{idx} = class(data{idx});
        dataTypesLengthBytes(idx) = length(typecast(eval([dataTypes{idx},'(1)']),'uint8'));
        rowElementCounts(idx) = prod(dimSize(1:end-1));

    end

    rowLengthBytes = sum(rowElementCounts .* dataTypesLengthBytes);
    buffer = zeros(rowLengthBytes, rowCount,'uint8'); %rows of the dataset map to column in the buffer matrix because fwrite writes columnwise

    bufferRowStartIdxs = cumsum([1 dataTypesLengthBytes .* rowElementCounts]);

    %load data 1 column at a time into the buffer
    for idx = 1:length(data)
        cDat = data{idx};
        columnWidthBytes = dataTypesLengthBytes(idx)*rowElementCounts(idx);

        cRowIdxs = bufferRowStartIdxs(idx):(bufferRowStartIdxs(idx+1)-1);

        buffer(cRowIdxs,:) = reshape(typecast(cDat(:),'uint8'),columnWidthBytes,[]); 
    end

end

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

dat = {};
dat{1} = uint16([1 2 3 4]);
dat{2} = uint16([5 6 7 8]);
dat{3} = double([9 10 ; 11 12; 13 14; 15 16])';

buffer = makeTabularDataBuffer(dat)

buffer =

  20×4 uint8 matrix

    1    2    3    4
    0    0    0    0
    5    6    7    8
    0    0    0    0
    0    0    0    0
    0    0    0    0
    0    0    0    0
    0    0    0    0
    0    0    0    0
    0    0    0    0
   34   38   42   46
   64   64   64   64
    0    0    0    0
    0    0    0    0
    0    0    0    0
    0    0    0    0
    0    0    0    0
    0    0    0    0
   36   40   44   48
   64   64   64   64

1 Ответ

4 голосов
/ 22 января 2020

Для лучшей производительности ввода / вывода используйте последовательные записи и избегайте пропусков.

  • Изменение порядка данных в ОЗУ перед сохранением в файл.
    Изменение порядка данных в ОЗУ в 100 раз быстрее, чем изменение порядка данных на диске.

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

В механических приводах (HDD) запись данных с пропуском может занять очень много времени, поскольку механическая головка привода должна двигаться (обычно ОС оптимизирует ее с помощью буфера памяти, но в принципе это требует много времени).

При использовании SSD механический поиск не выполняется, но последовательная запись все еще выполняется намного быстрее. Прочитайте следующий пост Последовательный или случайный ввод / вывод на SSD? для некоторого объяснения.


Пример для переупорядочения данных в ОЗУ:

a = uint8([1 2 3 4]);
b = uint8([5 6 7 8]);

% Allocate memory space for reordered elements (use uint8 type to save RAM).
c = zeros(1, length(a) + length(b), 'uint8');

%Reorder a and b in the RAM.
c(1:2:end) = a;
c(2:2:end) = b;

% Write array c to file
fwrite(f, c, 'uint8');
fclose(f);

Измерения времени на моей машине:

  • Запись файла на SSD:
    Elapsed time is 56.363397 seconds.
    Elapsed time is 0.280049 seconds.
  • Запись файла на жесткий диск:
    Elapsed time is 56.063186 seconds.
    Elapsed time is 0.522933 seconds.
  • Переупорядочение d в оперативной памяти:
    Elapsed time is 0.965358 seconds.

Почему в 300 раз медленнее, а не в 4 раза?
Я предполагаю, что программная реализация записи данных с пропуском не оптимизирована для лучшей производительности.


Согласно следующему post :

fseek() или fflush() требуется, чтобы библиотека выполняла буферизованные операции.

Предположение Даниэля (в комментарии), вероятно, верно.
"Пропуск заставляет MATLAB выводить грипп sh после каждого байта."
Пропуск, вероятно, реализуется с использованием fseek() и fseek() сил сброс данных на диск.
Это может объяснить, почему запись с пропуском мучительно медленная.

...