Transactional Memory Robert Brunel Technische Universität München 25.09.2009
Transactional Memory: Grundbegriffe Memory-Transaction: Eine Folge von Zugriffen auf den Arbeitsspeicher, die atomar und isoliert ausgeführt wird Atomarität: Alles oder nichts -Prinzip Bei commit: Alle Schreibzugriffe der T. werden gültig Bei abort: Alle Schreibzugriffe werden verworfen; T. hat keine Auswirkungen Isolation: Vor commit kann keine andere T. die Änderungen einer Transaktion sehen Serialisierbarkeit: Von außen betrachtet committen die Transaktionen in einer seriellen Reihenfolge
Programmieren mit TM void deposit(account, amount) { lock(account); int t = bank.get(account); t = t + amount; bank.put(account, t); unlock(account); Bisher: Explizite Verwaltung von Sperren (Locks) void deposit(account, amount) { atomic { int t = bank.get(account); t = t + amount; bank.put(account, t); Mit neuem Sprachkonstrukt atomic: Deklarative Synchronisation System setzt Synchronisation um
Beispiel: Thread-sichere Datenstruktur (1) HashMap bei Java 1.4: nicht Thread-sicher! public Object get(object key) { int idx = hash(key); HashEntry e = buckets[idx]; while (e!= null) { if (equals(key, e.key)) return e.value; e = e.next: return null;
Beispiel: Thread-sichere Datenstruktur (2) Synchronisierte HashMap: Explizites, grobgranulares Sperren durch Programmierer public Object get(object key) { synchronized (mutex) { return map.get(key); Vorteile: Thread-sicher, einfach zu programmieren Nachteile: Stark begrenzte Nebenläufigkeit u. Skalierbarkeit
Beispiel: Thread-sichere Datenstruktur (3) Concurrent HashMap (Java 5): Feingranularer Parallelismus, u.a. nebenläufige Lesezugriffe möglich Nachteile: komplex, fehlerträchtig
Beispiel: Thread-sichere Datenstruktur (4) Transaktionale HashMap: public Object get(object key) { atomic { return map.get(key); TM-System kontrolliert Nebenläufigkeit, garantiert Atomarität Vorteile: Thread-sicher, einfach zu programmieren Performanz? Skalierbarkeit?
Beispiel: Thread-sichere Datenstruktur (5) Vergleichende Messungen zu Performanz und Skalierbarkeit:
Vorteile von TM (1) Einfach zu verwendendes Synchronisierungskonstrukt atomic Performanz, Skalierbarkeit vergleichbar mit feingranularem Sperren TM erzeugt keinen neuen Parallelismus, hilft aber zu nutzen was schon da ist. Ziel: 90% des Vorteils bei 10% der Arbeit!
Vorteile von TM (2) Komposition von Softwaremodulen: Einfach, sicher, skalierbar void transfer(a, B, amount) synchronized(a) { synchronized(b) { withdraw(a, amount); deposit(b, amount); void transfer(a, B, amount) atomic { withdraw(a, amount); deposit(b, amount); void transfer(b, A, amount) synchronized(b) { synchronized(a) { withdraw(b, amount); deposit(a, amount); void transfer(b, A, amount) atomic { withdraw(b, amount); deposit(a, amount);
Vorteile von TM (3) Automatische Fehlerbehandlung! void transfer(a, B, amount) { synchronized(bank) { try { withdraw(a, amount); deposit(b, amount); catch (Exception e1) { /* */ catch (Exception e2) { /* */ void transfer(a, B, amount) { atomic { withdraw(a, amount); deposit(b, amount);
atomic{ vs. lock()+unlock() atomic{ High-Level-Deklaration: Programmierer spezifiziert Anforderung atomar + isoliert ; System setzt diese bestmöglich um Keine Angabe zu Umsetzung bzw. Blocking-Verhalten lock()+unlock() blockierendes Low-Level-Primitiv Stellt für sich alleine nicht Atomarität oder Isolation sicher! Kann verwendet werden um atomic zu implementieren Wird auch zu anderen Zwecken neben Atomarität genutzt Man kann also nicht einfach alle lock/unlock- Vorkommnisse durch atomic ersetzen!
Implementierung von TM TM-System muss Atomarität und Isolation sicherstellen, ohne Nebenläufigkeit zu opfern Optionen: Software TM Hardware-beschleunigte STM-Systeme Hardware-basierte TM-Systeme Hybride TM-Systeme Zwei grundlegende Anforderungen/Mechanismen: Datenversionierung Konflikterkennung u. -auflösung
Datenversionierung (1) Verwaltung von uncommitted (neuen) und committed (alten) Daten für jede Transaktion Eager Versioning Speicherstellen beim Schreiben direkt aktualisieren Informationen zum Rückgängig-Machen in Undo-Log merken Locks verhindern, dass anderer Code die uncommitteten Daten sieht Lesezugriffe und Commit schnell, Aborts langsam alternativ: Lazy Versioning (Schreibpuffer-basiert)
Datenversionierung (2): Eager Versioning
Konflikterkennung u. -auflösung (1) Erkennung von Read-Write- und Write-Write-Konflikten TM-System muss dazu Speicherzugriffe verfolgen: Read-Set: Adressen, die innerhalb der T. gelesen werden Write-Set: Adressen, die innerhalb T. geschrieben werden Granularität der Konflikterkennung: Objekt-, Element-/Feld-, Cache-Line-Granularität; oder aber gemischt, je nach Typ
Konflikterkennung u. -auflösung (2) Pessimistische Erkennung Auf Konflikte während Lese-/Schreibzugriffen prüfen Bei Konflikten entscheiden ob T. anzuhalten oder abzubrechen ist alternativ: Optimistische Erkennung Erst bei Commit-Versuch auf Konflikte prüfen (Read-/Write-Set validieren über Versionsnummern)
Software Transactional Memory (STM) atomic { a.x = t1; a.y = t2; if (a.z == 0) { a.x = 0; a.z = t3; tmtxnbegin(); tmwr(&a.x, t1); tmwr(&a.y, t2); if (tmrd(&a.z) == 0) { tmwr(&a.x, 0); tmwr(&a.z, t3); tmtxncommit(); Software-Barrieren ermöglichen TM- Buchführung Versionierung, Verfolgung der Read-/Write-Sets, Commit, Verwendung von Sperren, Zeitstempeln, Kopien der Daten
Beispiel für eine STM-Umsetzung (1) Dynamisch: Alle Speicherzugriffe im Programm werden dynamisch behandelt (vs. statisch) Lock-basiert: Implementierung benutzt blockierende Sperren (vs. non-blocking) Eager Versioning kann verwendet werden
Beispiel für eine STM-Umsetzung (2) U. a. benötigte Datenstrukturen: Transaction-Descriptor (pro Thread) Enthält Read-Set, Write-Set, Undo-Log, ggf. Schreibpuffer Verwendet für Konflikterkennung, Commit, Abort, Transaction-Record (32-bit, je TM-Datum) dient der Verfolgung des transaktionalen Daten-Zustandes Status: Durch Schreiber gesperrt oder Nicht gesperrt Wenn gesperrt: Zeiger auf Besitzer-Transaktion Wenn nicht gesperrt: Zeitstempel des letzten Commits
Beispiel für eine STM-Umsetzung (3) Zuordnung von Transaction Records zu Daten: class Foo { int x; int y; a.) In Objekt eingebettet: vtbl TxR x y b.) Hashing der Felder oder Arrayelemente in eine globale Tabelle: f(obj.hash, field.index) vtbl Hash x y TxR 1 TxR 2 TxR n
Beispiel für eine STM-Umsetzung (4) Zur Versionsverfolgung benutzte Zeitstempel: Globaler Zeitstempel Wird erhöht, wenn eine schreibende T. committet Lokaler Zeitstempel für jede Transaktion Wird beim Validieren der T. jeweils auf globalen Zeitstempel gesetzt
Beispiel für eine STM-Umsetzung (5) Lesezugriff: optimistisch 1. Datum direkt von Speicherstelle lesen (eager) 2. Datum validieren: Prüfen ob entsperrt und Version <= Lok. Zeitstempel Wenn nicht, alle Daten im Read-Set auf Konsistenz überprüfen 3. In Read-Set einfügen 4. Wert zurückgeben Schreibzugriff: pessimistisch 1. Datum validieren. Prüfen ob entsperrt und Version <= Lok. Zeitstempel 2. Sperre akquirieren 3. In Write-Set einfügen 4. Eintrag im Undo-Log erstellen 5. Wert direkt an Speicherstelle schreiben (eager)
Beispiel für eine STM-Umsetzung (6) Validierung des Read-Set: 1. Glob. Zeitstempel ermitteln 2. Für jedes Datum im Read-Set: Wenn von anderer T. gesperrt oder Version > Lokaler Zs: Abort! 3. Lokalen Zs auf glob. Zs setzen Commit: 1. Glob. Zs atomar um 2 erhöhen 2. Wenn alter glob. Zs > lok. Zs: Read-Set validieren 3. Für jedes Datum im Write-Set: Lock freigeben Version um 2 erhöhen
Beispiel-STM-Umsetzung (7) - Illustration Transaktion 1: atomic { t = foo.x; bar.x = t; t = foo.y; bar.y = t; commit Kopiert das Objekt foo in das Objekt bar Liest <foo, 3> Schreibt <bar, 5> Undo <bar.x, 0> Undo <bar.y, 0> foo 3 hdr x = 9 y = 7 T1 75 hdr x = 90 y = 70 bar T2 wartet Transaktion 2: atomic { t1 = bar.x; t2 = bar.y; abort Sollte bar entweder als (0,0) oder als (9,7) lesen Liest <bar, 5> Liest <bar, 7>
Herausforderungen bei STM Klonen der Funktionen Overhead durch Software-Barrieren Single-Thread-STM-Performance 1,8- bis 5,6-fach langsamer gegenüber sequentieller Ausführung Optimierungspotential bei Softwarebarrieren: Verhinderung von redundantem Logging/Locking Standard- und STM-Spezifische Compiler-Optimierungen Interaktion mit nicht-transaktionalem Code Weak Atomicity vs. Strong Atomicity Starke Atomizität bei STM schwer zu gewährleisten
Weitere Herausforderungen bei TM Robustes Contention Management Schlechte Performanz bei langlaufenden Transaktionen Verschachtelung von Transaktionen Nicht-umkehrbare Operationen in atomic-blöcken I/O-Geräte, I/O-Register, Performanz (Compileroptimierung, Hardwarebeschleunigung) Werkzeuge für Debugging und Performanzanalyse
Literatur Transactional Memory: Concepts, Implementations & Opportunities. Christos Kozyrakis, Stanford University (2008) Unlocking Concurrency. A. Adl-Tabatabai, Intel; C. Kozyrakis, Stanford University; B. Saha, Intel. ACM Queue (2006) TCC-Prototypen: http://tcc.stanford.edu/prototypes/ Software Transactional Memory - Why is it only a research toy? Calin Cascaval et. al., ACM Queue (2008)