Title: Software Transactional Memory
1Software Transactional Memory
- How D can make concurrent programming a piece of
cake - Bartosz Milewski
- D Programming Language
2Obvious Things
- Multi-Core is here to stay
- Programmers must use concurrency
- (Dead-) Lock Oriented Programming is BAD
- New paradigm badly needed
3The Problem
- void Toggle ()
-
- if (x 0)
- x 1
- else
- x 0
- Fetch value of x
- Store it in a temporary
- Compare with zero
- Based on the result, write new value
What if, in the meanwhile, the original x was
modified by another thread? Write is based on
incorrect assumption!
4Logically Speaking
- Theorem
- Hypothesis x 0 (read x, compare to 0)
- Conclusion x 1 (write 1 into x)
- Problem Hypothesis invalidated before conclusion
reached - Must re-check the hypothesis before writing!
5Transaction
- Delay checking Log read values for later
- Must re-check before writing Log the intent to
write (speculative writes) for later execution - Verify hypothesis Are read values unchanged?
- Reach conclusion Execute writes from log to
memory
6What Does It Buy Us?
- Concurrency issues postponed to the commit phase
(read-check and write) - Commit uses generic code, which can be optimized
and tested once for all - User code simple, less error-pronecode as if
there were a single global lock - Increased concurrencyexecutes as if every word
were separately locked - No deadlocks!
7STM in a Nutshell
- Start a transaction create log
- Speculative executionreads and writes logged
- Commit phase (atomic)
- Read-check
- Write to memory
- If failure, restart transaction
8Composability
- Combining atomic operations using locksalmost
impossible! - Atomic (transacted) withdrawalatomic
acc.Withdraw (sum) - Atomic depositsimilar
- Atomic transferatomic accOne.Withdraw
(sum) accTwo.Deposit (sum)
9Blocking Transactions
- Example Producer/Consumer
atomic Item item pcQueue.Get () Item
Get () atomic // PCQueue method if
(_queue.Count () 0) retry else
return _queue.pop_front ()
10Retry
- Restart transaction without destroying the log
- Make the read-log globally available
- Block until any of the logged read locations
changes - Every commit checks the read-sets of blocked
transactions and unblocks the ones that overlap
with its write-set
11The Beauty of Retry
- Consumer doesnt have to specify what its
waiting for - Producer doesnt have to signal anybody
- Composability Wait for two items
atomic item1 pcQueue.Get () item2
pcQueue.Get ()
12Object-Based STM
- Transactable (atomic) objects
- Visible as opaque handles
- Can be opened only inside transaction
- Open (for read) returns a const pointer to the
actual object - Open for write clones the object and returns
pointer to the clone
atomic struct Foo int x atomic Foo f (new
Foo) // an opaque handle atomic // start
transaction Foo foo f.open_write ()
foo.x
13Cloning
- Deep copy of the object
- Embedded atomic handles are copied but not the
objects they refer to - Transactable data structures build from small
atomic objects (tree from nodes) - Value-semantic objects (e.g. structs) cloned by
copy construction
14Type System Support
- Struct or class marked as atomicall methods
(except constructor) atomic - Open and open_write can be called only inside a
transactioni.e. from inside - Atomic block
- Atomic function/method
- Atomic function/method may only be called from
inside a transaction
15Singly-Linked List
struct Slist // not atomic this () //
Insert sentinels SNode last new SNode
(Infin, null) _head new SNode (MinusInfin,
last) // atomic methods const (SNode)
Head () const atomic retrun _head.open
() void Insert (int i) atomic void Remove
(int i) atomic private atomic SNode _head //
atomic handle
16Node
struct SNode atomic public this (int i, const
(SNode) next) _val i _next
next // atomic methods (by default) int
Value () const return _val const (SNode)
Next () const return _next.open () Snode
SetNext (const (SNode) next) SNode self
this.open_write () self._next
next return self private int _val ato
mic Snode _next
17Insert
atomic myList.Insert (x) //
transactioned void Insert (int i)
atomic const (SNode) prev Head () //
sentinel const (SNode) cur prev.Next
() while (cur._val lt i) prev cur cur
prev.Next () assert (cur ! 0) // at
worst high sentinel SNode newNode new SNode
(i, cur) (void) prev.SetNext (newNode)
18Implementation
- Versioning and Locking
- Global Version Clock (always even)
- Version numbers always increase
- (Hidden) version/lock word per atomic object
(lock is the lowest bit) - Consistent Snapshot maintenance
- Version checks when opening an object
- Read-check during commit
19Consistent Snapshot
- Transaction starts by reading Version Clock into
the transactions read-version variable - Open object
- Check the object lock (bit). If taken, abort
- Check object version number. If its greater than
read-version abort
20Logging
- Every open is recorded in read-log
- Pointer to original object (from which its
version lock can be retrieved) - Every open_write is recorded in read-log and
write_log - Pointer to original object
- Pointer to clone
- Okay to call open_write after open (read)
21Commit
- Lock all objects recorded in the write-log
- Bounded spinlock on each version lock
- Increment global Version Clockstore as
transactions write-version - Sequence Point (if transaction commits, thats
when it happened) - Read-check
- Re-check object version numbers against
read-version
22Commit Writes
- For each location in the write-log
- Swap the clone in place of original
- Stamp it with write-version
- Unlock
23D Work
- We have C implementation (Brad Roberts port of
GPLd Dice, Shalev, and Shalit) - Write D implementation
- Modify type system
24Bibliography
- Dave Dice, Ori Shalev, and Nir Shavit.
Transactional Locking II - Tim Harris, Simon Marlow, Simon Peyton Jones, and
Maurice Herlihy. Composable Memory Transactions.
ACM Conference on Principles and Practice of
Parallel Programming 2005 (PPoPP'05). 2005.