Forum: PC-Programmierung c++: Mit std::chrono ermittelte Zeit für Performance-Vergleich schwankt / variiert


von PerformanceHeini (Gast)


Lesenswert?

Hallo allerseits,

ich habe da ein Problem:
Also ganz kurz: Ich habe mehrere Versionen einer Klasse geschrieben, 
deren Performance ich vergleichen möchte. Dazu werden sie jeweils in 
einer Schleife "genutzt". Aber alle in einem gemeinsamen Programm.

Also quasi
1
zeitmessung_Version_1(); // mal bin ich schneller
2
zeitmessung_Version_2(); // mal ich
3
zeitmessung_Version_3(); // und mal ich
4
...

Lasse ich das Programm laufen, gibt es, pro run, aber immer mal einen 
anderen "Gewinner", da die gemessenen Zeiten schwanken und zwar nicht 
etwa gemeinsam, sondern so, dass mal die eine Version, mal die andere 
Version, schneller ist.
(Eine mehrfache Messung pro Version ergibt allerdings untereinander 
konstante Zeiten)
Etwa bei:
1
zeitmessung_Version_1(); // == t1
2
zeitmessung_Version_2(); // == t2
3
zeitmessung_Version_3(); // == t3
4
...
5
6
zeitmessung_Version_1(); // == t1
7
zeitmessung_Version_2(); // == t2
8
zeitmessung_Version_3(); // == t3
9
...


Kann mir das bitte jemand erklären?! Danke schon mal - ich dreh solange 
durch.:)

MfG

von Sebastian V. (sebi_s)


Lesenswert?

Ohne deine Klasse zu sehen kann man das schwer beantworten. Performance 
Unterschiede kann alle möglichen und unmöglichen Gründe haben. Das kann 
an an Memory Alignment oder bei Multithreaded Sachen sowas wie False 
Sharing liegen. Je Programmaufruf liegen die Daten dann mal mehr oder 
weniger günstig im Speicher sodass sich verschiedene Ergebnisse ergeben.

von Irgend W. (Firma: egal) (irgendwer)


Lesenswert?

PerformanceHeini schrieb:
> Kann mir das bitte jemand erklären?! Danke schon mal - ich dreh solange
> durch.:)

Die Funktion liefert keine beliebig genau Zeit. Je nachdem wo du in 
deren Zeitraster anfängst und aufhörst bekommst du total 
unterschiedliche Differenzwerte. Versuche mal heraus zu bekommen wie die 
Auflösung bei deinem System überhaupt ist.

Versuche die Zeit von eine Handvoll Befehle zu ermitteln gehen meist 
Gründlich schief. Daher sollte man solche Zeitmessungen immer so in eine 
Schleife Packen das jeder Test mindestens einige Sekunden dauert damit 
eben Messungenauigkeiten und auch schwankungen durch externe Einflüsse 
minimiert werden.

von Rolf M. (rmagnus)


Lesenswert?

Auch wichtig wäre, welche Uhr verwendet wird und in welchem Bereich die 
gemessenen Zeiten liegen. Extrem kurze Zeitmessungen können sehr ungenau 
werden.

von PerformanceHeini (Gast)


Lesenswert?

Ist jetzt etwas umständlich, eine Testklasse zusammenzubasteln. Aber die 
Versionen unterscheiden sich teilweise nur darin, dass
1
 class V1{
2
  A* a;
3
  B* b;
4
  ... a == reinterpret_cast<A*>(b) ... // Version 1
5
 }
6
 class V2{
7
  B* b;
8
  A* a;
9
  ... b == reinterpret_cast<B*>(a) ... // Version 2
10
 }
11
  //etc

Also Versionen mit eig. identischem Speicherlayout.
..Was eigentlich gar keinen Unterschied machen sollte. Und selbst da 
ergeben sich manchmal(!) deutliche Unterschiede. Manchmal ist es aber 
auch identisch.

Wird mit -O3 etc kompiliert.

"alignas(16)" an die Klassen macht auch keinen Unterschied.

Wird alles jeweils für ca ne halbe Sekunde iteriert und mit 
"chrono::high_resolution_clock", oder "chrono::system_clock" gemessen.

Gibts einen anderen, zuverlässigen Weg, verschiedene Versionen zu 
vergleichen?

von Oliver S. (oliverso)


