2

我有一个 1.6 GB 大的 CSV 文件,我需要将其输入 matlab。我将不得不经常这样做,我需要它快速运行。该文件的格式为:

20111205    00:00.2 99.18   6   E
20111205    00:00.2 99.18   5   E
20111205    00:00.2 99.18   1   E
20111205    00:00.2 99.195  5   E
20111205    00:00.2 99.195  5   E
20111205    01:27.0 99.19   5   E
20111205    02:01.4 99.185  1   E
20111205    02:01.4 99.185  1   E
20111205    02:01.4 99.185  1   E
20111205    02:01.4 99.185  1   E

我现在拥有的代码如下:

tic;
format long g
fid = fopen('C:\Program Files\MATLAB\R2013a\EDU13.csv','r');
[c] = fscanf(fid, '%d,%d:%d.%d,%f,%d,%c');
c = reshape(c, 7, length(c)/7)  
toc;

但这太慢了。我希望能以最有效的方式将此 CSV 文件导入 matlab。谢谢!

4

3 回答 3

3

考虑使用二进制文件格式。二进制文件要小得多,不需要通过 MATLAB 转换成二进制格式。因此,它们的读写速度要快得多。它们也可能更准确(精度可能更高)。

http://www.mathworks.com.au/help/matlab/ref/fread.html

于 2013-06-12T05:47:38.797 回答
1

推荐的语法是 textscan ( http://www.mathworks.com/help/matlab/ref/textscan.html )

您的代码如下所示:

fid = fopen('C:\Program Files\MATLAB\R2013a\EDU13.csv','r');
c = textscan(fid, '%d,%d:%d.%d,%f,%d,%c');
fclose(fid);

你最终得到了一个单元格数组......是否值得将其转换为另一种形状真的取决于你以后想要如何访问数据。

如果您包含一个允许您在大部分操作中使用较小的固定内存量的循环,这很可能会更快。读取大文件的一个问题是您事先不知道它有多大——这很可能意味着 Matlab 猜测它需要的内存量,并且经常需要重新调整大小。这是一个非常缓慢的操作 - 例如,如果它每 1MB 发生一次,那么它会复制 1 MB 一次,然后复制 2 MB,然后再复制 3 MB,等等 - 正如您所看到的,它在数组的大小上是二次的。

相反,如果您为最终结果分配固定数量的内存,并以较小的批次进行处理,则可以避免所有这些开销。我很确定它会快得多 - 但你必须对块大小进行一些试验。看起来像这样:

block = 1000;
Nlines = 35E6; 
fid = fopen('C:\Program Files\MATLAB\R2013a\EDU13.csv','r');
c = struct(field1, field2, fieldn, value); %... initialize structure array or other storage for c ...
c_offset = 0;
while ~feof(fid)
  temp = textscan(fid, '%d,%d:%d.%d,%f,%d,%c', block);
    bt = size(temp, 1); % first dimension - should be `block`, except for last loop
    %... extract, process, store in c(c_offset + (1:bt))... 
    c_offset = c_offset + bt;
end
fclose(fid);
于 2013-06-12T05:44:06.963 回答
1

受@Axon 回答的启发,我实现了一个“快速”C 程序将文件转换为二进制文件,然后使用 Matlab 的fread函数将其读取。剧透警告:阅读速度提高了 20 倍……尽管初始转换需要一点时间。

为了使 Matlab 中的工作更容易,文件大小更小,我将每个数字字段转换为一个int16(短整数)。对于第一个字段 - 看起来像一个 yyyymmdd 字段 - 这涉及拆分为两个较小的数字;同样,十进制数字被转换为两个短整数(鉴于我认为有效的明显范围)。所有这一切都是在认识到“要真正优化,您必须真正了解您的问题”——因此,如果假设无效,结果也会如此。

这是C代码:

#include <stdio.h>
int main(){
  FILE *fp, *fo;
  long int ld1;
  int d2, d3, d4, d5, d6, d7;
  short int buf[9];
  char c8;
  int n;
  short int year, monthday;
  fp = fopen("bigdata.txt", "r");
  fo = fopen("bigdata.bin", "wb");
  if (fp == NULL || fo == NULL) {
    printf("unable to open file\n");
    return 1;
  }
  while(!feof(fp)) {
    n = fscanf(fp, "%ld %d:%d.%d %d.%d %d %c\n", \
      &ld1, &d2, &d3, &d4, &d5, &d6, &d7, &c8);
    year = d1 / 10000;
    monthday = d1 - 10000 * year;
    // move everything into buffer for single call to fwrite:
    buf[0] = year;
    buf[1] = monthday;
    buf[2] = d2;
    buf[3] = d3;
    buf[4] = d4;
    buf[5] = d5;
    buf[6] = d6;
    buf[7] = d7;
    buf[8] = c8;
    fwrite(buf, sizeof(short int), 9, fo);
    }
  fclose(fp);
  fclose(fo);
  return 0;
}  

生成的文件大约是原始文件大小的一半——这是令人鼓舞的,并且会加快访问速度。请注意,如果可以将输出文件写入与输入文件不同的磁盘,那将是一个好主意——它确实有助于保持数据流,而不会在查找操作上浪费大量时间。

基准测试:使用 2 M 行的文件作为输入,运行时间约为 2 秒(相同的磁盘)。生成的二进制文件在 Matlab 中读取,内容如下:

tic
fid = fopen('bigdata.bin');
d = fread(fid, 'int16');
d = reshape(d, 9, []);
toc

当然,现在如果要将数字恢复为浮点数,则需要做一些工作;但我认为这是值得的。您必须解决的一个可能问题是小数点后的值具有不同位数的情况:当 b > 100 时,将 (a,b) 转换为浮点数并不像“a + b/100”那么简单...“为学生锻炼”?

一点点基准测试:上面的代码花了大约 0.4 秒。相比之下,我的第一个建议textread在同一个文件上花费了大约 9 秒;你的原始代码用了 11 秒多一点。当文件变大时,差异可能会变大。

如果您经常这样做(如您所说),显然值得将您的文件一次转换为二进制格式,然后以这种方式使用它们。特别是如果文件只需要转换一次,并且需要多次读取,节省的费用将是相当可观的。

更新

我用一个 13M 的行文件重复了基准测试。转换耗时 13 秒,二进制读取 < 3 秒。相比之下,其他两种方法中的每一种都花费了一分钟(textscan:61s;fscanf:77s)。似乎事情是线性缩放的(文件大小 470M 文本,240M 二进制)

于 2013-06-12T16:40:10.463 回答