改进D中的逐行I/O操作

Pau*_* M. 12 python io d

我需要以行方式处理大量的中型到大型文件(几百MB到GB),所以我对用于迭代线的标准D方法感兴趣.这个foreach(line; file.byLine())成语似乎符合要求,并且令人愉快,简洁易读,但性能似乎不太理想.

例如,下面是Python和D中的两个简单程序,用于迭代文件行并计算行数.对于~470 MB的文件(~3.6M行),我得到以下时间(最好的10个):

D次:

real    0m19.146s
user    0m18.932s
sys     0m0.190s
Run Code Online (Sandbox Code Playgroud)

Python时间(在EDIT 2之后,见下文):

real    0m0.924s
user    0m0.792s
sys     0m0.129s
Run Code Online (Sandbox Code Playgroud)

这是D版本,编译有dmd -O -release -inline -m64:

import std.stdio;
import std.string;

int main(string[] args)
{
  if (args.length < 2) {
    return 1;
  }
  auto infile = File(args[1]);
  uint linect = 0;
  foreach (line; infile.byLine())
    linect += 1;
  writeln("There are: ", linect, " lines.");
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

现在相应的Python版本:

import sys

if __name__ == "__main__":
    if (len(sys.argv) < 2):
        sys.exit()
    infile = open(sys.argv[1])
    linect = 0
    for line in infile:
        linect += 1
    print "There are %d lines" % linect
Run Code Online (Sandbox Code Playgroud)

编辑2:我改变了Python代码,使用了for line in infile下面评论中建议的更加惯用,从而为Python版本带来了更大的加速,现在已接近标准wc -l调用Unix wc工具的速度.

在D中我可能做错了什么的建议或指示,那就是表现如此糟糕?

编辑:为了比较,这里是一个D版本,它将byLine()成语抛出窗口并立即将所有数据吸收到内存中,然后将数据拆分为post-hoc行.这提供了更好的性能,但仍然比Python版本慢约2倍.

import std.stdio;
import std.string;
import std.file;

int main(string[] args)
{
  if (args.length < 2) {
    return 1;
  }
  auto c = cast(string) read(args[1]);
  auto l = splitLines(c);
  writeln("There are ", l.length, " lines.");
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

最后一个版本的时间如下:

real    0m3.201s
user    0m2.820s
sys     0m0.376s
Run Code Online (Sandbox Code Playgroud)

And*_*scu 13

编辑和TL; DR:此问题已在https://github.com/D-Programming-Language/phobos/pull/3089中解决.File.byLine从D 2.068开始,将提供改进的性能.

我在一个包含575247行的文本文件上尝试了您的代码.Python基线大约需要0.125秒.这是我的代码库,每个方法的注释中都嵌入了时序.解释如下.

import std.algorithm, std.file, std.stdio, std.string;

int main(string[] args)
{
  if (args.length < 2) {
    return 1;
  }
  size_t linect = 0;

  // 0.62 s
  foreach (line; File(args[1]).byLine())
    linect += 1;

  // 0.2 s
  //linect = args[1].readText.count!(c => c == '\n');

  // 0.095 s
  //linect = args[1].readText.representation.count!(c => c == '\n');

  // 0.11 s
  //linect = File(args[1]).byChunk(4096).joiner.count!(c => c == '\n');

  writeln("There are: ", linect, " lines.");
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

我用于dmd -O -release -inline每个变种.

第一个版本(最慢)一次读取一行.我们可以而且应该提高byLine的性能; 目前它被诸如byLine与其他C stdio操作的混合使用所困扰,这可能过于保守.如果我们废除它,我们可以轻松地进行预取等.

第二个版本一下子读取文件,然后使用标准算法用谓词计算行.

第三个版本承认没有必要考虑任何UTF细微之处; 计数字节也一样好,因此它将字符串转换为字节方式(免费),然后计算字节数.

最后一个版本(我最喜欢的)一次从文件中读取4KB的数据并使用它来懒散地展平它们joiner.然后它再次计算字节数.

  • @Veedrac:嗯,你是对的 - 被手中的微基准线搞定了.我只是看看事情,男孩可以改进代码.请参阅https://github.com/D-Programming-Language/phobos/pull/3089.在相同的测试条件下,byLine版本现在需要0.136秒. (3认同)