Hyper-Threading: Optimierungen und Fallen

Separation der Daten

Bei der Aufteilung eines Programms in mehrere Threads gibt es einen fundamentalen Grundsatz zu berücksichtigen: "Threads sollten so unabhängig wie nur irgend möglich voneinander sein". Dabei sind zwei Hauptaspekte zu beachten: Die Aufteilung in unabhängige funktionale Code-Blöcke und die Separation der Daten. In den folgenden Überlegungen wird der Einfachheit halber von einer Aufteilung in zwei Blöcke und zwei Threads ausgegangen, in der Praxis ist jedoch eine weitaus höhere Aufteilung nicht selten.

Im Idealfall lassen sich die Daten in vollkommen unabhängige Blöcke aufteilen und mit unabhängigen parallelen Threads bearbeiten. Eine Bildbearbeitung etwa kann ein Foto in die obere und untere Hälfte teilen und auf zwei CPUs parallel manipulieren. Da einfache Operationen wie die Erhöhung der Bildhelligkeit jeden Pixel unabhängig bearbeiten, ist hier die Datenaufteilung perfekt.

Komplexere Filter wie Schärfen oder Weichzeichnen beziehen jedoch neben dem eigentlichen Bildpunkt auch die Nachbarwerte ein. In diesem Fall ist Vorsicht an der Schnittstelle der beiden Bildhälften geboten. Bei einstufigem Filter können beide Threads den Schnittbereich aber noch ohne spezielle Absicherung gemeinsam lesen. Das neu berechnete Bild müssen sie aber zunächst in einem temporären Speicherbereich ablegen, da sonst ein Thread auf schon bearbeitete Daten des anderen und nicht mehr auf die Originaldaten zugreifen könnte.

Spätestens bei mehrstufigen Filtern, die die Daten mehrfach hintereinander manipulieren, ist es aber mit der simplen Aufteilung vorbei. In diesem Fall ist eine Kommunikation der Threads untereinander oder eine Synchronisation nötig. Beispielsweise muss nun Thread 1 sicher sein, dass auch Thread 2 mit dem ersten Schritt fertig ist, bevor er mit dem zweiten Filterdurchgang startet.