Я использую GCC 4.8 и glibc 2.19 на Linux x86_64.
Во время игры с различными методами ввода для другого вопроса я сравнивал 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
намного быстрее. Например, обработка seveal десятков тысяч строк с 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 perf для измерения этого. Например, для 50000 строк код fscanf
требует около 50 М циклов, а код sscanf
- около 3300 М циклов. Поэтому я сломал верхние сайты вызовов с помощью 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
вызовов, t думаю здесь. Я также думал, что fscanf
имеет лучшую локальность памяти (используя один и тот же буфер снова и снова), но это не может сделать такую большую разницу.
Есть ли у кого-нибудь идеи в этом странном несоответствии?