Lesenswert?

Weil es in dem Fall wichtig ist: Windows?

Oliver

von Mombert H. (mh_mh)


Lesenswert?

PerformanceHeini schrieb:
> Ist jetzt etwas umständlich, eine Testklasse zusammenzubasteln. Aber die
> Versionen unterscheiden sich teilweise nur darin, dass

Es ist etwas umständlich dir zu helfen, wenn wir nichtmal wissen um 
welche Zeitskalen es geht oder was und wie gemessen wird. Unterscheiden 
sich die zeiten um Nanosekunden oder Minuten? Versuchst du ein memcpy 
oder das Parsen einer csv-Datei zu timen? Hat deine unbekannte CPU 1 
oder 128 CPU-Kerne? Welches OS? Läuft nebenbei ein Browser oder ein 
Fileindexer? ...

von cppbert3 (Gast)


Lesenswert?

PerformanceHeini schrieb:
> Hallo allerseits,
> ich habe da ein Problem:
> Also ganz kurz: Ich habe mehrere Versionen einer Klasse geschrieben,
> deren Performance ich vergleichen möchte. Dazu werden sie jeweils in
> einer Schleife "genutzt". Aber alle in einem gemeinsamen Programm.
> Also quasi
> zeitmessung_Version_1(); // mal bin ich schneller
> zeitmessung_Version_2(); // mal ich
> zeitmessung_Version_3(); // und mal ich
> ...
>
> Lasse ich das Programm laufen, gibt es, pro run, aber immer mal einen
> anderen "Gewinner", da die gemessenen Zeiten schwanken und zwar nicht
> etwa gemeinsam, sondern so, dass mal die eine Version, mal die andere
> Version, schneller ist.
> (Eine mehrfache Messung pro Version ergibt allerdings untereinander
> konstante Zeiten)
> Etwa bei:
> zeitmessung_Version_1(); // == t1
> zeitmessung_Version_2(); // == t2
> zeitmessung_Version_3(); // == t3
> ...
> zeitmessung_Version_1(); // == t1
> zeitmessung_Version_2(); // == t2
> zeitmessung_Version_3(); // == t3
> ...
>
> Kann mir das bitte jemand erklären?! Danke schon mal - ich dreh solange
> durch.:)
> MfG

1. Dein Testcode darf nicht wegoptimierbar sein
2. Dein Test darf nicht zu kurz sein, lieber 30sek bis 1Min, sonst ist 
das alles ungenau mit der Zeitmessung
3. Releasebuild sonst kommt noch Debug Heap, Pruefungen dazu
4. Keinen trivialkram testen der im Zeitverhalten im Rauscheb untergeht
5. Profiler wie z.B. VTune verwenden wenn es um Details geht

von PerformanceHeini (Gast)


Lesenswert?

Danke schon mal für die Tipps.

Mit VTune, oder Profilern überhaupt, habe ich mich noch gar nicht 
beschäftigt. Muss ich mal reinschauen.

Läuft auf Linux. Ich hab nur gehört, dass der Zeitegeber von Linux 
genauer sein soll!?

Genügt eine volatile gegen das Wegoptimieren?


Es ist jetzt so, dass zwei Versionen jeweils die gleiche binary erzeugen 
und die dritte nicht. Also wenn man sie nicht zusammen in einem 
Test-Programm verwurstet, sonder einzeln.

Aber auch wenn man sie alle drei in ein Programm packt, verhalten sie 
sich so, wie als würde man die "Einzelprogramme" aufrufen.

Ich würde erwarten, dass unterschiedlicher Code, der aber das gleiche 
Einzelprogramm ergibt, sich wenigstens als Teilprogramm identisch 
verhält (wenn schon nicht pro Programmaufruf).
Wenn ich mir also das Programm aus "Teil 1/3", "Teil 2/3" und "Teil 3/3" 
vorstelle, und "Teil 1/3" == "Teil 2/3" (als Einzelprogramm kompiliert 
zumindest) gilt, warum wird dann identischer Code nicht identisch 
ausgeführt und ergibt identische Ergebnisse??

