2016-04-05 4 views
1

Ich bin nicht sicher, ob Stackoverflow das beste Forum ist, aber hier geht es ...was können große Unterschiede in der Laufzeit eines multithreaded Code verursachen

Wir haben unsere Software Benchmarking. Plötzlich sahen wir eine enorme Leistungsverschlechterung. Um zu testen, was passiert, haben wir die Software mehrmals am gleichen Eingang laufen lassen. Die Ergebnisse wurden verblüffende (enorme Leistungsvariabilität):

Solution time = 9.69 sec. 
Solution time = 7.55 sec. 
Solution time = 4.78 sec. 
Solution time = 5.12 sec. 
Solution time = 6.94 sec. 
Solution time = 2.15 sec. 
Solution time = 5.48 sec. 

Unsere Software multithreaded, beginnen genau einen Thread auf jedem Kern (Hyperthreading deaktiviert) eine 12-Core-Maschine. Es läuft sonst nichts auf der Maschine. Vor dem Wochenende hatten wir noch nie so eine Laufzeitvariation.

Aus einer Laune heraus, wiederholten wir den Test mit CPU-Bindung aktiviert (das heißt, an einen anderen Kern jedes der 12 Fäden Pin):

Solution time = 0.95 sec. 
Solution time = 0.95 sec. 
Solution time = 0.95 sec. 
Solution time = 0.95 sec. 
Solution time = 0.94 sec. 
Solution time = 0.95 sec. 
Solution time = 0.95 sec. 

An dieser Stelle habe ich keine Ahnung, was dieses verursachen könnte. Soweit wir wissen, hat sich an der Konfiguration der Maschine nichts geändert (RHEL 6.6). Ich würde für jeden Vorschlag dankbar ...

Danke, --Laci

EDIT:

nur noch einmal zu betonen: in der Vergangenheit der nicht gebundene Code Variation tat Ausstellung, aber es war in der Größenordnung von höchstens 10-15%, und im Durchschnitt war es sehr nah an dem cpu-gebundenen Code (innerhalb von 1-2%). Es ist erst seit dem letzten Wochenende, als wir begannen, diese Veränderung zu sehen, und soweit wir wissen, hat sich in der Umwelt nichts geändert. Aber (offensichtlich) muss sich etwas geändert haben, und ich frage mich, was es hätte sein können.

EDIT2:

lief ich den Code durch perf (mit 10 Wiederholungen), und das ist, was ich bekam.

mit dem CPU-Bindung:

15713.138442 task-clock-msecs   #  9.341 CPUs (+- 0.037%) 
      6958 context-switches   #  0.000 M/sec (+- 0.357%) 
      11 CPU-migrations   #  0.000 M/sec (+- 1.786%) 
      49147 page-faults    #  0.003 M/sec (+- 0.514%) 
    45890046261 cycles     # 2920.489 M/sec (+- 0.030%) 
    51929307378 instructions    #  1.132 IPC  (+- 0.021%) 
    11050565282 branches     # 703.269 M/sec (+- 0.032%) 
     446256370 branch-misses   #  4.038 %  (+- 0.003%) 
     421789915 cache-references   #  26.843 M/sec (+- 0.048%) 
     18989944 cache-misses    #  1.209 M/sec (+- 0.305%) 

    1.682190890 seconds time elapsed (+- 0.131%) 

ohne CPU-bindenden:

36219.945761 task-clock-msecs   #  5.677 CPUs (+- 3.978%) 
      8742 context-switches   #  0.000 M/sec (+- 1.677%) 
      34 CPU-migrations   #  0.000 M/sec (+- 5.243%) 
      48799 page-faults    #  0.001 M/sec (+- 0.839%) 
    106384797638 cycles     # 2937.188 M/sec (+- 3.989%) 
    93465235493 instructions    #  0.879 IPC  (+- 3.085%) 
    23685574664 branches     # 653.937 M/sec (+- 3.672%) 
     477076300 branch-misses   #  2.014 %  (+- 0.563%) 
     414008416 cache-references   #  11.430 M/sec (+- 0.189%) 
     17910783 cache-misses    #  0.495 M/sec (+- 1.468%) 

    6.380128466 seconds time elapsed (+- 5.171%) 

beachte, dass die Code-deterministisch ist, das heißt, es nimmt immer den gleichen Ausführungspfad. Es ist jedoch möglich, dass ein Thread beschäftigt ist und darauf wartet, dass er mit einem globalen deterministischen Status synchronisiert wird. Aber warum sollte das einen so großen Unterschied in Zyklen/Anweisungen/etc ... verursachen?

Beachten Sie auch, dass ich versucht habe, die Threads an die Kerne in zufälliger Reihenfolge anzuheften, um die Hypothese zu testen, die sie zu den Kernen in die Reihenfolge ihrer Entstehung macht einen Unterschied. Aber das machte keinen Unterschied, es war immer noch schnell.

+0

Obwohl der Zeitunterschied dramatisch ist, vermute ich, dass die Datenlokalität von entscheidender Bedeutung ist und die Threadmigration über die Kerne hinweg das verursacht. Ich frage mich, ob Ihr Algorithmus den L1- und L2-Cache in der CPU maximal nutzt. Können Sie einen Einblick geben, was dieser Algorithmus bewirkt? – ekarak

+0

Sind Sie sicher, dass Ihr Code reentrant ist? Irgendwelche Mutexe, irgendwelche geteilten Strukturen, die aktualisiert werden müssen? – ekarak

+0

Es ist eine gemischte Ganzzahl Programmierung Löser. Und ja, Cachespeicherort spielt eine große Rolle (leider bekommen wir nur etwa 90% Cache-Treffer-Verhältnis, und es sieht nicht so aus, als wäre es möglich, das zu verbessern). Bis zum letzten Wochenende betrug der Unterschied zwischen dem nicht gebundenen und dem cpu-gebundenen Code 1-2%, und die maximale Variation, die wir im nicht-gebundenen Code beobachtet haben, betrug ~ 10-15%. Immerhin ist die Maschine nicht geladen, der Kernel hat keinen Grund, die Threads häufig zu migrieren. Und ja, der Code ist Re-entrant, und soweit Helgrind kann sagen, es ist richtig. – LaszloLadanyi

Antwort

2

Das Problem ist gelöst. Dem Vorschlag von Ekarak folgend, habe ich Profiling durchgeführt und festgestellt, dass die Zeit verschwendet wird, wenn unsere Threads interagieren und aufeinander warten. Ich habe die Maschine neu gestartet, und dann ging alles wieder normal.

Jetzt haben wir 40 Maschinen in einem Cluster und alle zeigten dieses Verhalten. Dies bedeutet, dass entweder ein externer Einfluss oder die Betriebszeit der Maschinen eine Rolle gespielt hat. Ich habe nach "linux uptime 209" gegoogelt und das ist aufgetaucht, dass im Kernel, den diese Maschinen laufen, ein Überlauf-Bug in sched_clock(), der nach 208,5 Tagen ausgelöst wurde.

Also ... haben wir Probleme im Zusammenhang mit Thread-Interaktion auf Maschinen, die für 209 Tage waren und es gibt einen Fehler in sched_clock(), der nach 208,5 Tagen manifestiert. Das ist einfach zu viel Zufall für mich, also habe ich festgestellt, dass dies unser Problem ist.

Verwandte Themen