Perf überzählen einfache CPU-gebundene Schleife: mysteriöse Kernel-Arbeit?
Ich benutze Linux perf für einige Zeit, um Anwendungsprofile zu erstellen. In der Regel ist die Anwendung mit Profil ziemlich komplex, daher werden die gemeldeten Zählerwerte in der Regel einfach als Nennwert verwendet, sofern kein @ vorhanden isbrutt Diskrepanz mit dem, was Sie aufgrund erster Prinzipien erwarten könnten.
In letzter Zeit habe ich jedoch einige einfache 64-Bit-Assembly-Programme vorgestellt - triival genug, dass man den erwarteten Wert verschiedener Leistungsindikatoren fast genau berechnen kann, und es scheint, dassperf stat
zählt zu viel.
Nehmen Sie zum Beispiel die folgende Schleife:
.loop:
nop
dec rax
nop
jne .loop
Dies wird einfach eine Schleifen
mal, won
ist der Anfangswert vonrax
. Jede Iteration der Schleife führt 4 Anweisungen aus, sodass Sie @ erwarten würde4 * n
ausgeführte Anweisungen, plus einige kleine Fixkosten für das Starten und Beenden von Prozessen und das kleine Stück Code, das @ setn
vor dem Betreten der Schleife.
Hier ist das (typische)perf stat
Ausgabe fürn = 1,000,000,000
:
~/dev/perf-test$ perf stat ./perf-test-nop 1
Performance counter stats for './perf-test-nop 1':
301.795151 task-clock (msec) # 0.998 CPUs utilized
0 context-switches # 0.000 K/sec
0 cpu-migrations # 0.000 K/sec
2 page-faults # 0.007 K/sec
1,003,144,430 cycles # 3.324 GHz
4,000,410,032 instructions # 3.99 insns per cycle
1,000,071,277 branches # 3313.742 M/sec
1,649 branch-misses # 0.00% of all branches
0.302318532 seconds time elapsed
Huh. Anstelle von ungefähr 4.000.000.000 Anweisungen und 1.000.000.000 Zweigen sehen wir mysteriöse zusätzliche 410.032 Anweisungen und 71.277 Zweige. Es gibt immer "zusätzliche" Anweisungen, aber die Menge variiert ein bisschen - nachfolgende Läufe hatten zum Beispiel 421K, 563K und 464Kextr Anweisungen jeweils. Sie können dies auf Ihrem System selbst ausführen, indem Sie my @ erstellesimple github project.
OK, Sie können also davon ausgehen, dass es sich bei diesen wenigen hunderttausend zusätzlichen Anweisungen nur um feste Installations- und Abbaukosten für Anwendungen handelt (das Userland-Setup lautetsehr klei, aber es könnte versteckte Sachen geben). Lass uns versuchen fürn=10 billion
dann
~/dev/perf-test$ perf stat ./perf-test-nop 10
Performance counter stats for './perf-test-nop 10':
2907.748482 task-clock (msec) # 1.000 CPUs utilized
3 context-switches # 0.001 K/sec
0 cpu-migrations # 0.000 K/sec
2 page-faults # 0.001 K/sec
10,012,820,060 cycles # 3.443 GHz
40,004,878,385 instructions # 4.00 insns per cycle
10,001,036,040 branches # 3439.443 M/sec
4,960 branch-misses # 0.00% of all branches
2.908176097 seconds time elapsed
Jetzt gibt es ~ 4,9 Millionenextr -Anweisungen, etwa 10-fache Zunahme gegenüber zuvor, proportional zur 10-fachen Zunahme der Schleifenzahl.
Sie können verschiedene Zähler ausprobieren - alle CPU-bezogenen zeigen ähnliche proportionale Erhöhungen. Konzentrieren wir uns dann auf die Anzahl der Anweisungen, um die Dinge einfach zu halten. Verwendung der:u
und:k
Suffixe zum MessenBenutze und kernel counts zeigt an, dass im kernel Konto für fast alle zusätzlichen Ereignisse:
~/dev/perf-test$ perf stat -e instructions:u,instructions:k ./perf-test-nop 1
Performance counter stats for './perf-test-nop 1':
4,000,000,092 instructions:u
388,958 instructions:k
0.301323626 seconds time elapsed
Bingo. Von den 389.050 zusätzlichen Anweisungen entfielen 99,98% (388.958) auf den Kernel.
OK, aber wo bleibt uns das? Dies ist eine triviale CPU-gebundene Schleife. Es werden keine Systemaufrufe durchgeführt und es wird nicht auf den Speicher zugegriffen (wodurch der Kernel indirekt über den Seitenfehlermechanismus aufgerufen werden kann). Warum führt der Kernel Anweisungen für meine Anwendung aus?
Es scheint nicht durch Kontextwechsel oder CPU-Migrationen verursacht zu werden, da diese bei oder nahe Null liegen und auf jeden Fall dasextrie Anzahl der @ -Anweisungen korreliert nicht mit Läufen, bei denen mehr dieser Ereignisse auftraten.
Die Anzahl der zusätzlichen Kernel-Anweisungen ist in der Tat mit der Schleifenzahl sehr glatt. Hier ist ein Diagramm mit (Milliarden von) Schleifeniterationen im Vergleich zu Kernel-Anweisungen:
Sie können sehen, dass die Beziehung so ziemlich perfekt linear ist - tatsächlich gibt es bis zu 15e9-Iterationen nur einen Ausreißer. Danach scheinen zwei separate Linien zu existieren, die auf eine Art Quantisierung dessen hindeuten, was die überschüssige Zeit verursacht. In jedem Fall fallen für jede 1e9-Anweisung, die in der Hauptschleife ausgeführt wird, etwa 350.000 Kernel-Anweisungen an.
Zum Schluss habe ich bemerkt, dass die Anzahl der ausgeführten Kernel-Befehle proportional zu @ zu sein scheinLaufzei1 (oder CPU-Zeit) statt ausgeführter Anweisungen. Um dies zu testen, benutze ich einähnliches Programm, aber mit einem dernop
Anweisungen durch ein @ ersetidiv
mit einer Latenz von ca. 40 Zyklen (einige uninteressante Zeilen entfernt):
~/dev/perf-test$ perf stat ./perf-test-div 10
Performance counter stats for './perf-test-div 10':
41,768,314,396 cycles # 3.430 GHz
4,014,826,989 instructions # 0.10 insns per cycle
1,002,957,543 branches # 82.369 M/sec
12.177372636 seconds time elapsed
Hier haben wir ~ 42e9 Zyklen benötigt, um 1e9 Iterationen durchzuführen, und wir hatten ~ 14.800.000 zusätzliche Anweisungen. Das ist vergleichbar mit nur ~ 400.000 zusätzlichen Anweisungen für die gleichen 1e9-Schleifen mitnop
. Wenn wir mit dem @ vergleichnop
Schleife, die ungefähr die gleiche Anzahl von @ nimcycles
(40e9-Iterationen) sehen wir fast genau die gleiche Anzahl zusätzlicher Anweisungen:
~/dev/perf-test$ perf stat ./perf-test-nop 41
Performance counter stats for './perf-test-nop 41':
41,145,332,629 cycles # 3.425
164,013,912,324 instructions # 3.99 insns per cycle
41,002,424,948 branches # 3412.968 M/sec
12.013355313 seconds time elapsed
Was ist los mit dieser mysteriösen Arbeit im Kernel?
1 Hier verwende ich die Begriffe "Zeit" und "Zyklen" mehr oder weniger austauschbar. Die CPU läuft während dieser Tests auf Hochtouren, daher modulieren einige turboaufladungsbedingte thermische Effekte die Zyklen direkt proportional zur Zeit.