Neues in Scala 2.9

Performance der parallelen Collections

Sinnvolle und belastbare Performancemessungen und -vergleiche sind aufwändig und sprengen den Rahmen dieses Artikels. Trotzdem sind ein paar allgemeine Aussagen zur Performance hier wichtig – schließlich ist Performance ja der Daseinszweck der parallelen Collections.

Zunächst einmal gilt, dass Parallelverarbeitung zusätzlichen Overhead mit sich bringt. Parallele Collections können deshalb nur dort Vorteile bringen, wo unbenutzte Prozessorkerne vorhanden sind. Das ist in vielen typischen Serveranwendungen zurzeit nicht der Fall, weil mehrere parallel eintreffende Requests ohnehin schon auf die vorhandenen Prozessorkerne verteilt werden. Bevor man in einer solchen Architektur auf Parallelverarbeitung umstellt, sollte man also unbedingt die Lastverteilung auf die vorhandenen Kerne messen. Auf dem Desktop sieht die Situation dagegen häufig anders aus, hier drehen die in zunehmender Zahl vorhandenen Prozessorkerne oft Däumchen.

Zweitens liegt der Performancevorteil in der parallelen Verarbeitung je Element, während die Verteilung der Arbeit auf Threads eher zusätzlichen Overhead erzeugt. Parallele Collections lohnen sich also umso mehr, je aufwändiger die Operation je Element ist.

Listing 2 illustriert diesen Effekt qualitativ. Es handelt sich dabei ausdrücklich nicht um einen Benchmark, und die Ergebnisse sind nicht vergleichbar und hängen stark von der Umgebung ab, in der der Code ausgeführt wird. Es illustriert aber den eben beschriebenen Trend.

Listing 2: Performancevergleich der sequenziellen vs. parallelen Collections
def measure (empty: Seq[Int], delay: Int) = {
  var coll = empty
  for (i  {Thread.sleep(delay); x+1})
  println (System.currentTimeMillis - start)
}
Listing 3: Aufruf des Performancevergleichs
// "warmlaufen" von Hotspot
for (i 

Die measure-Methode füllt eine übergebene Collection mit 10 000 Elementen. Anschließend transformiert sie sie und legt bei jedem Schritt den Thread eine konfigurierbare Anzahl von Millisekunden schlafen, um unterschiedlich aufwändige Operationen je Element zu simulieren.

Bei Ausführung ohne Delay (Listing 3) dominiert das Iterieren über die Collection die Performance. Die Laufzeit schwankt stark und nimmt tendenziell von Aufruf zu Aufruf ab, weil die Hotspot-VM intern „warmläuft“. Die Laufzeit für sequenzielle und parallele Collections liegt dabei in der gleichen Größenordnung.

Bei einer Verzögerung von einer Millisekunde je Element – was im „realen Leben“ natürlich exorbitant viel wäre – sieht die Situation völlig anders aus. Jetzt dominiert die Verarbeitung der Elemente deutlich gegenüber der Iteration und Arbeitsverteilung, und die Verarbeitung mit der parallelen Collection ist genau doppelt so schnell wie mit der sequenziellen Variante. Dieser Faktor ist natürlich spezifisch für meinen Computer und entspricht einfach der Zahl der Prozessorkerne.

Je nach Situation ist also manchmal eine parallele Verarbeitung sinnvoller, manchmal eine sequenzielle. Deshalb haben Collections-Klassen jetzt die neuen Methoden par und seq, die die parallele bzw. sequenzielle Version einer Collection liefern. Das passiert effizient und wo möglich ohne Umkopieren der Daten. Mithilfe dieser Methoden kann man je Kontext die parallele oder eine sequenzielle Verarbeitung wählen.

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.