Hyper-Threading: Optimierungen und Fallen

Pingpong-Thread-Wechsel

Als erstes Beispiel zur Synchronisation sollen zwei Threads betrachtet werden, die nur abwechselnd arbeiten dürfen. Wenn Thread 1 arbeitet, muss Thread 2 warten und umgekehrt. Handelt es sich dabei nur um kurze Code-Abschnitte, nimmt man die Synchronisation am besten selbst in die Hand und verzichtet auf die relativ langsamen Betriebssystemobjekte. Zunächst wird dies anhand einer suboptimalen Lösung demonstriert.

Alle folgenden Beispiele sind bewusst einfach gehalten und erledigen keine sinnvolle Aufgabe. Auch sind die angegeben Messwerte nicht als exakte Benchmarkergebnisse von hoch optimiertem Code zu verstehen. Sie dienen lediglich der Veranschaulichung der Größenordnung.

Das Testprogramm startet zwei der unten abgebildeten Threads, wobei der erste die Thread-Nummer num=0, der zweite num=1 zur internen Abstimmung erhält. Die Kommunikation mit dem Hauptprogramm erfolgt dabei über die globale Variable "Status". Die eigentliche Arbeit passiert in der Schleife while (Status != STOP). Zur besseren Demonstration fällt die Arbeit recht einfach aus: Eine globale Zählervariable iLoops soll jeweils um 1 erhöht werden.

Jeder Thread läuft so lange in einer leeren Warteschleife, bis die globale Variable "owner" gleich seiner Thread-Nummer wird. Ist dies der Fall, erhöht er iLoops, ändert owner und übergibt damit die Kontrolle an den nächsten Thread und beginnt wieder zu warten. So ist sichergestellt, dass immer nur ein Thread arbeitet und anschließend der andere an die Reihe kommt.

Selbst auf einem 3,06 GHz Pentium 4 unter Windows XP finden bei diesem Ansatz ohne Hyper-Threading nur elf Wechsel pro Sekunde statt. Wenn ein Thread eine Zeitscheibe vom Scheduler erhält, erledigt er in Mikrosekunden seine Arbeit und verbrät anschließend einige zig Millisekunden sinnlos in der leeren Warteschleife "while (owner != num);". Erst wenn der Scheduler den anderen Thread auf die CPU lässt, wird durch diesen iLoops wieder erhöht.

Dieses Verhalten ändert sich dramatisch bei aktiviertem Hyper-Threading. Jetzt laufen beide Threads tatsächlich parallel auf der CPU und sind nicht mehr auf das simulierte Multitasking des Windows-Schedulers angewiesen. Als Ergebnis können sie sich den Ball 8,6 Millionen Mal pro Sekunde zuwerfen.