C++ transactional memory
Nikolai Kutiavin
Professional problem solver in C++ and beyond | C++ design and architecture expert
Sometimes I feel like an archaeologist, and today I’ll share one of my findings: an old proposal for the C++ standard, N1613. It discusses transactions for C++. Yes, you read that right—transactions. The good news is, this is partially supported by GCC, though not fully.
What are transactions?
A transaction typically involves one or more actions that either complete successfully together, or none of them have any side effects. If several variables are updated within a transaction, either all variables get new values, or they all retain their old values.
What’s inside the proposal?
The proposal introduces the following language constructs:
With transactions, you get a total order of transaction execution and the ability to roll back changes. All transactions execute in a global order, one after another, eliminating the need for mutexes or other synchronization mechanisms. This behavior is illustrated below:
What do I need to get started?
This feature is partially implemented in GCC since version 4.6. You'll need that version of the compiler, the `itm` library, and the compiler flag `-fgnu-tm`. Below is an example of the CMake configuration:
Just synchronization
If you only need synchronization for a few operations across multiple threads, consider the new keyword `synchronized`. It marks a block to be synchronized, meaning each block with this keyword is executed in a global order, as if protected by a global mutex. This can be used to create a thread-safe counter without explicit thread synchronization, as shown here:
There are no restrictions on the operations within a `synchronized` block, so printing and other operations are allowed.
Atomic transactions
While synchronization is useful, what's more exciting is the ability to cancel applied modifications if a condition isn't met. Let’s say I have an array, and I want to assign the sum of all elements to each element. If the sum exceeds a maximum value, the operation is aborted, and all previously modified elements revert to their previous values.
To create a transaction block, use the `__transaction_atomic` statement. To roll back changes, use `__transaction_cancel`. In `calculateNext()`, the sum of the array is calculated and assigned to each element. If the sum exceeds the defined maximum, all changes are rolled back, and an exception is thrown:
The method `sum()` is marked `transaction_safe` to indicate that it can be used inside a transaction block:
One key point: `__transaction_atomic` blocks are globally synchronized, so there's no need for additional mutexes or atomics. However, there are some limitations:
Additionally, you can't return values if a transaction is canceled, though exceptions can still be thrown to indicate a rollback.
Alternatives
For synchronization, you can use mutexes or atomic variables if you're dealing with individual types or small objects. This approach eliminates the global ordering requirement and allows for concurrent operations on unrelated variables, improving performance.
For rollback operations, there aren’t many alternatives, but custom solutions might be faster and impose fewer restrictions.
Performance comparison
Let’s compare the performance of this new feature against alternatives.
For a `synchronized` block versus a mutex implementation with 200,000 iterations:
For atomic transactions versus a mutex with an object copy that updates only if the condition is met:
Conclusion
You can find the source code in my repo: https://github.com/sqglobe/transaction-memory.
While transactional memory is an intriguing concept and opens up new programming paradigms, I personally would consider alternatives due to performance reasons, especially with the current software-only implementation.
Multidisciplinary Innovator, High-Performance Software Development, 3D Reconstruction, Speaker: C++ and Intellectual Property
2 个月What does/did this feature map into in terms of machine code?!
Building a distributed database at Regatta | Organizing C++ communities
2 个月Transactional memory was once a hot topic, at least in academia, as it seemed to have made parallel programming easier. Several software implementations were created and even a hardware one in Intel CPUs, but AFAIK none got much traction. Where it could have been a perfect fit was working with persistent memory. While that tech is also on hold, it will hopefully have a comeback with CXL. If I were taking a fresh look at TM, I would have done it on a heterogeneous memory environment based on CXL.
Seasoned Senior Software Engineer, Modern C++, Available ?? Technical Speaker ?? ISO-Cpp/WG21 Standards Committee Member
2 个月This is NOT a standard feature. This is an "experimental thing" https://en.cppreference.com/w/cpp/language/transactional_memory "Experimental Feature?The functionality described on this page is part of the Transactional Memory Technical Specification ISO/IEC TS 19841:2015"
Staff Software Engineer, Looking for C++ Software Roles, (Platform Software | Robotics)
2 个月Performance wise seems not so great though it looks very handy to build applications and avoid errors Sounds similar to "synchronized_value": https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p0290r4.html Let’s compare the performance of this new feature against alternatives. For a `synchronized` block versus a mutex implementation with 200,000 iterations: Transaction memory: 6.47201 ms Mutex: 2.74545 ms For atomic transactions versus a mutex with an object copy that updates only if the condition is met: Transaction memory: 274.483 ms Mutex: 126.692 ms
System Software Developer, Embedded Systems Security Architect, С++/Python/TEE/CCA/Virtualisation
2 个月Does transactional memory without HW support make any sense?