为什么用C编写的行计数函数比Ruby慢?

时间:2014-05-23 15:33:52

标签: c ruby gcc

我有一个函数的两个实现,它打印给定文件中的行数,一个用C编写,另一个用Ruby编写。出于某些奇怪的原因,Ruby版本快了2倍!这是代码:

linecount.c (使用gcc linecount.c -o linecount编译)

#include <stdio.h>

int main(int argc, char **argv) {
  FILE *fp;
  int c;
  int count;

  fp = fopen(argv[1], "r");

  while ((c = getc(fp)) != EOF) {
    if (c == '\n') {
      count++;
    }
  }

  fclose(fp);
  printf("%d\n", count);
  return 0;
}

ruby​​_linecount.rb

#!/usr/bin/env ruby

puts File.open(ARGV[0]).lines.count

这些是基准:

time (for i in {1..100}; do ./linecount /usr/share/dict/words; done)
real    0m14.438s 
user    0m14.041s
sys     0m0.298s

time (for i in {1..100}; do ./ruby_linecount.rb /usr/share/dict/words; done)
real    0m6.910s
user    0m5.917s
sys     0m0.734s

为什么C版这么慢?如何提高C代码的性能?是否有任何编译器标志可以提供帮助?

4 个答案:

答案 0 :(得分:2)

您可以使用编译器选项-O3来优化性能。您也可以考虑使用fgets来避免逐个字符地读取文件。

答案 1 :(得分:1)

从文件中读取是一种高延迟操作。最有可能的是,通过从文件中读取更大的数据块,可以提高C版本的速度。

我提供两个例子。

第一个使用16K缓冲区。可以更改缓冲区大小以查看更好的性能。

示例1

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#define BUFSIZE (16 * 1024)

int main(int argc, char **argv)
   {
   int rCode;
   FILE *fp = NULL;
   char *buf = NULL;
   int count = 0;
   size_t bufLen;

   errno=0;
   fp = fopen(argv[1], "r");
   if(NULL == fp)
      {
      rCode=errno;
      fprintf(stder, "fopen() failed.  errno:%d\n", errno);
      goto CLEANUP;
      }

   errno=0;
   buf = malloc(BUFSIZE);
   if(NULL == buf)
      {
      rCode=errno;
      fprintf(stder, "malloc() failed.  errno:%d\n", errno);
      goto CLEANUP;
      }

   bufLen = fread(buf, 1, BUFSIZE, fp);
   while(bufLen)
      {
      char *cp;

      for(cp=buf; (cp < buf + bufLen); ++cp)
         if('\n' == *cp)
            ++count;

          bufLen = fread(buf, 1, BUFSIZE, fp);
      }

   printf("%d\n", count);

CLEANUP:

   if(fp)
      fclose(fp);

   if(buf)
      free(buf);

   return(rCode);
   }

下一个将文件映射到进程内存映射(或地址空间)。然后,寻找新行是内存search for newlines操作。

示例2

#include <errno.h>    /* errno, ... */
#include <fcntl.h>    /* open(), O__RDONLY, ... */
#include <stdio.h>    /* fprintf(), stderr, printf(), ... */
#include <sys/mman.h> /* mmap(), PROT_READ, MAP_SHARED, ... */
#include <sys/stat.h> /* fstat(), struct stat, ... */
#include <unistd.h>   /* close(), ... */

int main(int argc, char **argv)
   {
   int rCode=0;
   int fd = (-1);
   struct stat statBuf;
   char *fileBuf=NULL;
   char *cp;
   int   count=0;

   errno=0;
   fd = open(argv[1], O_RDONLY);
   if((-1) == fd)
      {
      rCode=errno;
      fprintf(stderr, "open() failed.  errno:%d\n", errno);
      goto CLEANUP;
      }

   errno=0;
   if((-1) == fstat(fd, &statBuf))
      {
      rCode=errno;
      fprintf(stderr, "fstat() failed.  errno:%d\n", errno);
      goto CLEANUP;
      }

   errno=0;
   fileBuf = mmap(
         NULL,            /* preferred start address, normally NULL (system chooses) */
         statBuf.st_size, /* length of the mapped region */
         PROT_READ,       /* memory protection */
         MAP_SHARED,      /* private/shared */
         fd,              /* fd of mapped file */
         0                /* file offset (should be a multiples of a page) */
         );
   if((void *)(-1) == fileBuf)
      {
      rCode=errno;
      fprintf(stderr, "mmap() failed.  errno:%d\n", errno);
      goto CLEANUP;
      }

   for(cp=fileBuf; cp < fileBuf + statBuf.st_size; ++cp)
      if('\n' == *cp)
         ++count;

  printf("%d\n", count);

CLEANUP:

   if(fileBuf)
      munmap(fileBuf, statBuf.st_size);

   if((-1) != fd)
      close(fd);

   return(rCode);
   }

修改

我同意Neil Slater's评论。虽然上面的例子可以提高操作的速度(与问题代码中的例子相比);也许Ruby会很快。

答案 2 :(得分:0)

在MacBook Pro上使用您的代码显示C版本的错误结果。我建议将while循环更改为:

 while ( (c=fgetc(fp)) != EOF )

使用fgetc代替getc。此外,您可以使用前面建议的-O3标志优化编译器。

对于ruby代码,最好使用each_line而不是lines(这是一个弃用的别名)。

根据您使用的ruby版本,您可以获得不同的结果。在我的机器上,ruby比相应的C代码慢大约50倍。

答案 3 :(得分:0)

你要做的事情应该是严重的IO限制。换句话说,大部分时间用于读取文件而不是进行实际计算。您应该尝试使用另一种方式在C版本中读取文件,并且可以查看Ruby正在使用的内容。

我猜mmap可以为您提供更好的表现getc

另外,请注意基准程序的顺序。在第一次运行其中一个程序后,该文件可能会在内存缓存中,使另一个程序更快。您应该多次运行每个程序,以获得更准确的计时。