6

我正在解析一个充满数据的大型文本文件,然后将其作为 *.mat 文件保存到磁盘,以便我可以轻松地仅加载其中的一部分(有关读取文件的更多信息,请参见此处,有关数据,请参见此处)。为此,我一次读取一行,解析该行,然后将其附加到文件中。问题是文件本身比其中包含的数据大 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 格式不是默认格式。有谁知道解决这个问题的方法?这是错误还是功能?

4

1 回答 1

3

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

从预分配开始:

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

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

于 2013-10-25T00:29:39.127 回答