Sowas kommt z.B. raus (bei ein paar 10^6 Iterationen):
1
// ein run:
2
V1:
3
10170.8ms
4
10163.7ms
5
10149.1ms
6
10146.1ms
7
V2:
8
10434.1ms
9
10428.9ms
10
10422.7ms
11
10405.9ms
12
V3:
13
10595.4ms
14
10601.2ms
15
10601.1ms
16
10594.3ms
17
18
N/2:
19
V1:
20
5058.17ms
21
4964.43ms
22
5029.02ms
23
5036.99ms
24
V2:
25
4949.92ms
26
4968.27ms
27
4956.48ms
28
4952.95ms
29
V3:
30
5041.79ms
31
5056.29ms
32
5047.69ms
33
5043.88ms
34
35
// noch ein run:
36
V1:
37
10340.5ms
38
10371.4ms
39
10326.6ms
40
10216.4ms
41
V2:
42
10438.4ms
43
10437.9ms
44
10440.6ms
45
10418.4ms
46
V3:
47
10299.8ms
48
10279.7ms
49
10276ms
50
10302.4ms
51
52
N/2:
53
V1:
54
5179.37ms
55
5185.96ms
56
5150.43ms
57
5183.73ms
58
V2:
59
4972.21ms
60
4968.96ms
61
4975.42ms
62
4972.18ms
63
V3:
64
5057.03ms
65
5042.42ms
66
5052.62ms
67
5056.56ms

von Georg A. (georga)


Lesenswert?

PerformanceHeini schrieb:
> warum wird dann identischer Code nicht identisch
> ausgeführt und ergibt identische Ergebnisse??

Weil die Umgebungsbedingungen nicht gleich sind. Da können anderen 
Prozesse dazwischen laufen, der Cache ist anders, die Speicherzuordnung 
(Verteilung auf DRAM-Bänke) ist anders, etc. Gegen andere Prozesse hilft 
das Verpacken in Realtime (sched_setscheduler), das dürfte das viel von 
deinem Rauschen schon entfernen. Der Rest ist schwieriger bis unmöglich 
gleichzuhalten.

von Mikro 7. (mikro77)


Lesenswert?

PerformanceHeini schrieb:
> Sowas kommt z.B. raus (bei ein paar 10^6 Iterationen):
> ...

Das sehe ich Unterschiede in der Größenordnung von ca. 5%. Das sind imho 
(auch in Bezug auf die Messmethode, siehe unten) keine signifikanten 
Unterschiede. Wenn du den gleichen Testfalle mehrfach laufen läßt, 
liegst du doch schon im Bereich dieser Unsicherheit, oder?!

PerformanceHeini schrieb:
> Wird alles jeweils für ca ne halbe Sekunde iteriert und mit
> "chrono::high_resolution_clock", oder "chrono::system_clock" gemessen.

Ich sehe gerade nicht, ob die STL auch per Process/Thread Zeiten 
unterstüzt. Anyway, die Funktionen basieren idR auf clock_gettime(). 
Dort gibt es dann auch CLOCK_PROCESS_CPUTIME_ID / 
CLOCK_THREAD_CPUTIME_ID die möglicherweise für deine Messung besser 
geeignet sind.

von Rolf M. (rmagnus)


Lesenswert?

PerformanceHeini schrieb:
> Wenn ich mir also das Programm aus "Teil 1/3", "Teil 2/3" und "Teil 3/3"
> vorstelle, und "Teil 1/3" == "Teil 2/3" (als Einzelprogramm kompiliert
> zumindest) gilt, warum wird dann identischer Code nicht identisch
> ausgeführt und ergibt identische Ergebnisse??

Bei allem, was etwas komplexer als ein AVR-µC ist, gibt es neben den 
reinen Taktzyklen der Instruktionen noch viele weitere Effekte, die die 
Laufzeit beeinflussen, vor allem auf einem Multitasking-OS. Beim PC gibt 
es z.B. Caches, Branch Prediction, eine superskalare Architektur, 
dynamische Taktung, Multicore-CPU u.s.w.
Beim Multitasking-Betriebssystem kommt noch das Scheduling und die 
Speichervirtualisierung hinzu. Und die Uhr, mit der du misst, kann auch 
einen sehr großen Unterschied machen. Da wirst du nie identische Werte 
bekommen.

von M. Н. (Gast)


Lesenswert?

Ein sehr schöner Talk zum Thema Performance und worauf es wirklich 
ankommt. Lohnt sich.

https://youtu.be/r-TLSBdHe1A

Ist allerdings Englisch

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.