Studenten der Informatik, aufgepasst: hier gibt’s Wissen pur!
Heute ging eine Pressemeldung an diverse Redaktionen, in der folgendes zu lesen steht:
18% mehr Erstsemester in der Informatik – Berufsaussichten weiterhin exzellent
Im Studienjahr 2011 (Sommer- und Wintersemester 2011) haben sich laut Mitteilung des Statistischen Bundesamtes 48.400 Studierende für den Studienbereich Informatik eingeschrieben. Dies entspricht einer Steigerung von 18 % im Vergleich zum Vorjahreszeitraum.
Diese Meldung stammt von der Gesellschaft für Informatik, die verständlicherweise über diese Entwicklung sehr erfreut ist, denn:
“Durch diesen Anstieg werden wir auch deutlich mehr Absolventinnen und Absolventen bekommen, die in Wirtschaft, Wissenschaft und Verwaltung dringend gebraucht werden. Denn nach wie vor ist der Bedarf an gut qualifizierten Informatikerinnen und Informatikern sehr hoch.”
Das sagt GI-Präsident Stefan Jänichen höchstpersönlich, und der wird es ja wohl wissen. Dabei muss allerdings die Frage erlaubt sein, ob die Fachhochschulen und Universitäten dieses Landes auf solch einen Zustrom an Erstsemestlern auch richtig eingestellt sind. Und zwar in Sachen Ausstattung und Lehrinhalte. Zu ersterem können wir natürlich nichts beitragen, zu Punkt zwei aber schon.
Daher folgt an dieser Stelle unser ultimativer Erstsemester-Survival-Kit in Sachen Multicore-Programmierung. Auf dass die Software-Anwendungen der Zukunft auf Rechnersystemen mit 4, 6, 8 und mehr Prozessorkernen verteilt und superskalar laufen. Viel Spaß beim Schmökern!
Grundwissen Multicore-Programmierung
Wie man sich die parallele Datenverarbeitung/Programmierung vorstellen kann
Von Single-Threading bis zur Multicore-Programmierung
In der Parallelprogrammierung steckt die Zukunft!
12 Thesen und Antithesen zur Multicore-Programmierung
Stephen Blair-Chappell über Chancen und Grenzen der Parallel-Programmierung
Know-how: Von Deadlocks und Live locks
Know-how und Programmiertipps: Data Races
Multithreading-Konzepte: OpenMP, APIs und Intel TBB
Tutorials & Regeln
Workshops und Tutorials für Parallelprogrammierer
Beliebte Fehler bei der Parallelprogrammierung
Einsteiger-Kurs: So programmiere ich für Multicore-Systeme unter Microsoft . NET (Teil 1, Teil 2, Teil 3)
Workshop: Alles über .NET-Threads (Teil 1, Teil 2, Teil 3, Teil 4)
Programmiermethoden: Daten- und Aufgabenparallelität
Vier Schritte zum optimierten Multicore-Quellcode
Fünf Multicore-Programmierregeln auf einen Blick
Acht nützliche Tipps zum Parallelisieren
Mögliche Thread-Modi und deren Folgen
OpenMP-Workshops
Workshop: parallel Programmieren mit OpenMP
OpenMP: fünf Bedingungen für parallele Schleifen
OpenMP: Schleifen anpassen für Multithreading-Ausführung
Buchtipp
Buchtipp: Multicore-Programmierung lernen mit Intel
Zwei kostenlose Webinare: So verbessere ich die Performance meiner Applikationen
Über das Optimierungstool Intel VTune Amplifier XE habe ich ja schon das ein oder andere Mal berichtet. Dazu passt ganz gut ein Eintrag auf software.intel.com, der auf eine zweiteilige Webinar-Reihe aufmerksam macht, die Intel interessierten Entwickler kostenlos zur Verfügung stellt. Hierzu müssen Sie sich einmal registrieren, um anschließend beide Online-Trainings in voller Länge konsumieren zu können.
Teil 1 der virtuellen Kurse beschäftigt sich vor allem mit der Frage, wie sich Anwendungen, die auf den aktuellen Prozessoren von Intel (Stichwort: Sandy Bridge) laufen, so optimiert werden können, damit sie noch besser und performanter auf dem Rechner ausgeführt werden. Hierzu bietet VTune Amplifier XE eine eigene Funktion, mit der sich die Anwendung analysieren und auf Hotspots und ähnliche Bremsklötze untersuchen lässt.
Teil 2 der Webinar-Reihe zeigt, welche Hemmschuhe in einer Anwendung vorkommen können und wie sie mit Hilfe von Sandy-Bridge-Funktionen wie AVX behoben werden können. Natürlich lernen Sie auch, wie sich diese Funktionen optimal einsetzen lassen und weiter optimiert werden können.
Und wem das noch nicht genügt, dem möchte ich die zwei Videos ans Herz legen, die sich mit dem Thema Performance-Analyse ausführlich beschäftigen. Für das Abspielen der Videoclips müssen Sie sich übrigens nicht anmelden.
Alle Infos und den Anmeldelink finden Sie auf der zugehörigen Webseite auf dem Intel Software Network.
How-to: Parallel programmieren mit der Open-Source-Sprache Go
Mein gestriger Interviewpartner Anton Klotz hat mich darauf gebracht: Mit dem Open-Source-Projekt Go steht vor allem für C++-Programmierer eine Sprache zur Verfügung, mit der vieles leichter werden soll, vor allem das Programmieren von Nebenläufigkeiten, was angesichts von zunehmenden Prozessorkernen pro Maschine ein immer wichtigeres Thema wird. Alleine deshalb ist ein Blick auf Go für mich unbedingt lohnenswert.
Zunächst einmal muss man festhalten, dass es drei verschiedene Go-Compiler gibt: einen für AMD-Prozessoren, einen für die x86-Plattform von Intel und eine Variante für ARM-CPUs, die derzeit vor allem in Smartphones und Tablets verbaut werden. Die erstgenannten sind laut goloang.org fertig und bieten sogar diverse Möglichkeiten zu automatisierten Code-Optimierung auf Registerbasis. Der ARM-Compiler ist offenkundig noch in einer frühen Beta-Phase, zumindest werden bis dato nur Linux-Binaries unterstützt, der Optimizer ist noch nicht fertig und Floating-Point-Berechnungen werden derzeit nur auf Basis der VFP-Unit durchgeführt.
Aus Sicht dieses Blogs ist natürlich das Konzept der Nebenläufigkeiten von ganz besonderem Interesse, das Go mit neuen Ansätzen optimal unterstützen will. Hierzu gehören folgenden Dinge:
- “Share by Communication”: Im Gegensatz zu anderen Programmiersprachen, bei denen die meisten Probleme aus dem konkurrierenden Zugriff auf Daten resultieren, verfolgt Go einen neuen Weg: Daten werden mithilfe von Kanälen “herumgereicht”, was dazu führt, dass jede Go-Routine immer nur auf genau einen Wert zugreift, nämlich den aktuell ermittelten. Um das in einem Satz zu verdeutlichen, haben die Macher von Go sich den Slogan “Do not communicate by sharing memory, instead share memory by communicating” ausgedacht.
- Go-Routinen: Diese Ansammlung von Funktionen laufen parallel zu anderen Go-Routinen im selben Adressraum ab. Zudem benötigt solch eine Routine nur wenig Speicher, da sie lediglich ein wenig Stack in Anspruch nimmt. Solch eine Funktion oder Methode wird mit dem Schlüsselwort go aufgerufen. Endet die Funktion oder Methode, wird auch die Go-Routine beendet.
- Channels (Kanäle): Kanäle sind Referenztypen und sorgen dafür, dass zwei oder mehrere Go-Routinen in einem definierten Zustand bleiben. Channels werden mit dem Befehl make erzeugt.
- Kanäle von Kanälen: Channels sind Werte “erster Klasse”, können also beliebig herumgereicht werden. Das erlaubt ein thread-sicheres, paralleles Verarbeiten mehrerer Kanäle, so genannter Channel Bundles. Der englische Fachbegriff hierfür nennt sich De-Multiplexing. Daraus entsteht ein quotiertes, paralleles, nicht-blockierendes RPC-System – und das ganz ohne Mutexe.
- Parallelisieren: Das asynchrone Berechnen eines Vektors ist eines der Anwendungsbeispiele von Go-Routinen mithilfe von Kanälen. Dabei werden die Elemente eines Vektors auf die Anzahl der vorhandenen Kerne verteilt, und das völlig unabhängig voneinander. Sobald sämtliche Vektorberechnungen durchgeführt worden sind, werden die zugeghörigen Kanäle gelöscht und das Ergbnis ermittelt.
Wer Go mal ausprobieren möchte: auf golang.org steht eine genaue Installationsanleitung bereit. Und ein Blick in die umfangreiche Online-Dokumentation kann sicherlich auch nicht schaden. Und besonders schick: einen sehr übersichtlichen Programmierleitfaden gibt es sogar in Deutsch!
Know-How und Programmiertipps: Data Races
Im Zuge meiner kleinen Serie zu den häufigsten Code-Pannen der Parallel-Programmierer habe ich bereits über Deadlocks und Livelocks geschrieben. Jetzt geht es um Data Races – ein sehr häufiges Programmierproblem, das zwar auch Programmhänger oder –abstürze verursacht, vor allem aber zu falschen Berechnungen führen kann, wodurch die eigene App dann im Zuge bestimmter Prozesse unberechenbar reagiert.
Aber der Reihe nach: Data Races (auch: “Dataraces”) gehören zur Klasse der Race Conditions. Der Begriff „race“ deutet bereits an, dass es sich hierbei um einen Wettlauf handelt: Eine Gesamtprozess soll das Endergebnis zweier oder mehrerer Teilprozesse darstellen, wobei die Teilprozesse aufeinander aufbauen, fälschlicherweise aber parallel gestartet werden und somit zu einem falschen Resultat führen.
Eine kleine Analogie, die das verdeutlicht: Bei einem Staffellauf übergibt Läufer 1 das Hölzchen an Läufer 2 und so fort. So ergibt sich eine funktionale Kette, wobei ein Läufer erst dann starten darf, wenn er den Stab vom heraneilenden Partner erhalten hat. Diese funktionale Kette würde unterbrochen, wenn Läufer 2 nicht wartet, sondern sich ein eigenes Hölzchen schnappt und – warum auch immer – vor dem vereinbarten Kommando losläuft. Dann würde Läufer 3 von 2 Teamkollegen und 2 Hölzern bedient. Das führt in der Leichtathletik zur Disqualifikation.
Nun etwas technischer: Bei Data Races beschreibt beispielsweise ein Thread einen Speicherbereich, während ein zweiter Thread auf einen überlappenden Bereich zugreift. Bislang war die Analyse dieser Fehler sehr aufwändig, weil die Auswirkungen nicht immer sofort sichtbar sind oder keine klaren Rückschlüsse auf einzelne Code-Phrasen zulassen.
Ist die Ursache aber gefunden, lässt sich die Panne über eine neue Anordnung korrigieren: der erste Thread muss exklusiven Zugriff auf Ressourcen (etwa den Speicherbereich) erhalten und diesen Zugriff erst nach Abschluss seiner Aufgabe zugunsten des zweiten Threads lösen – also eine sequentielle Abfolge der Threads. Praktisch wird das meist über Semaphore erledigt. Semaphore sind Datenstrukturen, die – vereinfacht ausgedrückt – dazu dienen, bei der parallelen Ausführung die Threads in eine sinnvolle Reihenfolge zu bringen.
Das klingt kompliziert – und das ist es auch. Aber nur, wenn Sie auf den Einsatz des Intel Inspector XE 2011 verzichten. Der kümmert sich selbstständig um das Fixing. Zunächst aber prüft das Tool gewissenhaft die Threads und die Speicherbelegung. Dafür können Sie übrigens anhand der integrierten Tutorials vorgehen. Memoryleaks werden dabei sofort aufgespürt und im Quellcode lokalisiert. Das gilt auch für Data Races, die Sie in einem Zug gemeinsam mit Deadlocks ermitteln.
Wie einfach und hilfreich das mitunter in der Praxis ist, schildert dieses sehr gute Video mit Anwendungsbeispielen aus der Parallelprogrammierung:
Wie Entwickler von neuen Sandy-Bridge-Registern und -Ops profitieren
Über die neuen Intel AVX-Register und -Operationen habe ich ja hier schon des öfteren berichtet. Heute soll es mal ganz konkret um Anwendungsbeispiele gehen und wie AVX dabei hilft, entsprechenden Sourcecode zu beschleunigen. Für weiterführende Informationen und Code-Beispiele habe ich ans Ende jedes Szenarios einen Link gepackt.
Szenario I: Videokodierung/-dekodierung
Bei der Kodierung und Dekodierung von Videodaten kommen vor allem zwei mathematische Funktionen häufig zum Einsatz, nämlich die Diskrete Kosinus-Transformation und die zugehörige Inverse Transformation. Hierfür steht eine eigene AVX-optimierte Funktion zur Verfügung, die im Vergleich zu der entsprechenden SSE-Floating-Point-Operation solch eine Transformation mit dem Faktor 1,78 schneller berechnet. Mehr Infos dazu
Szenario II: Bildbearbeitung
In der Bildbearbeitung sind vor allem Filter häufig angewandte Funktionen, und auch hier können AVX-basierte Operationen die Anwendung erheblich beschleunigen – und das ohne größere Veränderungen am Sourcecode. Beispiel hierfür ist der Gauß’sche Weichzeichner, der vor allem für das Entfernen von Bildrauschen verwendet wird. Da sich Pixel meist völlig unabhängig voneinander analysieren und manipulieren lassen, existiert hier ein großes Parallelisierungspotenzial auf AVX-Basis. Für den Gauß’schen Filter kommt übrigens OpenMP zum Einsatz, und die benötige Rechenzeit halbiert sich nahezu im Vergleich zu SSE-Operationen bei einem Bild der Größe 2048*2048. Mehr Infos dazu.
Szenario III: 3D-Anwendungen
Häufig in 3D-Anwendungen vorkommende mathematische Operationen sind Matrizenberechnungen, die zur Darstellungen von Vektoren notwendig sind. Die wichtigsten Berechnungen hierbei sind die Addition (mm256_add_ps), die Multiplikation (mm256_mul_ps) und die Berechnung der Matrix-Determinanten, die ja rein mathematisch betrachtet eine Kombination aus Matrizen-Addition, -Multiplikation und -Subtraktion darstellt, also mm256_add_ps(), mm256_mul_ps() und mm256_sub_ps(). Die zur Verfügung stehenden AVX-Operationen beschleunigen die Berechnungen kleiner Matrizen um den Faktor 1,8 (Addition) bis 1,56 (Determinante). Mehr Infos dazu
Sandy Bridge: Grafik-Power und Power-Tools für Entwickler
Wie Ende der vergangenen Woche versprochen, folgt heute eine Übersicht der wichtigsten Grafikverbesserungen, die Intel mit Sandy Bridge einführt und ein paar wirklich gute Developer-Ressourcen und Code-Beispiele – denn in den USA haben einige Programmierer bereits losgelegt und konnten mit ihren Samples den Performance-Gewinn der neuen Prozessorgeneration für Spiele(r) eindrucksvoll unter Beweis stellen.
Zunächst aber die Übersicht der wesentlichen Grafik- und Power-Technologien, die Sandy Bridge auf den Core-Chips bietet. Bislang habe ich diese Funktionen eher tröpfchenweise beschrieben, hier nun der Gesamtüberblick.
- Verbesserte Grafikleistung durch Integration von GPU und CPU auf einem einzigen Chip (Die). Per Intel Switchable Graphics können Notebook-Anwender bei sehr grafikintensiven Spielen wie FIFA 11 ohne Neustart des Computers auf die vorhandene diskrete Grafikkarte umschalten. Allerdings muss der jeweilige Rechner dieses Feature explizit unterstützen.
- Turbo Boost 2.0: Turbo Boost wurde von Intel für Sandy Bridge optimiert und beschleunigt serielle Anwendungen, indem sie die Taktfrequenz einzelner Kerne anhebt und damit die Geschwindigkeit um bis zu 75 Prozent steigert. Turbo Boost funktioniert auch bei der GPU und prüft automatisch, ob die Prozessorkerne oder die Grafik beschleunigt werden soll. Sie geht dabei auch bis zu 25 Sekunden lang über das TDP-Limit hinaus. So können einzelne oder alle CPU-Kerne kurzfristig um bis zu 700 MHz “übertaktet” werden.
- Gemeinsamer Last Level Cache: Die Grafik-Engine ist nun direkt an den Cache des Prozessors angebunden. Deswegen auch die neue Bezeichnung Last Level Cache statt L3-Cache. Der Cache steht sowohl den Kernen als auch der Grafikeinheit zur Verfügung. Die Grafikleistung profitiert dabei von einem 64-fach höheren Durchsatz als beim bisherigen Speicherzugriff.
- Intel QuickSync Video: Videos lassen sich deutlich schneller für mobile Geräte wie iPhone und iPad konvertieren. So zeigen Intel-interne Tests, dass mit QuickSync Video nur noch vier Minuten Konvertierungszeit für eine Minute Video anfallen. Das ist schneller als mit der schnellsten diskreten Grafikkarte, die derzeit erhältlich ist.
- Intel InTru 3D: Stereoskopische 3D-Blu-ray-Wiedergabe in Full HD 1080p-Auflösung über HDMI 1.4.
- Intel Clear-Video-HD: Qualität und Farbdarstellung bei der HD-Wiedergabe wurde nochmals deutlich verbessert.
- Advanced Vector Extensions (AVX) : Neuer Befehlssatz für Video- und Bildbearbeitung, Gaming. 3D-Modellierung, wissenschaftliche Simulationen und Finanzanalysen. AVX beschleunigt die anfallenden Berechnungen mit einer Verdoppelung der Registerbreite von 128 auf 256 Bit. Hierbei wurde auf volle Komptabilität zu den bisherigen Befehlssätzen wie beispielsweise SSE4 geachtet.
- Intel HD Graphics 3000: Mehr 3D-Leistung für Highend-Games wie Shooter. Die dynamische Grafikfrequenz liegt bei maximal 1350 MHz. Unterstützt werden DirectX 10.1 und OpenGL 3.0.
Und nun zu den Ressourcen, die Ihnen diese neuen Grafik-Technologien aus Entwicklersicht näher bringen, Code-Beispiele nennen und auch den einen oder anderen Insider-Tipp verraten:
- Intel Integrated Graphics Performance Developer’s Guide für Intel HD Graphics: Eine Leitfaden für Spiele-Entwickler, die ihren Code konsequent auf die neuen Funktionen von Sandy Bridge ausrichten möchten. Natürlich steht dabei Intel HD Graphics im Vordergrund. Es geht aber auch um den älteren Intel Graphics Media Accelerator mit Fokus auf das Thema Leistungsanalyse unter DirectX.
- AVX Cloth ist ein Beispiel, das die Möglichkeiten der Vektorprogrammierung mit 256 Bit breiten Registern verdeutlicht. Dazu gibt es Screenshots, ein Video und natürlich den Source Code und die Binaries zum Download.
- Onloaded Shadows ist eine Technik, mit der Shadow Maps asynchron auf der CPU berechnet werden. Erfahren Sie hier, wie Sie mit Hilfe von Cascades einen optimalen Lastenausgleich zwischen CPU und GPU herstellen.
- Sample Tweaker beschreibt, wie die Grafikdemo Ocean Fog mit Hilfe von Intel Integrated Graphics optimiert wurde. Dabei konnte die vierfache Geschwindigkeit (von 40 FPS auf 160 FPS) gemessen werden, indem neue Technologien angewendet wurden, die beispielsweise die Texturgröße oder die Schärfe verringert haben – ohne nennenswerte Auswirkungen auf die Qualität.
Ausblick auf die CES, Sandy Bridge und zwei nützliche Tools
Keine Ahnung, wie viele Menschen Ihnen in den letzten Tagen „Ein gutes neues Jahr!“ gewünscht haben. Aber vermutlich waren es viele, und ganz sicher werden sie Recht behalten. Denn das Jahr 2011 wird für Parallel-Programmierer und Spiele-Entwickler Maßstäbe setzen!
In wenigen Tagen wird Intel während der CES in Las Vegas die neue Prozessorgeneration Sandy Bridge offiziell vorstellen. Vor Ort werden dann auch gleich ein paar Hardware-Hersteller erste Notebooks mit den neuen Recheneinheiten zeigen.
Die Prozessor-Architektur von Sandy Bridge wird vor allem in puncto Multimedia-Performance alle Vorgänger der Nehalem-Bauweise in den Schatten stellen. Sandy Bridge integriert den Grafikkern (GPU) in die CPU bei einer Strukturbreite von 32 Nanometern. Dank der integrierten Encoding-Einheit lassen sich Videos wesentlich schneller konvertieren als beispielsweise mit bekannten Core-i5-Prozessoren.
Die technologischen Neuerungen von Sandy Bridge und die daraus resultierenden, erweiterten Möglichkeiten für Parallel-Computing sind Anlass genug, dieses Blog wiederzubeleben. Ich werde Sie ab sofort regelmäßig mit News, technischem Background und Veranstaltungshinweisen versorgen, die Ihnen den Programmieralltag etwa erleichtern.
Zunächst aber darf ich Ihnen zwei neue, sehr nützlich Tools empfehlen, die Intel im Rahmen der Partnerprogramme veröffentlich hat und vor allem Entwickler von Media- und Videoanwendungen adressieren.
Das Intel Media Software Development Kit in der Version 1.5 ist ein plattformübergreifendes SDK, das Ihnen hilft, auf einfache Weise leistungsstarke und schnelle Videoanwendungen zu entwickeln. Das Tool-Paket bietet Ihnen drei Highlights:
- Codecs für Hardwarebeschleunigung: Sie greifen auf Video-Codecs zu, die für Intel Multicore-Prozessoren und Intel HD Graphics optimiert wurden und bestmögliche Performance bieten
- Einheitliches API für unterschiedliche Plattformen: Weniger Code und weniger Komplexität durch ein universelles API, das viele PC-Plattformen unterstützt, darunter selbstverständlich auch die zukunftsträchtigen Intel Graphics- und Multicore-Architekturen.
- Support und Add-Ons: Schneller optimale Ergebnisse erzielen dank nützlicher Features wie Video-Pre-Processing, Decodieren und Encodieren
Sie können das Media Software Development Kit kostenlos laden und installieren. Sinnvoll ergänzt wird das SDK durch das Intel Media Checker Software Assessment Tool. Zugegeben, eine grauenhafte Bezeichnung, aber das Tool ist überaus nützlich. Denn damit stellen Sie beispielsweise sicher, dass Sie das SDK korrekt in die Anwendungen einbinden. Zudem fungiert es als Tutorial, indem es die Funktionen und Technologien des SDK erläutert. Also für die ersten Schritte mit dem SDK sicherlich genau richtig. Und kostenlos ist es natürlich auch. Viel Erfolg damit!
Multicore-Guide, Teil 2 ist verfügbar
Mitte März hatte ich über eine ausführliche Sammlung gebloggt, in der es sehr ausführlich um das Thema Multicore-Programmierung geht. Und jetzt habe ich dazu passend eine E-Mail des Intel-Service “Software Dispatch” erhalten. Diese Mail verweist nämlich auf den zweiten Teil des großen Multicore-Guides. Darin geht es auf gut 50 Seiten unter anderem um folgende Dinge:
- das richtige Speicher-Management beim Threaden von Anwendungen
und
- der richtige Einsatz der bestehenden Intel-Tools wie Integrated Performance Primitives und Parallel Inspector, Parallel Amplifier und Parallel Composer.
Toll an diesem Programmier-Leitfaden ist die sehr ausführlich Darstellung möglicher Szenarien und wie sich diese richtig behandeln lassen. Dazu gibt es eine Menge nützlicher Diagramme und Code-Beispiele.
Allerdings hat Intel vor den Download dieser digitalen Broschüre eine kleine Registrierung gestellt, die aber nur aus einem Namen und einer E-Mail-Adresse besteht. Anschließend könnt ihr sogar auswählen, welche Themen und Broschüren euch besonders interessieren. Im Falle der Multithreading-Anleitungen solltet ihr “Intel Software Dispatch” anklicken. Viel Spaß beim Lesen!
Aus seriell mach parallel: Intel Parallel Advisor Lite
Kollege Preiss von Intel hat mal wieder fleißig geschrieben, und rausgekommen ist ein gedruckter Beitrag in der Elektronik Industrie, den es aber natürlich auch als PDF zum kostenlosen Download gibt. Thema des Artikels: Intel Parallel Advisor Lite, ein ziemlich mächtiges Tool zum Konvertieren von seriellem in parallelen Code. Der Advisor Lite ist übrigens Teil der Intel-Suite Parallel Studio, ein Plug-In für Visual Studio 2005 und 2008.
In dem ausführlichen Beitrag wird gezeigt, wie das Intel-Tool dabei helfen kann, serielle Codeabschnitte zu parallelisieren. Ach ja: Das betrifft ausschließlich C++-Programmierer. Toll an Advisor Lite ist sein schrittweises Herantasten an den möglichen Multithread-Code: Anstatt alles auf einmal umzustellen, erlaubt das Tool ein sukzessives Optimieren des seriellen Quellcodes. Hierzu setzt es vorhandene Debugger-Tools ein, mit denen der parallelisierte Quellcode sofort getestet und mögliche Fehler identifiziert und eliminiert werden können. Dazu zählen unter anderem Data-Sharing-Probleme, die sich beim Synchronisieren von Threads ergeben können.
Interessant an Edmunds Artikel ist übrigens die Vorgehensweise des Advisor Lite:
- Leistungsengpässe (Hotspots) identifizieren
- Annotationen in den seriellen Quellcode einfügen (eine Art von C-/C++-Makros)
- die annotierten Quellcodes verifizieren
- parallele Datenzugriffe untersuchen
- Datenkonflikte auflösen, die unter Schritt 4 aufgespürt wurden
- Quellcodeänderungen testen
Ach ja: Wer noch nicht wissen sollte, was Annotationen sind: Sogar hierzu hält Edmund die passenden Antworten parat. Also alles in allem eine wirklich gelungene Abhandlung in Sachen Parallel Advisor Lite. Und, worauf wartet ihr noch?
Videosessions: TechTalks mit Intel und Microsoft
Der Juni 2009 stand bei Intel und Microsoft ganz im Zeichen der parallelen Programmierung (woran sich im Juli natürlich nichts geändert hat). Daher fanden zu diversen Terminen in diversen Städten sogenannte TechTalks statt, die erfahrungsgemäß immer sehr gut besucht sind.
Ich war auf der Veranstaltung in München und konnte mich selbst davon überzeugen: der Raum war proppenvoll, da das Thema wohl für viele sehr spannend ist. Der Bedarf nach mehr Wissen zum Thema Parallelprogrammierung ist sehr groß, und mindestens genauso groß sind die Lücken, die sich dazu bei vielen Entwicklern auftun.
Nun, diese Defizite konnten Intel und Microsoft hoffentlich ein wenig beheben. Und wer selbst nicht die Gelegenheit hatte, sich vor Ort ein eigenes Bild davon zu machen, was es mit der parallelen Programmierung auf sich hat, dem seien die drei Videoclips empfohlen, die Microsoft online gestellt hat.
Allerdings sollte man sich viel Zeit nehmen, denn die Videokollektion umfasst mehr als drei Stunden Anschauungsmaterial zu diversen Themen:
- Warum wird Parallel Computing immer wichtiger?
- Was genau verbirgt sich hinter Parallel Studio?
- Was kommt alles auf Entwickler mit Visual Studio 2010 und .NET 4 zu?
- Wer braucht Intel Threading Building Blocks?
Deshalb: Popcorn und Coke bereitstellen, Füße hochlegen und Videos gucken. Und zwar welche der lehrreichen Sorte, Powerpoint-Folien inklusive.
Screencasts zur Parallelen Programmierung unter .NET 4
Auf Channel 9 habe ich gerade drei sehr interessante Screencasts entdeckt, die Dariusz Parys dort eingestellt hat. Anhand sehr anschaulicher Beispiele zeigt Dariusz folgende Dinge:
In diesem Screencast geht es vor allem um eine Kernaussage: Vergesst Threads und denkt ab sofort in Tasks, also in einer abstrahierten Form von Threads. Warum das so ist und welche Mechanismen der Threadpool des .NET-4-Frameworks hierfür bereithält, lernt ihr in dem Sechsminüter.
Task Parallel Library: Task Continuations
Dieser Screencast behandelt die Frage, wie sich einzelne Aufgaben (Tasks) mithilfe der Task-Klasse verketten lassen, um weitere Ereignisse möglichst einfach zu parallelisieren. Dies geschieht mithilfe des Aufrufs task.ContinueWith().
Task Parallel Library: Exception Handling
In diesem Screencast geht es um die Fehlerbehandlung innerhalb von Tasks. Hierzu gibt es die Möglichkeit, per AggregateException mögliche Fehler zur Laufzeit abzufangen.
Ihr seht also: drei gute Gründe, euch die Screencasts anzusehen. Viel Spaß dabei!
Workshops und Tutorials für Parallelprogrammierer
Was mir als Software-Dev-Blogger wirklich am Herzen liegt, ist ein möglichst hoher Nutzwert, den meine Blog-Beiträge bieten sollen. Daher haben sich im Laufe der letzten gut sechs Monate einige Workshops und Tutorials angesammelt, die zeigen, wie die parallele Programmierung vonstattengeht. Und damit diese Tipps und Tricks nicht in Vergessenheit geraten, folgt jetzt die ultimative Tutorial-Sammelliste:
>> Los ging es mit den bekannten Multithreading-Konzepten OpenMP, APIs und Intel Threading Building Blocks und der Frage, welche der drei Methoden sich zum Threaden am besten eignet.
>> Dann folgten fünf Multicore-Programmierregeln, die zeigen sollen, mit welchen Anforderungen Programmierer und Software-Entwickler konfrontiert werden, wenn sie sequenziellen Code in parallele Anwendungen überführen wollen. Ich sage nur: Denke parallel!
>> Ein wichtiges, weil fundamentales Konzept der Multicore-Programmierung ist der Unterschied zwischen Daten- und Aufgabenparallelität. Diesen zu verstehen ist die erste Programmiererpflicht, wenn es darum geht, skalierbare Multithread-Anwendungen zu erstellen.
>> Wie viele Schritte muss man gehen, um aus seriellem Quellcode parallel ablaufenden zu machen? Genau vier. Rein abstrakt betrachtet zumindest. Auch hierfür habe ich den passenden Workshop parat, der die vier Stufen der Parallelprogrammierung genauer beleuchtet.
>> Was aber, wenn ich zwar weiß, wie ich parallel programmieren soll, ich aber keinen Schimmer davon habe, welche Stolperfallen dabei auf mich warten? Da heißt es dann meinen Beitrag lesen, welche Fehler bei der Parallelprogrammierung der Entwicklergemeinde drohen und wie sich diese (die Fehler, nicht die Entwickler) umgehen lassen.
OpenMP: Schleifen anpassen für Multithreading-Ausführung
Gestern habe ich mit einer neuen Serie angefangen, die sich intensiv mit dem Thema OpenMP beschäftigt. Im ersten Teil ging es sehr fundamental um die Voraussetzungen, die eine Schleife erfüllen muss, um per OpenMP multithreading-tauglich zu sein. Heute geht es um die Frage, welche Dinge zu beachten sind, damit eine Schleife ordnungsgemäß in mehrere Threads aufgeteilt werden kann.
Zunächst einmal: Das Threaden von Schleifenkonstrukten bedeutet nichts anderes, als dass unabhängige Schleifeniterationen auf mehreren Threads gleichzeitig ausgeführt werden können, was natürlich Rechenzeit pro Takt spart. Hierzu wird die Schleife in eine neue Form gebracht, die das Parallelisieren derselben überhaupt erst ermöglicht. Dies ist aber nur umsetzbar, wenn die Schleife keine Abhängigkeiten aufweist.
Daher muss man als Entwickler zunächst einmal mit einem passenden Tool wie VTune Performance Analyzer diejenige Schleife finden, die insgesamt die meiste Rechenzeit verschlingt. Anschließend wird diese umstrukturiert, um festzustellen, dass keine iterationsübergreifenden Abhängigkeiten bestehen. Erst dann sollte diese Schleife mithilfe eines OpenMP-Pragmas parallelisiert werden.
OpenMP: fünf Bedingungen für parallele Schleifen
Das Thema OpenMP steht bei vielen Lesern dieses Blogs hoch im Kurs. Das liegt zum einen an der Parallel-Computing-Seite von MSDN, auf der ein entsprechender Beitrag lange verlinkt war. Zum anderen findet Google meine OpenMP-Bemühungen wohl ganz nett, zumindest steht besagter Artikel im deutschsprachigen Index auf dem fünften Platz.
Daher starten wir heute mit einer Serie, die sich mit kleineren und größeren Aspekten der OpenMP-basierten Programmierung beschäftigt. Also mit solchen Fragen wie:
- Welche Bedingungen müssen erfüllt sein, damit OpenMP-basierende Schleifen überhaupt in parallelen Threads ausgeführt werden können?
- Welche Dinge sind zu beachten, damit eine Schleife ordnungsgemäß in mehrere Threads aufgeteilt werden kann?
- Wie lässt sich möglicher Threading-Overhead vermeiden?
- Wie kann man das Optimum aus OpenMP herausholen?
- Wie lassen sich sinnvoll OpenMP-Bibliotheksfunktionen und -Umgebungsvariablen einsetzen?
- Wie geschieht das Kompilieren und Debuggen mithilfe von OpenMP?
- Wovon hängt eine maximale Multithread-Leistung auf OpenMP-Basis ab?
Welche Bedingungen müssen erfüllt sein, damit OpenMP-basierende Schleifen überhaupt in parallelen Threads ausgeführt werden können?
1. In der Version 2.5 müssen Schleifenvariablen vom Typ vorzeichenbehafteter Integer sein. Mit der OpenMP-Spezifikation 3.0 ist diese Beschränkung weggefallen.
2. Die Vergleichsoperation muss in der Form loop_variable <, <= oder >= loop_invariant_integer sein.
3. Der Inkrementteil (z.B. i++) der for-Schleife muss additiv oder substraktiv sein, und zwar mit einem schleifeninvarianten Wert.
4. Ist die Vergleichsoperation vom Typ < oder <=, muss die die Schleifenvariable bei jeder Iteration erhöht werden. Umgekehrt muss die Variable dekrementiert werden (also bei > oder >=).
5. Die Schleife muss zwingend einen Eintritt und einen Austritt haben. Daher sind nicht erlaubt: Sprünge aus der Schleife heraus bzw. von außen in die Schleife hinein. Das gilt beispielsweise für goto- oder break-Anweisungen oder für Ausnahmebehandlungen. Eine Ausnahme dieser Regel stellt die exit-Anweisung dar, die die komplette Anwendung beendet.
Diese Bedingungen müssen aus Kompilierungsgründen eingehalten werden. Andernfalls kann keine automatische Parallelisierung erfolgen.
Workshop: Alles über .NET-Threads – Teil 4
Eine neue Woche beginnt, und im selben Atemzug endet meine vierteilige Serie zum Thema .NET-Threads. So habe ich im ersten Kapitel über das Erzeugen von Threads geschrieben, im zweiten Teil mich über das Verwalten derselben ausgelassen, und am letzten Donnerstag war der ThreadPool und dessen Möglichkeiten dran. Und heute?! Nun, heute geht es um die Synchronisierung mehrerer Threads und atomare Aktionen.
Zunächst einmal kann man festhalten, dass die Thread-Synchronisierung im .NET-Framework ähnlich funktioniert wie im Win32- oder Pthreads-Umfeld. Es geht also um den gegenseitigen Ausschluss sowie um atomare Aktionen auf spezielle Variablen. Wie bei der von C# bekannten Methode lock wird ein Codeabschnitt mithilfe der geschweiften Klammern geblockt, sodass zu dieser Zeit nur ein einziger Thread darauf zugreifen kann. Hierfür bietet das .NET-Framework eine ähnliche Konstrukte:
Monitor.Enter ( this )
try
{
……. shared_var = other_shared_var +1;
……. other_shared_var = 0;
}
finally
{
…… Monitor.Exit ( this )
}
Mit der Klasse Monitor wird der entsprechende Codeabschnitt blockiert. Mit Enter() wird der Abschnitt gesperrt und mit Exit() wieder freigegeben. Praktisch an Monitor ist auch dessen Möglichkeit, Datenstrukturen als Parameter zu übergeben.
Bei Monitor.Enter() geschehen übrigens zwei Dinge: Erstens wird eine Warteschlange eingerichtet, die auf diejenigen Threads verweist, die gesperrt werden sollen und eine zweite Queue mit Threads, die darüber informiert werden wollen, wenn eine Speere verfügbar ist. Monitor.Exit() sorgt dafür, dass der erste verfügbare Thread in Warteschlange #1 gesperrt wird.
Workshop: Alles über .NET-Threads – Teil 3
Teil eins und Teil zwei meines Minispecials zum Thema .NET-Threads stehen bereits online, und heute folgt sehr chronologisch der dritte Abschnitt. Dieser handelt von den Thread-Pools, mit deren Hilfe eine größere Anzahl von notwendigen Threads mithilfe des .NET-Frameworks verwaltet werden können.
Zunächst einmal sollte man sich klar machen, dass das Erzeugen und Verwalten von .NET-Threads mit einem immensen administrativen Aufwand verbunden ist: Lokaler Thread-Speicher muss angelegt und die Systemstrukturen für die Thread-Verwaltung müssen eingerichtet werden. Darüber hinaus nimmt die Komplexität des Quellcodes deutlich zu, sobald mehrere Threads mit Bordmitteln verwaltet werden sollen.
Aus diesem Grund hat Microsoft dem .NET-Framework ein Ressource spendiert, die sich ThreadPool nennt. Dabei wird zunächst eine bestimmte Anzahl von Threads generiert, außerdem wird eine Art Arbeitswarteschlange erstellt. Sobald eine auszuführende Aufgabe in diese Warteschlange befördert wird, wird ein Thread für diesen Task aktiviert und ihm die Aufgabe zugewiesen. Dies erledigt das .NET-Framework automatisch. Allerdings ist dabei zu beachten, dass der ThreadPool nur dann sinnvoll eingesetzt werden kann, wenn ein Programm aus mehreren Threads besteht, die immer wieder benötigt werden.
Workshop: Alles über .NET-Threads – Teil 2
Meine kleine .NET-Serie zum Thema Threads geht heute in die zweite Runde. Nachdem es gestern um das Erzeugen von Threads ging, handelt Teil zwei von der Thread-Verwaltung. Dabei unterscheidet man zwischen Beenden, Warten, Anhalten und Fortsetzen.
Beenden von Threads: Dies geschieht am einfachsten, indem ein Thread ganz regulär während des Programmablaufs verlassen wird und die Anwendung wieder zum Masterthread zurückkehrt. Dies gibt der Common Language Runtime (CLR) die Möglichkeit, die notwendigen Aufräumarbeiten durchzuführen. Allerdings muss manchmal ein anderer als der gerade laufende Thread beendet werden. Daher hat Microsoft den .NET-Thread-APIs eine Methode spendiert, die sich Abort() nennt, mit deren Hilfe ein laufender Thread abgebrochen werden kann. Beim Aufruf von Abort() wird automatisch eine ThreadAbortException ausgelöst.
Der Aufruf von Abort() zieht übrigens eine Reihe verschiedener Dinge nach sich. Dazu gehört die Fähigkeit des Threads, den eigenen Abbruch zu vereiteln, indem er innerhalb des Exception-Handlers die Methode System.Threading.Thread.ResetAbort aufruft. Daneben besteht die Möglichkeit, innerhalb eines Codeblocks mithilfe von finally weiteren Quellcode ausführen zu lassen, was im ungünstigsten Fall eine beachtliche Verzögerung des Thread-Abbruchs nach sich zieht. Aus diesen beiden Gründen sollte man zur Laufzeit überprüfen, ob ein bestimmter Thread auch vollständig abgebrochen wurde. Hierfür steht die Join-Methode zur Verfügung.
Workshop: Alles über .NET-Threads – Teil 1
Ende Januar, im Anschluss an die OOP 2009, habe ich hier eine kleine Miniserie mit dem Titel “Multicore-Programmierung im .NET-Umfeld” veröffentlicht. Was dabei ein wenig zu kurz kam sind die technischen Aspekte, die daraus resultieren. Wie beispielsweise Threads mithilfe des .NET-Frameworks erzeugt, verwaltet und synchronisiert werden. Und über die vorhandenen Thread-Pools habe ich ebenfalls zu wenig erzählt.
Das alles werde ich ab heute in Form einer Miniserie mit der Überschrift “Alles über .NET Threads” nachholen. Der erste Teil beschäftigt sich mit dem Thema “Threads erzeugen”.
Da die .NET-APIs etwas “schlanker” sind als ihre Win32-Brüder und -Schwestern, gestaltet sich das Erzeugen eines .NET-Threads relativ einfach. Dies sieht wie folgt aus:
using System.Threading;
[Definition von Variablen]
Thread t = new Thread( new ThreadStart( ThreadFunc ));
Der Aufruf von ThreadStart() erzeugt einen neuen Thread. Der Parameter ist eine Delegat namens ThreadFunc. Übrigens: In C# ist ein Delegat identisch mit der Adresse einer Funktion in C. Der Thread endet dann an der Stelle, sobald ThreadFunc() endet. Doch damit nicht genug. Für eine fehlerfreie Ausführung des Threads muss weiterer Code implementiert werden:
t.Start() — Dies startet den Thread unter .NET explizit (was z. B. unter Win32 nicht erforderlich ist).
Thread.Sleep( 40 ) — Dies unterbricht den (seriellen) Hauptthread für die Dauer der Zeiteinheit in der Klammer, die in Millisekunden angegeben wird. Dies kann in der Praxis jedoch erheblich von der tatsächlich benötigten Zeit abweichen.
[aufzurufende Funktion] — Diese Funktion wird parallel auf den vorhandenen Prozessorkernen ausgeführt.
Thread.Sleep( 0 ) — Dies beendet den parallelen Thread und reaktiviert den “schlafenden” Hauptthread.
Mögliche Thread-Modi und deren Folgen
Multicore-Programmierung ist sicherlich kein einfaches Ding. Zu viel kann dabei passieren und schiefgehen, wenn man bestimmte Regeln nicht beachtet. Daher starten wir heute einen Mehrteiler, der sich mit den Details der Multicore-Programmierung beschäftigt. Im ersten Teil geht es um die fundamentale Frage, welche Zustände ein Thread während des Programmablaufs annehmen kann.
Im ersten Schritt wird beispielsweise vom Thread Manager ein Thread erzeugt und in den Bereit-Zustand versetzt (“ready”). Dort verharrt der Thread so lange, bis er eine Anweisung, eine Funktion oder ähnliches ausführen soll. Darum kümmern sich entsprechende Programmanweisungen wie zum Beispiel die Pragma-Methoden der OpenMP-basierten Programmierung.
Im zweiten Schritt kommt es zur Ausführung der entsprechenden Quellcodestelle. Währenddessen muss der betreffende Thread möglicherweise auf einen anderen Thread oder ein Datum eines parallel ablaufenden Threads warten. Dann begibt er sich in den Wartend-Modus, bis der benötigte Thread beendet ist oder dessen Ergebnis vorliegt. Anschließend wird der angehaltene Thread weiter ausgeführt oder wieder in den Bereit-Zustand versetzt, da der Thread zu einem späteren Zeitpunkt nochmals benötigt wird.
Ach ja: Gerade dieser Wartend-Modus bereitet vielen Programmierern erhebliche Probleme, da hierbei Dead Locks, Data Races oder andere unvorhersehbare Ereignisse auftreten können. Wie gut, dass es hierfür Tools wie den Thread Checker oder Parallel Inspector gibt, die solche Schwachstellen aufspüren können.
Workshop: parallel Programmieren mit OpenMP
Über OpenMP habe ich hier schon des öfteren gebloggt, aber noch nie so wirklich im Detail. Das wird sich heute schlagartig ändern, denn ein lieber Kollege (danke, Edmund!) hat mir am Wochenende einen sehr anschaulichen Artikel zukommen lassen, der mir als Grundlage für das heutige Posting dient. Den kompletten Beitrag samt Programmierbeispiel anhand der Kreiszahl Pi gibt es als PDF zum Download.
Zunächst einmal: OpenMP liegt in der Version 3.0 vor und stellt eine standardisierte Programmierungsmethode dar. In Sachen Komplexität bewegt sie sich zwischen den herkömmlichen Threads – also API-Threads, PThreads oder WinThreads – und den höher entwickelten Intel TBB oder der geplanten Parallel Pattern Library (PPL), die von Microsoft kommen wird.
Daraus ergeben sich zwei wesentliche Aspekte: OpenMP ist weitaus einfacher zu verstehen, zu implementieren und zu testen als die Low-Level-Threads, bei denen sich der Programmierer um viele Dinge selbst kümmern muss, was das Ganze natürlich extrem fehleranfällig macht.
Die Kehrseite von OpenMP ergibt sich aus der geringeren Komplexität: Nicht alle Programmieraufgaben lassen sich mit OpenMP erledigen, was den Entwickler natürlich ein wenig einschränkt. OpenMP versteht sich vor allem sehr gut auf das Parallelisieren von rechenintensiven Schleifenkonstrukten.


