我正在使用fwrite命令在MATLAB中编写一些较大的(〜500MB-3GB)二进制数据。

我希望数据以表格格式写入,因此我使用了skip参数。例如。我有2个uint8值a = [ 1 2 3 4]; b = [5 6 7 8]的 vector 。我希望二进制文件看起来像这个1 5 2 6 3 7 4 8
所以在我的代码中,我做了类似的事情(我的数据更复杂)

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

但是写入速度非常慢(2MB / s)。

我运行了以下代码块,当我将跳过计数设置为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倍的速度下降,因为您必须在skip参数设置为1的情况下遍历两倍的字节,但是300倍让我觉得我做错了。

有人遇到过吗?有没有办法加快写入速度?

谢谢!

更新

我编写了以下函数来格式化任意数据集。对于大型数据集,写入速度得到了极大提高(〜300MB / s)。

%
%  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而不使用skip参数,并且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

最佳答案

为了获得最佳的I / O性能,请使用顺序写入,并避免跳过。

  • 在保存到文件之前,对RAM中的数据重新排序。
    对RAM中的数据进行重新排序的速度比对磁盘上的数据进行排序的速度快100倍。

  • I / O操作和存储设备针对大型数据块的顺序写入进行了优化(在硬件和软件方面均进行了优化)。

    在机械驱动器(HDD)中,由于驱动器的机械磁头必须移动(通常OS通过使用内存缓冲区对其进行优化,但原则上会花费很长时间),因此通过跳过写入数据可能会花费很长时间。

    使用SSD时,不会进行机械搜索,但是顺序写入仍然要快得多。阅读以下Sequential vs Random I/O on SSDs?帖子以获得一些解释。

    在RAM中重新排序数据的示例:
    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.
  • 在RAM中重新排序d:Elapsed time is 0.965358 seconds.

  • 为什么慢300倍而不是4倍?
    我猜测使用跳过写入数据的软件实现并未针对最佳性能进行优化。

    根据以下post:

    fseek()fflush()要求库提交缓冲的操作。

    丹尼尔(在评论中)的猜测可能是正确的。
    “跳过会导致MATLAB在每个字节之后刷新。”
    跳过可能是使用fseek()实现的,并且fseek()强制将数据刷新到磁盘。
    它可以解释为什么用跳过写会很痛苦。

    10-04 11:54
    查看更多