【问题标题】:Excessively large overhead in MATLAB .mat fileMATLAB .mat 文件中的开销过大
【发布时间】:2013-11-03 21:26:12
【问题描述】:

我正在解析一个充满数据的大型文本文件,然后将其作为 *.mat 文件保存到磁盘,以便我可以轻松地仅加载其中的一部分(有关读取文件的更多信息,请参阅here,和here 用于数据)。为此,我一次读取一行,解析该行,然后将其附加到文件中。问题是文件本身比其中包含的数据大>3个数量级!

这是我的代码的精简版:

database = which('01_hit12.par');
[directory,filename,~] = fileparts(database);
matObj = matfile(fullfile(directory,[filename '.mat']),'Writable',true);

fidr = fopen(database);
hitranTemp = fgetl(fidr);
k = 1;
while ischar(hitranTemp)
    if abs(hitranTemp(1)) == 32;
        hitranTemp(1) = '0';
    end

    hitran = textscan(hitranTemp,'%2u%1u%12f%10f%10f%5f%5f%10f%4f%8f%15c%15c%15c%15c%6u%2u%2u%2u%2u%2u%2u%1c%7f%7f','delimiter','','whitespace','');

    matObj.moleculeNumber(1,k)      = uint8(hitran{1});
    matObj.isotopeologueNumber(1,k) = uint8(hitran{2});
    matObj.vacuumWavenumber(1,k)    = hitran{3};
    matObj.lineIntensity(1,k)       = hitran{4};
    matObj.airWidth(1,k)            = single(hitran{6});
    matObj.selfWidth(1,k)           = single(hitran{7});
    matObj.lowStateE(1,k)           = single(hitran{8});
    matObj.tempDependWidth(1,k)     = single(hitran{9});
    matObj.pressureShift(1,k)       = single(hitran{10});

    if rem(k,1e4) == 0;
        display(sprintf('line %u (%2.2f)',k,100*k/K));
    end
    hitranTemp = fgetl(fidr);
    k = k + 1;
end
fclose(fidr);

在解析了 224,515 行中的 13,813 行后,我停止了代码,因为它花费了很长时间并且文件大小越来越大,但最后的打印输出表明我只清除了 10k 行。我清空了内存,然后跑了:

S = whos('-file','01_hit12.mat');
fileBytes = sum([S.bytes]);

T = dir(which('01_hit12.mat'));
diskBytes = T.bytes;

disp([fileBytes diskBytes diskBytes/fileBytes])

并获得输出:

524894 896189009 1707.37141022759

什么占用了额外的 895,664,115 字节?我知道帮助页面说应该有一点额外的开销,但我觉得近 Gb 的描述性标题有点过分!

新信息:
我尝试预先分配文件,认为当矩阵在一个循环中嵌入并在每次写入时为整个矩阵重新分配一块磁盘空间时,MATLAB 可能正在做同样的事情,但事实并非如此。用适当数据类型的零填充文件会生成我的简短检查脚本返回的文件:

8531570 71467 0.00837677004349727

这对我来说更有意义。 Matlab 是稀疏保存文件,因此磁盘文件大小远小于内存中完整矩阵的大小。但是,一旦它开始用真实数据替换值,我就会得到与以前相同的行为,并且文件大小开始飙升,超出所有合理范围。

新的新信息:
对 100 行长的数据子集进行了尝试。要流式传输到磁盘,数据必须采用 v7.3 格式,因此我通过脚本运行子集,将其加载到内存中,然后重新保存为 v7.0 格式。结果如下:

v7.3: 3800 8752 2.30
v7.0: 3800 2561 0.67

难怪 v7.3 格式不是默认格式。有谁知道解决这个问题的方法?这是错误还是功能?

