C++ transactional memory

C++ transactional memory


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:

  • Global synchronization for specified code blocks.
  • The ability to roll back changes applied during a transaction.

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:

  • Functions called within a transaction must be marked as `transaction_safe`, or the compiler should deduce it.
  • No assignment, initialization, or reading from `volatile` objects.
  • No inline assembly (`asm`).

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:

  • 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

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.

Ben FrantzDale

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?!

Eran Gilad

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.

Chris Ryan

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"

Ashutosh Singh

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

Aliaksandr Zavatski

System Software Developer, Embedded Systems Security Architect, С++/Python/TEE/CCA/Virtualisation

2 个月

Does transactional memory without HW support make any sense?

要查看或添加评论,请登录

Nikolai Kutiavin的更多文章

  • Golang generic implementation for Producer-Consumer

    Golang generic implementation for Producer-Consumer

    Producer-consumer design pattern is well-known in software development. In a few words, one object produces values, and…

    6 条评论
  • Factory Method in Python with blackjack and ?h?o?o?k?e?r?s? decorators.

    Factory Method in Python with blackjack and ?h?o?o?k?e?r?s? decorators.

    Design patterns help organize code in a better way, improving readability and maintainability. Many design patterns can…

    8 条评论
  • Non-blocking synchronization for std::vector

    Non-blocking synchronization for std::vector

    In my previous post, I described how to protect a std::vector using std::mutex. It was straightforward.

    13 条评论
  • Unit-test in C++: what should you know.

    Unit-test in C++: what should you know.

    Unit tests are important for a single reason - they prove that a single component works as expected in isolation. If a…

    9 条评论
  • Behavior vs Data concurrency protection

    Behavior vs Data concurrency protection

    Multithreading is one of the hottest topics in C++. However, the most crucial question isn't just about running code…

  • Why preprocessor directives are evil

    Why preprocessor directives are evil

    I'll start with a simple quiz: Do you think the code below is correct? Does it make more sense now? Preprocessor…

    42 条评论
  • Perl with classes

    Perl with classes

    Originally developed as a procedural language, Perl was later adapted to support modern Object-Oriented Programming…

    4 条评论
  • r-value reference: when and why?

    r-value reference: when and why?

    C++ has two commonly used types of references: l-value and r-value. An l-value reference points to an object with a…

    6 条评论
  • Network transport protocol: reliability and message-orientation out of the box

    Network transport protocol: reliability and message-orientation out of the box

    If I asked you to name a reliable network protocol for the transport layer, what would be the first one that comes to…

    4 条评论
  • File-backed allocator for STL containers

    File-backed allocator for STL containers

    The STL is renowned for its modular design, allowing universal algorithms to be applied to a wide range of containers…

社区洞察

其他会员也浏览了