【问题标题】:Fastest way to count lines in file in MATLAB (Perl faster than C?)在 MATLAB 中计算文件行数的最快方法(Perl 比 C 快?)
【发布时间】:2014-08-09 17:19:09
【问题描述】:

在我的研究中,我必须计算 10+ GB csv 文件中的行数。在 MATLAB 上执行此操作的经典方法似乎是使用 textscan()\n 作为分隔符,但这会占用大量内存并且速度非常慢。有人建议我编写一个 Perl 脚本并使用 str2double(perl('countlines.pl', path)) 调用它,这似乎确实要快得多:

# countlines.pl
while (<>) {};
print $.,"\n";

然后,我想看看在 C 中编写一个 MEX 函数是否有任何优势,但没有运气,更令人惊讶的是,我发现这比 Perl 脚本慢了大约 10 倍(使用Xcode 4.6.3 上的 LLVM 编译器):

//countlines.c

#include "mex.h"

void countlines(char *filepath, double *numLines)
{
    /* Routine */

    numLines[0] = 0;
    FILE *inputFile = fopen(filepath, "r");
    int ch;

    while (EOF != (ch=getc(inputFile)))
        if ('\n' == ch)
            ++numLines[0];
}

void mexFunction( int nlhs, mxArray *plhs[],
                  int nrhs, const mxArray *prhs[])
{
    /* Gateway function */

    int bufferLength, status;
    char *filepath;                 // Input: File path
    double *numLines;               // Output Number of lines

    bufferLength = (mxGetM(prhs[0]) * mxGetN(prhs[0])) + 1; // Get length of string
    filepath = mxCalloc(bufferLength, sizeof(char)); // Allocate memory for input

    // Copy the string data from prhs[0] into a C string
    status = mxGetString(prhs[0], filepath, bufferLength);
    if (status != 0)
        mexErrMsgIdAndTxt("utils:countlines:insufficientSpace", "Insufficient space, string is truncated.");

    // Create the output matrix and get a pointer to the real data in the output matrix
    plhs[0] = mxCreateDoubleMatrix(1,(mwSize)1,mxREAL);
    numLines = mxGetPr(plhs[0]);

    // Call the C routine
    countlines(filepath, numLines);
}

所以,

  1. 除了网关功能之外,MEX 功能中的这些开销来自哪里?
  2. 我还能做些什么来加快速度吗?只要我们可以让例程与 MATLAB 交互,我愿意使用任何语言。似乎唯一的其他方法是内存映射文件块并将工作负载拆分到几个内核。

【问题讨论】:

  • 在linux上可以使用系统命令wc -l &lt; filename
  • 按照 PetrH 的建议,您是否尝试在 MATLAB 中使用 system 调用它?
  • 是的!非常感谢。我尝试了[~,output] = unix(strcat('wc -l &lt; ', path)); numLines = str2double(output);,它比 Perl 方法稍快。我将它封装在if isunix 块中,并将Perl 方法保留在else 块下。 :)
  • wc -l 绝对是如果 Matlab 在本机上执行速度很慢的方法,但如果文件名包含空格或任何其他 shell 元字符,strcat('wc -l &lt; ', path) 将失败。您需要先将路径转换为文字。您需要的代码是 die if $path =~ /\0/; $path =~ s/'/'\\''/g; $path = "'".$path."'"; 的 Matlab 等效代码

标签: c++ c performance perl matlab


【解决方案1】:

您是否阅读过 Perl 关于这个主题的常见问题解答,其中提供了大约 6 个示例?

perldoc -q 'How do I count the number of lines in a file'

wc 命令已移植到 Windows,因此如果您想安装它,这可能是最好的解决方案。否则,我会在 wc 示例之前使用 Perl 示例(下面已修复和优化)。

    my $lines = 0;
    open my $fh, '<:raw', $filename
        or die "Can't open $filename: $!";
    while( sysread $fh, $buffer, 64*1024 ) {
        $lines += ( $buffer =~ tr|\n||; );
    }
    close $fh;

【讨论】:

  • 感谢您的参考链接。我尝试了这个实现,奇怪的是它实际上比使用菱形运算符对 200 到 600 MB 的文件大小进行简单计数慢了 20-40%。但是,我会接受您的回答,因为我同意使用wc 是最快的。
【解决方案2】:

除了网关功能之外,MEX 功能中的这些开销来自哪里?

  1. MEX 函数正在分配内存。
  2. 该函数正在将内存转换为字符串。
  3. 该函数正在创建一个双精度矩阵。

无法与 Perl 的简单行计数功能相比,因为它们在功能上并不等效。

我还能做些什么来加快速度吗? 是的,只计算行数。
没有额外的东西,比如读取双打矩阵。

以下是使用 C++ 计算文本文件中行数的示例:

std::ifstream text_file(/*...*/);
std::string   text_from_file;
unsigned int  line_count = 0;
while (std::getline(text_file, '\n'))
{
  ++line_count;
}