【问题讨论】:

  • 您的操作系统说明磁盘上实际使用了多少空间?
  • 857,185Kb(Windows 7 64 位,MATLAB 2013a 64 位)
  • 在我看来确实是个 bug,但你可以通过分块编写来绕过它。我尝试了 10,000 个块,文件大小合理。
  • 我正在使用的这个测试文件可能都适合内存,但这是一个完整的 HITRAN 2012 数据库的测试用例,它是一个 700 Mb 的文本文件。将其加载到内存中,然后将其拆分为不同的变量,然后将其保存到磁盘,它使我的计算机瘫痪了。第一次尝试时,我不得不进行硬重置并丢失了一堆其他工作。
  • 这就是诀窍。我没有足够的内存来避免它。我的主要目标是将数据文件转换为 matlab 二进制文件,我可以在其中加载单个变量,比如行频或分子,搜索标准,如范围或同位素,然后仅加载该部分满足条件的数据库。以本机文本形式,我必须将整个内容加载到内存中(或者进行逐行搜索,这需要花费大量时间。如果我可以将其转换为与matfile 命令兼容的合理大小的二进制文件,这样可以节省时间和内存。

标签: matlab


【解决方案1】:

这对我来说似乎是一个错误。一种解决方法是分块写入到预分配的数组。

从预分配开始:

fid = fopen('01_hit12.par', 'r');
data = fread(fid, inf, 'uint8');
nlines = nnz(data == 10) + 1;
fclose(fid);

matObj.moleculeNumber = zeros(1,nlines,'uint8');
matObj.isotopeologueNumber = zeros(1,nlines,'uint8');
matObj.vacuumWavenumber = zeros(1,nlines,'double');
matObj.lineIntensity = zeros(1,nlines,'double');
matObj.airWidth = zeros(1,nlines,'single');
matObj.selfWidth = zeros(1,nlines,'single');
matObj.lowStateE = zeros(1,nlines,'single');
matObj.tempDependWidth = zeros(1,nlines,'single');
matObj.pressureShift = zeros(1,nlines,'single');

然后为了写入 10000 个块,我将您的代码修改如下:

... % your code plus pre-alloc first
bs = 10000;
while ischar(hitranTemp)
    if abs(hitranTemp(1)) == 32;
        hitranTemp(1) = '0';
    end

    for ii = 1:bs,
        hitran{ii} = textscan(hitranTemp,'%2u%1u%12f%10f%10f%5f%5f%10f%4f%8f%15c%15c%15c%15c%6u%2u%2u%2u%2u%2u%2    u%1c%7f%7f','delimiter','','whitespace','');
        hitranTemp = fgetl(fidr);
        if hitranTemp==-1, bs=ii; break; end
    end

    % this part really ugly, sorry! trying to keep it compact...
    matObj.moleculeNumber(1,k:k+bs-1)      = uint8(builtin('_paren',cellfun(@(c)c{1},hitran),1:bs));
    matObj.isotopeologueNumber(1,k:k+bs-1) = uint8(builtin('_paren',cellfun(@(c)c{2},hitran),1:bs));
    matObj.vacuumWavenumber(1,k:k+bs-1)    = builtin('_paren',cellfun(@(c)c{3},hitran),1:bs);
    matObj.lineIntensity(1,k:k+bs-1)       = builtin('_paren',cellfun(@(c)c{4},hitran),1:bs);
    matObj.airWidth(1,k:k+bs-1)            = single(builtin('_paren',cellfun(@(c)c{5},hitran),1:bs));
    matObj.selfWidth(1,k:k+bs-1)           = single(builtin('_paren',cellfun(@(c)c{6},hitran),1:bs));
    matObj.lowStateE(1,k:k+bs-1)           = single(builtin('_paren',cellfun(@(c)c{7},hitran),1:bs));
    matObj.tempDependWidth(1,k:k+bs-1)     = single(builtin('_paren',cellfun(@(c)c{8},hitran),1:bs));
    matObj.pressureShift(1,k:k+bs-1)       = single(builtin('_paren',cellfun(@(c)c{9},hitran),1:bs));

    k = k + bs;
    fprintf('.');
end
fclose(fidr);

磁盘上的最终大小为 21,393,408 字节。用法分解为,

>> S = whos('-file','01_hit12.mat');
>> fileBytes = sum([S.bytes]);
>> T = dir(which('01_hit12.mat'));
>> diskBytes = T.bytes; ratio = diskBytes/fileBytes;
>> fprintf('%10d whos\n%10d disk\n%10.6f\n',fileBytes,diskBytes,ratio)
   8531608 whos
  21389582 disk
  2.507099

仍然相当低效,但没有失控。

【讨论】:

  • 我有一夜之间运行的版本(不是说它需要那么长时间,但我在下班之前运行它)。早上我要么有一个我可以使用的 20Mb 文件,要么有一个 30Gb 的怪物,它会吃掉我的硬盘。当您关注该主题并拥有数据文件时,如果您可以查看我上周发布的有关 MATLAB 在使用 textscan 时占用前导空间的链接问题(因此是 if(abs(hitranTemp)==32);hitranTemp(1)='0';end 块),我将不胜感激.你在解析数据时看到同样的东西吗?
  • 现在应该已经完成​​了。 :) 在我的机器上只花了几分钟,因为只有大约 20 次写入而不是 >200,000。
  • 我的电脑只有 6 个月大,但对我来说运行速度仍然很慢。我开始运行它,当我收拾好东西准备离开时,它仍在运转。
  • 做到了!实际上,完成后,文件大小为 8531570 whos,7263675 ​​disk,比例为 0.851388。
猜你喜欢
  • 2015-07-21
  • 2015-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多