mardi 1 novembre 2016

Does standard C++11 guarantee that `volatile atomic

As known, std::atomic and volatile are different things.

There are 2 main differences:

  1. Two optimizations can be for std::atomic<int> a;, but can't be for volatile int a;:

    • fused operations: a = 1; a = 2; can be replaced by the compiler on a = 2;
    • constant propagation: a = 1; local = a; can be replaced by the compiler ona = 1; local = 1;
  2. Reordering of ordinary reads/writes across atomic/volatile operations:

    • for volatile int a; any volatile-read/write-operations can't be reordered. But nearby ordinary reads/writes can still be reordered around volatile reads/writes.
    • for std::atomic a; reordering of nearby ordinary reads/writes restricted based on the memory barrier used for atomic operation a.load(std::memory_order_...);

I.e. volatile don't introduce a memory fences, but std::atomic can do it.

As is well described in the article:

I.e. std::atomic should be used for concurrent multi-thread programs (CPU-Core <-> CPU-Core), but volatile should be used for access to Mamory Mapped Regions on devices (CPU-Core <-> Device).


But if required both has unusual semantics and has any or all of the atomicity and/or ordering guarantees needed for lock-free coding, i.e. if required volatile std::atomic<>, require for several reasons:

  • ordering: to prevent reordering of ordinary reads/writes, for example, for reads from CPU-RAM, to which the data been written using the Device DMA-controller

for example:

char cpu_ram_data_written_by_device[1024];
device_dma_will_write_here( cpu_ram_data_written_by_device );

// physically mapped to device register
volatile bool *device_ready = get_pointer_device_ready_flag();

//... somewhere much later
while(!device_ready); // spin-lock (here should be memory fence!!!)
for(auto &i : cpu_ram_data_written_by_device) std::cout << i;

  • spilling: CPU write to CPU-RAM and then Device DMA-controller read from this memory: http://ift.tt/10GYNCz

example:

char cpu_ram_data_will_read_by_device[1024];
device_dma_will_read_it( cpu_ram_data_written_by_device );

// physically mapped to device register
volatile bool *data_ready = get_pointer_data_ready_flag();

//... somewhere much later
for(auto &i : cpu_ram_data_will_read_by_device) i = 10;
data_ready=true; //spilling cpu_ram_data_will_read_by_device to RAM, should be memory fence

  • atomic: to guarantee that the volatile operation will be atomic - i.e. It will consist of a single operation instead of multiple - i.e. one 8-byte-operation instead of two 4-byte-operations

For this, Herb Sutter said about volatile atomic<T>, January 08, 2009: http://ift.tt/2ewXe1d

Finally, to express a variable that both has unusual semantics and has any or all of the atomicity and/or ordering guarantees needed for lock-free coding, only the ISO C++0x draft Standard provides a direct way to spell it: volatile atomic.

But does modern standard C++11/14/17 (not C++0x draft) guarantee that volatile atomic<T> has both semantics (volatile + atomic)?

What should we use volatile atomic<T> or atomic<volatile T> to guarantee the most stringent guarantees from both volatile and atomic?

  1. As in volatile: Avoid fused-operations and constant-propagation as described in the beginning of the question
  2. As in std::atomic: Introduce memory fences to provide: ordering, spilling, atomic

And can we do reinterpret_cast from volatile int *ptr; to std::atomic<volatile int>* or to volatile std::atomic<int>*?

Aucun commentaire:

Enregistrer un commentaire