我正在使用GCC 4.8和glibc 2.19在x86_64 Linux上。
在尝试不同的输入方法(另一个问题)时,我比较了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
代码如何避免这种代价?
我尝试搜索了一下,但最好的结果只是针对已锁定的realloc
调用的讨论,但我认为这并不适用于这里。我还在想fscanf
具有更好的内存局部性(反复使用同一缓冲区),但这并不能产生如此大的差异。
是否有人能够提供关于这种奇怪差异的任何见解?
fscanf
和sscanf
的完整代码。 - Kerrek SB_IO_vfscanf
的源代码。这个链接是我能找到的最好的,但那并不一定是glibc 2.19的。 - Kerrek SBsscanf
每次都会扫描到字符串的末尾吗?这将与存储在b
中的值相矛盾,该值具有预期值(即每次调用都会消耗一行输入)。 - Kerrek SB