Implementation notes

The transaction stack

The implementation is based around a transaction stack that records each change to a container. Each change is recorded in a struct

struct Transaction
{
    TransactionalContainer *container;
    void *object;
    enum Action { created, deleted, none } action;
};

that is pushed onto a vector. All containers share the same stack.

Transactional containers

When a container adds an item, it calls the function log_insert(), which pushes an operation onto the transaction- stack, with action=created, container=the container holding the item, and object=the node of the item being inserted.

When a container removes an item, it calls log_delete(), which pushes an operation onto the transaction-stack, with action=deleted, container=the container containing the item, and object=the node that was deleted. The deleted item is not actually deleted at this point in case it needs to be reinserted back into the container on roll-back. The container makes sure that the deleted object holds enough context to be inserted back into the container in the right position if need be.

Containers inherit from the atomic::container class:

class container
{
public:
    virtual ~container();
    virtual void destroy(void*)=0;
    virtual void unlink(void*)=0;
    virtual void relink(void*)=0;
};

When an insertion operation is rolled back, the transaction manager calls container->unlink() to remove the item from the container, and then container->destroy() to destroy the object.

When a deletion operation is rolled back, the transaction manager calls container->relink() which inserts the item back into the container.

Cleanup

When the outermost transaction has been finished, the transaction stack can be emptied. This scans the transaction stack, and calls container->destroy() for all delete operations. Containers can no longer be rolled back after a cleanup has occurred.

Cleanup means that objects are not kept for longer than necessary, and that the memory used by deleted items is deallocated so that the program will not run out of memory. In fact if transactions are short, there is very little memory overhead at all.

Performance

The benchmark compares the performance of the atomic containers versus their std:: equivalents, using the atomic_bench.cpp program.  (download)

The output was from 1000000 operations, performed using gcc 3.4.4, on a 1.7GHz Centrino processor.

Container Operation std:: (ms) atomic:: (ms) atomic/std
list<int> insert 750 937 1.25
  delete 297 313 1.05
list<string> insert 3469 3718 1.07
  delete 985 937 0.95
vector<int> insert 16 375 23.4
vector<string> insert 3688 4516 1.22
set<int> insert 1172 1484 1.26
  delete 375 390 1.04
set<string> insert 5765 5890 1.02
  delete 1062 1016 0.96
map<int,int> insert 2203 2452 1.11
  delete 375 390 1.04
map<string,string> insert 9517 9813 1.03
  delete 1375 1391 1.01

This shows that there is overhead in using atomic containers, which is to be expected.

Further work

Restore points.

Implement thread safety.