【发布时间】:2014-07-18 09:40:20
【问题描述】:
我在 x86_64 Linux 上使用 GCC 4.8 和 glibc 2.19。
在为a different question 使用不同的输入法时,我比较了fscanf 和sscanf。具体来说,我会直接在标准输入上使用fscanf:
char s[128]; int n;
while (fscanf(stdin, "%127s %d", s, &n) == 2) { }
或者我会先将整个输入读入缓冲区,然后使用sscanf 遍历缓冲区。 (将所有内容读入缓冲区需要很短的时间。)
char s[128]; int n;
char const * p = my_data;
for (int b; sscanf(p, "%127s %d%n", s, &n, &b) == 2; p += b) { }
令我惊讶的是,fscanf 版本的速度大大快了。例如,用fscanf 处理几万行需要这么长时间:
10000 0.003927487 seconds time elapsed
20000 0.006860206 seconds time elapsed
30000 0.007933329 seconds time elapsed
40000 0.012881912 seconds time elapsed
50000 0.013516816 seconds time elapsed
60000 0.015670432 seconds time elapsed
70000 0.017393129 seconds time elapsed
80000 0.019837480 seconds time elapsed
90000 0.023925753 seconds time elapsed
现在与sscanf 相同:
10000 0.035864643 seconds time elapsed
20000 0.127150772 seconds time elapsed
30000 0.319828373 seconds time elapsed
40000 0.611551668 seconds time elapsed
50000 0.919187459 seconds time elapsed
60000 1.327831544 seconds time elapsed
70000 1.809843039 seconds time elapsed
80000 2.354809588 seconds time elapsed
90000 2.970678416 seconds time elapsed
我使用 Google 性能工具来衡量这一点。例如,对于 50000 行,fscanf 代码需要大约 50M 周期,sscanf 代码大约需要 3300M 周期。所以我用perf record/perf report 分解了热门呼叫站点。与fscanf:
35.26% xf libc-2.19.so [.] _IO_vfscanf
23.91% xf [kernel.kallsyms] [k] 0xffffffff8104f45a
8.93% xf libc-2.19.so [.] _int_malloc
还有sscanf:
98.22% xs libc-2.19.so [.] rawmemchr
0.68% xs libc-2.19.so [.] _IO_vfscanf
0.38% xs [kernel.kallsyms] [k] 0xffffffff8104f45a
所以几乎所有sscanf 的时间都花在rawmemchr 上!为什么是这样? fscanf 代码如何避免这种成本?
我尝试搜索此内容,但我能想到的最好的结果是 this discussion 锁定的 realloc 调用,我认为这里不适用。我还认为fscanf 具有更好的内存局部性(一遍又一遍地使用相同的缓冲区),但这不会产生如此大的差异。
有人对这种奇怪的差异有任何见解吗?
【问题讨论】:
-
我找不到
_IO_vfscanf的源代码。 This 是我能找到的最好的,但不一定是 glibc 2.19。 -
显示循环处理 - 看起来你有一个"Schlemiel the Painter" problem。
-
@MichaelBurr:我链接了测试代码,并在问题中发布了循环。你认为
sscanf每次都会扫描到字符串的末尾吗?这将与存储在b中的值相矛盾,后者具有预期值(即,每次调用都会消耗一行输入)。 -
@MichaelBurr:其实我觉得Michael Burr 是对的,看来
sscanf正在整个文件 中搜索尾随的null,然后解析出三个变量你要。看linux.die.net/man/3/rawmemchr上的例子
标签: c performance glibc scanf