需要比“ wc -l”更快的东西


12

对于1GB wc -l这样的大文件,它的运行速度很慢。我们是否有更快的方法来计算特定文件的换行数?


25
购买更快的磁盘?鉴于必须检查输入的每个字节的0x0A完整性,因此I / O无疑是瓶颈。
2016年

2
如果您怀疑wc有太多的开销,则可以尝试自己实现foreach byte in file: if byte == '\n': linecount++。如果用C或汇编程序实现,我认为它不会更快,除非在具有最高优先级的RTOS的内核空间中(甚至为此使用中断-您无法对系统做任何其他事情)。 ..好吧,我离题了;
墨菲(Murphy

3
只是为了了解扩展的规模,我快速浏览time wc -l some_movie.avi了一个未缓存的文件,结果为5172672 some_movie.avi -- real 0m57.768s -- user 0m0.255s -- sys 0m0.863s。基本上证明@thrig是正确的,在这种情况下,I / O会降低您的性能。
墨菲

10
证明它是磁盘IO瓶颈的最佳方法time wc -l some_large_file_smaller_than_cache,快速连续执行两次,看看第二次操作有多快,然后time wc -l some_large_file_larger_than_cache看看两次运行之间的时间如何变化。对于此处的〜280MB文件,时间从1.7秒变为0.2秒,而对于2GB文件,则均为14秒。
AugustBitTony

1
多慢对您来说太慢了?怎么/usr/bin/time wc -l <file>说?您的硬件是什么?重复运行命令会更快吗?我们真的需要更多信息;)
marcelm

Answers:


21

您可以尝试用C编写:

#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(){
  char buf[BUFSIZ];
  int nread;
  size_t nfound=0;
  while((nread=read(0, buf, BUFSIZ))>0){
    char const* p;
    for(p=buf; p=memchr(p,'\n',nread-(p-buf)); nfound++,p++) {;}
  }
  if(nread<0) { perror("Error"); return 1; }
  printf("%lu\n", nfound);
  return 0;
}

保存在例如中wcl.c,使用编译gcc wcl.c -O2 -o wcl并使用

<yourFile ./wcl

这发现换行符大约在370ms内散布在我系统上的1GB文件中(重复运行)。(增加缓冲区大小会稍微增加时间,这是可以预期的-BUFSIZ应该接近最佳值)。这与我收到的约380ms相当wc -l

Mmaping给了我大约280ms的更好的时间,但是它的局限性是仅限于实际文件(无FIFO,无终端输入等):

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
  struct stat sbuf;
  if(fstat(0, &sbuf)<0){ perror("Can't stat stdin"); return 1; }

  char* buf = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, 0/*stdin*/, 0/*offset*/);
  if(buf == MAP_FAILED){ perror("Mmap error"); return 1; } 

  size_t nread = sbuf.st_size, nfound=0;
  char const* p;
  for(p=buf; p=memchr(p,'\n',nread-(p-buf)); nfound++,p++) {;}

  printf("%lu\n", nfound);
  return 0;
}

我使用以下命令创建了测试文件:

 $ dd if=/dev/zero of=file bs=1M count=1042 

并添加了一些测试换行符:

 $ echo >> 1GB 

和十六进制编辑器。


我对TBH的mmap结果感到惊讶。我曾经认为mmaping比读/写要快,但是后来我看到一些Linux基准测试却显示了相反的结果。看起来在这种情况下是非常正确的。
PSkocik

4
mmap将在linux上获得更好的结果,因为这些天它们将映射到巨大的页面,而TLB的缺失也很常见。
jthill '16

在单独的线程中(例如,使用OpenMP for循环)读取文件的不同部分可能会有一些好处,以便在一个线程停止等待输入时可能取得一些进展。但是另一方面,它可能会妨碍I / O调度,因此我只能建议尝试并进行测量!
Toby Speight

read()版本可能会受益于预读。
Barmar

1
@TobySpeight是的,多线程可能会加快速度。同样通过2 ^ 16查找表一次扫描两个字节,这提供了相当不错的上次我玩的速度。
PSkocik

18

您可以通过减少对的调用次数来改进@pskocik建议的解决方案read。有很多调用来读取BUFSIZ1Gb文件中的块。通常的方法是通过增加缓冲区大小:

  • 只是为了好玩,尝试将缓冲区大小增加10或100。在我的Debian 7上BUFSIZ是8192。对于原始程序,这是12万次读取操作。您可能可以提供1Mb的输入缓冲区,以将其减少100倍。
  • 为了获得更好的方法,应用程序可以分配一个与文件一样大的缓冲区,需要进行一次读取操作。这对于“小”文件已经足够好了(尽管有些读者的计算机上容量超过1Gb)。
  • 最后,您可以尝试使用内存映射的I / O,它可以这样处理分配。

在对各种方法进行基准测试时,您可能要记住,某些系统(例如Linux)将计算机上大部分未使用的内存用作磁盘缓存。前一段时间(大约在20年前,在vile FAQ中提到),我为一个(不是很好的)分页算法出乎意料的好结果而感到困惑,该算​​法已经开发出来,可以在文本编辑器中处理低内存条件。有人向我解释说,它运行得很快是因为程序正在使用用于读取文件的内存缓冲区工作,并且只有重新读取或写入文件时,速度才会有所不同。

同样适用于此mmap(在另一种仍在待办事项列表中并包含在FAQ中的情况下,开发人员在磁盘高速缓存是真正需要改进的情况下报告了很好的结果)。制定基准需要花费时间和精力来分析性能良好(或较差)的原因。

进一步阅读:


2
您高估了缓冲区大小超过特定阈值的影响。通常,将缓冲区大小增加到超过4KB-ish并没有太大帮助,实际上可能是有害的,因为这可能会将缓冲区推出L1缓存。在我的机器上dd,使用1MB缓冲区进行的测试要比8KB 。实际上,wc的8KB默认值选择得相当不错,对于大多数系统来说,它将接近最佳值。
marcelm'3
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.