|
Personal Info:
Joe  leads the architecture of an experimental OS's developer platform, where
he is also chief architect of its programming language. His current mission is to enable
writing large-scale software that is reliable, secure, and scalable by-construction. Before this, Joe
founded the Parallel Extensions to .NET project.
He has been granted 19 patents, with 49 pending. When not working, Joe enjoys travelling with his wife,
writing books, writing music,
studying music theory & mathematics, and doing anything involving food & wine.
My books
My music
Disclaimer:
The content of this site are my own personal opinions and do
not represent my employer's view in anyway.
© 2012, Joe Duffy
|
|
 Saturday, May 28, 2005
My implementation of Software Transactional Memory (STM) on Rotor is coming along nicely. I mentioned it briefly here and here. I've taken a different approach than most, actually taking advantage of the JIT and EE to do my dirty work for me. Some prefer to stay inside the cozy confines of managed code, but I'd like to understand better the impact that my design might have on non-transactional code. I admit that my approach is likely not viable for real commercial use mostly due to its intrusive nature. Unless all code were transactional, which it's not. But this is partly what I'd like to understand better.
Most people confuse the idea of memory transactions with, say, database transactions. The concepts are very similar, but memory transactions are about dealing with concurrency at a much finer grained level (just your machine). Rather than using locks to protect shared memory, STM prefers to enable possibly conflicting operations to happen (inside a transaction), and then to resolve them by comparing transaction records to memory at commit time. The theory behind this work has been heavily influenced by database software, and the techniques it uses for its own memory consistency models. You can read more about STM on Tim Harris's site (MSR "smart dude"), e.g. these research papers.
My particular STM design affects the JIT, some core parts of the EE, requires new managed classes, and also a bunch of new FCalls. I'm writing up a paper on this project as I go, but in a few bullets here are the main points:
- Blocks of code can be marked as "atomic", along with options such as what style of locking (optimistic, pessimistic), granularity (memory location/field, object), and retry semantics (0..n).
- Atomic blocks get hoisted into methods of their own and annotated with a System.Threading.AtomicTransactionAttribute. This requires a hack to the C# compiler; for now, I've simply required that people write the method and tag it with [AtomicTransaction] on their own.
- The runtime knows about [AtomicTransaction] and treats these methods differently (details below).
- Note: I would like to understand how best to take advantage of and integrate with System.Transactions, but haven't gotten around to it yet.
- The MethodTable in the EE now contains a second set of vtable slots.
- This second vtable contains atomic versions of the JITted code. We'll see what this means later.
- All of the atomic vtable slots get pre-populated with a special JIT stub that invokes the JIT with a flag to indicate it desires atomic semantics. If you never call a method atomically, the stub will never get replaced with the JITted code, so this might not be as heavyweight as you first thought.
- Any methods marked with [AtomicTransaction] get the atomic JIT stub for their ordinary vtable slot too.
- When the JIT is called with the atomic flag, it does a couple things out of the ordinary:
- Method calls dispatch using the atomic vtable instead of the plain ole' vanilla vtable.
- Any calls to ldfld, ldsfld, ldelem, ... get routed through a runtime helper. This helper knows about previous reads/writes in the same transaction by consulting our transaction records in TLS. If we have written, it retrieves that. Otherwise, it reads the underlying memory, records the value read, and loads the value.
- Any calls to stfld, stsfld, stelem, ... get run through a similar runtime helper. This simply records the written value in the active transaction.
- Then there are Commit/Rollback APIs which resolve conflicting read/writes, and actually writes any updates to the memory. It does this through a nonblocking algorithm (assuming optimistic locking) which is guaranteed to never deadlock.
- If conflicts are found (memory value is different than the recorded read), it consults the transaction's retry options. If we haven't spent our retries left count, we loop back and attempt the transaction over again.
- Otherwise, we blit any writes to memory, mark the transaction as committed, and move on.
- Note: I also support nested transactions which changes the behavior of commit slightly. Firstly, when a new [AtomicTransaction] method is reached, we allocate a child transaction. Any reads must consult the parent chain for writes that parent transactions have recorded. Then during commits for children, we simply copy the contents of child reads/writes to the parent. The parent is then responsible for detecting consistency with memory when it decides to commit.
|
|
Recent Entries:
Search:
Browse by Date:
Browse by Category:
Notables:
|