Ich verwende GCC 4.8 und glibc 2.19 auf einem x86_64-Linux.
Beim Spielen mit verschiedenen Eingabemethoden für eine andere Frage habe ich verglichen fscanf
und sscanf
. Insbesondere würde ich entweder verwenden fscanf
auf der Standardeingabe direkt:
char s[128]; int n;
while (fscanf(stdin, "%127s %d", s, &n) == 2) { }
Oder ich würde zuerst die gesamte Eingabe in einen Puffer einlesen und dann den Puffer mit durchlaufen sscanf
. (Alles in den Puffer einzulesen dauert ein wenig.)
char s[128]; int n;
char const * p = my_data;
for (int b; sscanf(p, "%127s %d%n", s, &n, &b) == 2; p += b) { }
Zu meiner Überraschung, die fscanf
Fassung ist erheblich Schneller. Beispielsweise werden mehrere zehntausend Zeilen mit verarbeitet fscanf
dauert so lange:
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
Jetzt das gleiche mit 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
Ich habe die Google Perf-Tools verwendet, um dies zu messen. Zum Beispiel für 50000 Zeilen, die fscanf
Code erfordert etwa 50 Millionen Zyklen, und die sscanf
Code über 3300 Millionen Zyklen. Also habe ich die Top-Call-Sites mit aufgeschlüsselt perf record
/perf report
. Mit 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
Und mit 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
Also fast immer mit sscanf
darin verbracht wird rawmemchr
! Warum ist das? Wie kann die fscanf
Code diese Kosten vermeiden?
Ich habe versucht, danach zu suchen, aber das Beste, was ich finden konnte, ist diese Diskussion von gesperrt realloc
Anrufe, die meiner Meinung nach hier nicht zutreffen. Das habe ich mir auch gedacht fscanf
hat eine bessere Speicherlokalität (immer wieder denselben Puffer verwenden), aber das kann keinen so großen Unterschied machen.
Hat jemand irgendwelche Einblicke in diese seltsame Diskrepanz?
Vollständiger Code für:
fscanf
,sscanf
– Kerrek SB
29. Mai 2014 um 0:27 Uhr
Ich habe Probleme, den Quellcode für zu finden
_IO_vfscanf
. Dies ist das Beste, was ich finden konnte, aber das ist nicht unbedingt glibc 2.19.– Kerrek SB
29. Mai 2014 um 0:31 Uhr
Zeigen Sie die Schleifenverarbeitung – es sieht so aus, als hätten Sie eine “Schlemiel der Maler”-Problem.
– Michael Burr
29. Mai 2014 um 0:32 Uhr
@MichaelBurr: Ich habe den Testcode verlinkt und die Schleifen in der Frage gepostet. Denkst du
sscanf
scannt jedes Mal bis zum Ende der Zeichenfolge? Das würde dem gespeicherten Wert widersprechenb
die den erwarteten Wert hat (dh bei jedem Aufruf wird eine Eingabezeile verbraucht).– Kerrek SB
29. Mai 2014 um 0:33 Uhr
@MichaelBurr: Eigentlich denke ich, dass Michael Burr Recht hat, so scheint es
sscanf
sucht die gesamte Datei für die nachgestellte Null und parsen dann die drei gewünschten Variablen. Sehen Sie sich das Beispiel an linux.die.net/man/3/rawmemchr– Muhende Ente
29. Mai 2014 um 0:49 Uhr