[入门] 初学 D,大家帮忙看看这两个小程序为什么很慢
rainux
2009-01-08
这篇文章提出了一个小算法问题,将一个文本文件的重复行去掉。文中给出了 78000 行和 780000 行两个不同大小的测试数据文件。
下面这段类 C 风格的代码是我在回复里看到的,稍加了修改使其能被 DMD 1.030 编译运行。在我机器上处理 78000 行的文件需要 81ms。这个程序我无法理解的是为什么代码里注释处必须要使用一个临时变量?如果不使用这个临时变量将会导致处理同样的文件需要 397ms。 import std.stdio; import std.string; import std.perf; int main(char[][] argv) { if (argv.length < 3) { writefln("Usage: %s <infile> <outfile>", argv[0]); return 1; } const int READ_SIZE = 1024; FILE* fin = fopen(cast(char*)argv[1], "r"); FILE* fout = fopen(cast(char*)argv[2], "w"); char buffer[READ_SIZE]; int[char[]] emails; PerformanceCounter counter = new PerformanceCounter(); counter.start(); while (!feof(fin)){ fgets(cast(char*)buffer, READ_SIZE, fin); // 此处若不使用临时变量 email,会导致性能大幅度降低 char[] email = toString(cast(char*)buffer); if (!(email in emails)) { emails[email] = 1; fputs(cast(char*)email, fout); } } fclose(fout); fclose(fin); counter.stop(); writefln("%dms used", counter.milliseconds()); return 0; } 我觉得既然 Python、Ruby 版本都可以通过一次性读入整个文件内容来获得一些性能提升,那么 D 是不是也可以呢?于是写了个 Phobos 的版本,没想到这个版本处理 78000 行的文件需要 215ms,处理 780000 行数据竟然达到了夸张的 31208ms,Ruby 的版本处理 780000 也只需要 3579ms 而已。 import std.stdio; import std.string; import std.file; import std.perf; int main(char[][] argv) { if (argv.length < 3) { writefln("Usage: %s <infile> <outfile>", argv[0]); return 1; } int[char[]] emails; PerformanceCounter counter = new PerformanceCounter(); counter.start(); char[][] lines = splitlines(cast(char[])read(argv[1])); foreach (char[] line; lines) { emails[line] = 0; } write(argv[2], join(emails.keys ~ [""], "\n")); counter.stop(); writefln("%dms used", counter.milliseconds()); return 0; } |
|
oldrev
2009-01-08
逻辑有问题,每次读入1k文本却不用,还作了个 1k 指针到字符串的转换....
披着D外衣的C程序当然很慢,用D就按D的规则来,别用那些乱七八糟的fgets 和字符串指针之类,用 std.stream 里的 BufferedStream 和 File 类来操作文本文件 |
|
rainux
2009-01-09
oldrev 写道 逻辑有问题,每次读入1k文本却不用,还作了个 1k 指针到字符串的转换....
披着D外衣的C程序当然很慢,用D就按D的规则来,别用那些乱七八糟的fgets 和字符串指针之类,用 std.stream 里的 BufferedStream 和 File 类来操作文本文件 第一段类 C 风格的代码我也不喜欢,但是它确实很高效。现在才看明白这行代码 char[] email = toString(cast(char*)buffer); 是为了将固定 1k 长的 buffer 缩短为字符串实际长度,否则在下面将其用作关联数组的 key 时运算量大了很多,当然会慢。 现在改成了直接输出到一个 BufferedFile 里,果然效率可以接近类 C 风格的版本了,谢谢 oldrev。 补充: 其实我是换用了最新的 DMD 1.039 才可以获得较好的效率(780000 行的文件需要 3315ms,跟 Ruby 版本差不多),DMD 1.030 编译出的版本虽然有提高,但仍然很慢(14083ms)。但是这个优化后的 Python 版本只需要 1047ms,虽然它的算法不太一样。 import std.stdio; import std.string; import std.file; import std.stream; import std.perf; int main(char[][] argv) { if (argv.length < 3) { writefln("Usage: %s <infile> <outfile>", argv[0]); return 1; } int[char[]] emails; PerformanceCounter counter = new PerformanceCounter(); counter.start(); char[][] lines = splitlines(cast(char[])read(argv[1])); BufferedFile fout = new BufferedFile(argv[2], FileMode.OutNew); foreach (char[] line; lines) { if (!(line in emails)) { emails[line] = 0; fout.writeLine(line); } } fout.close; counter.stop(); writefln("%dms used", counter.milliseconds()); return 0; } |
|
rainux
2009-01-09
原来直接在 Stream 对象上迭代更快,现在可以达到 1050ms,跟优化后的 Python 版本差不多了。
import std.stdio; import std.stream; import std.perf; int main(char[][] argv) { if (argv.length < 3) { writefln("Usage: %s <infile> <outfile>", argv[0]); return 1; } int[char[]] emails; PerformanceCounter counter = new PerformanceCounter(); counter.start(); Stream fin = new BufferedFile(argv[1]); Stream fout = new BufferedFile(argv[2], FileMode.OutNew); foreach (char[] line; fin) { if (!(line in emails)) { emails[line] = 0; fout.writeLine(line); } } fin.close(); fout.close(); counter.stop(); writefln("%dms used", counter.milliseconds()); return 0; } |
|
oldrev
2009-01-09
楼上这个才是 D 程序
|
|
rainux
2009-01-09
多谢楼上指点
|
|
d_world
2009-02-20
呵呵。。。
|
|
codekitten
2009-02-20
一切D国主义都是纸老虎:)
|