在比较性能时,功能必须相同。

编辑 1:
决定。 你在数行吗?

您在计算矩阵中的行数吗?

您想计算文件中的行数吗?

如果你想计算矩阵的行数,你需要修改你的 Perl 脚本。

如果您希望 MEX 函数计算行数,请删除除对 countlines 函数的调用之外的所有内容。

为什么要使用 double 来计算行数?
您是否期望小数行数?

您要使用 C I/O 还是 C++ I/O?

以块为单位读取数据将加速您的 C I/O 功能:

#define MAX_CHUNK_SIZE 1024*1024
char buffer[MAX_CHUNK_SIZE];
size_t chars_read = 0;
unsigned int line_count = 0;
//...
while (!feof(inputFile))
{
  chars_read = fread(buffer, 1, MAX_CHUNK_SIZE, input_file);
  char c;
  for (unsigned int i = 0; i < chars_read; ++i)
  {
     if (c == '\n')
     {
       ++line_count;
     }
  }
}

访问文件的瓶颈是定位数据的开销。大量读取可减少开销。

【讨论】:

  • 我只计算文件中的行数,而不是矩阵中的行数。我使用双倍的行数,因为 MATLAB 为另一种方法产生双倍输出;这是一个很好的捕获,我会尝试将其切换到 unsigned long long,它应该有更快的操作,然后在返回之前将其转换回双精度。 UNIX 命令似乎很难被击败,但让我测试一下您的建议,因为我仍然想要一种在 Windows 上执行此操作的快速方法。
【解决方案3】:

在这段代码中计算总行数。但这需要几个小程序。

my $lines = do {
    open my $fh, '<', "filename" or die "Can't open filename: $!";
    1 while (<$fh>);
    $.
};
print "Total number of lines: $lines\n";

【讨论】:

  • @Miller:标量也计算总行数,那你为什么使用while条件。?
  • 这将尝试将整个文件加载到内存中。鉴于 OP 正在谈论一个 10gig 文件,这可能会耗尽空间并最终缓存。基本上,除非有人想要将整个文件放在内存中的特定原因,否则应该始终倾向于逐行处理。
【解决方案4】:

要有效地计算行数,只需执行以下操作:

int main()
{
   unsigned long lines = 0;
   int c; /* c must be an int, not char */

   while ((c = getchar()) != EOF) 
      if (c == '\n') 
         lines++;
   printf("%lu\n", lines);
   return 0;
} /* main */

我认为 Kernighan & Ritchie 有一个类似的例子,如果不一样的话。请不要使用double 来计算下一次。使用整数类型进行计数比使用浮点数进行计数要高效得多。

【讨论】:

  • 这效率不高,因为您一次将一个字符读入一个变量。这意味着您正在调用操作系统,告诉硬盘驱动器旋转,读取一个字节,然后旋转并让操作系统返回值。硬盘喜欢一次获取大量数据。因此,将 1 兆字节读入内存比 1 兆字节读取 1 字节的调用效率更高。在每种情况下,硬盘驱动器开销都是相同的。试试看。
  • 不是。你错了。 STDIO 以 BUFSIZ 大小的块缓冲,因此您一次只能处理每个字符。如果我在做while (read(&amp;c, 1, 0) == 1),我会一次读一个字符,但事实并非如此。先看看K&R。即使您从终端读取,stdio 也会缓冲所有行并使用 getchar() 一次处理一个字符;这给尝试使用 getchar() 一次读取一个字符并在 curses.h getch() 中寻找替代方法的新手带来了争议
  • 让我们举个紧密的例子,打印汇编代码。您会注意到,每次读取一个字符都有一个分支语句。所以要读取 1000 个字符,就会有 1000 个函数调用。使用 fread 读取 1000 个字符会导致 1 个函数调用。此外,OP 正在读取文件,getchar()stdin 读取。再次,计时。我做到了。这就是我在阅读大文件时节省了 5 多分钟的方法。
  • 顺便说一句,查看BUFSIZ 的大小。是 1 兆字节吗?是512吗?通过读取您自己的缓冲区,您可以将大小更改为大于BUFSIZ。命令将是 fread(buffer, 1, 1024 * 512, file); 或使用 C++:read(buffer, 1024 * 512)。我不知道你从哪里得到你的阅读语法。
  • 当然您也可以设置自己的缓冲区并将其用于 stdio 功能。我不会参与辩论……这不是我阅读角色的方式,而是 Kernighan 在他的书中使用的方式。 Getchar 通常也定义为宏,调用 fget 来获取字符。 BUFSIZ 针对系统的块大小进行了优化,没有标准强制它为 512。当然,你可以使用任何你喜欢的函数。但我认为你不会中奖。我不打算打印汇编代码,因为这个问题是通用语言问题,而不是特定的编译器实现问题。
猜你喜欢
  • 1970-01-01
  • 2010-09-07
  • 1970-01-01
  • 1970-01-01
  • 2014-04-17
  • 2014-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多