3

The return value of sizeof(atomic<T>) is not always equal to the return value of sizeof(T), based on following sentence from [atomics.types.generic]/p9:

Note: The representation of an atomic specialization need not have the same size as its corresponding argument type. Specializations should have the same size whenever possible, as this reduces the effort required to port existing code

Sometimes they are equal, like this:

struct A {
    char       i;
    const char padding[3];
    int        j;
};

A a;
a.i = 1;
a.j = 2;
A b;
b.i = 3;
b.j = 4;
std::atomic<A> A_atomic(a);

sizeof(A_atomic) is 8, and sizeof(A) is 8 too.

A_atomic.compare_exchange_weak(a,b) returns true.

A_atomic.is_always_lock_free returns true.

When I add an int variable, they are not the same:

struct A {
    char       i;
    const char padding[3];
    int        j;
    int        k;
};

A a;
a.i = 1;
a.j = 2;
A b;
b.i = 3;
b.j = 4;
std::atomic<A> A_atomic(a);

sizeof(A_atomic) is 16, and sizeof(A) is 12.

A_atomic.compare_exchange_weak(a,b) returns false.

A_atomic.is_always_lock_free returns true.

What happened to std::atomic<A> when I added an int?

How can I pick out the subtle difference between the two structs to align the memory layout in order to ensure CAS returns true?

My environment is:

  • macOSX10.13
  • clang-900.0.37
  • x86_64-apple-darwin18.7.0

more info:

I add another int variable like:

struct A {
    char       i;
    const char padding[3];
    int        j;
    int        k;
    int        h;
}

sizeof(A_atomic) is 16, and sizeof(A) is 16.

A_atomic.compare_exchange_weak(a,b) returns true.

A_atomic.is_always_lock_free returns true.

douglas
  • 41
  • 3
  • 7
    if i had to guess i would assume atomic operations add extra alignment requirements which causes padding to be added so that it aligns on 16 bytes rather than 12. – Borgleader Apr 28 '22 at 03:06
  • 3
    You don't pick out the difference. You write your code so that it doesn't matter. – JohnFilleau Apr 28 '22 at 03:06
  • when i use CAS operation, the first struct return true and the second return false. so i need know the difference to align the struct memory layout. – douglas Apr 28 '22 at 03:19
  • 3
    Above 8 bytes your platform probably needs a lock (mutex) to implement atomic ops – Nemo Apr 28 '22 at 03:37
  • but the is_always_lock_free return true ? – douglas Apr 28 '22 at 03:45
  • 5
    Most processors do not have an "atomic update 12 bytes" but some have a "atomic update 16 bytes". – Raymond Chen Apr 28 '22 at 04:37
  • where or which web-site to lookup the atomic specification of processors ? – douglas Apr 28 '22 at 05:08
  • 1
    You look it up on the processor manufacturer web site. – Raymond Chen Apr 28 '22 at 05:25
  • 1
    @Nemo: If it needed a mutex, `A_atomic.is_always_lock_free` definitely wouldn't be true. But it is for this implementation, so clearly Apple clang defaults to enabling `-mcx16` (since Apple never sold any x86-64 Macs with very early x86-64 CPUs that didn't support [the `cmpxchg16b` instruction](https://www.felixcloutier.com/x86/cmpxchg8b:cmpxchg16b), like K8 or P4). – Peter Cordes Apr 28 '22 at 05:38
  • And unlike recent GCC, clang does treat that as sufficient to report lock-free, even though reads are implemented with an atomic RMW CAS attempt (so read-only throughput doesn't scale with more cores). GCC reports non-lock-free and always calls a helper function, but on CPUs that support it, those functions can do the same thing clang does (and that older GCC did). See https://gcc.gnu.org/ml/gcc-patches/2017-01/msg02344.html / https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84563. – Peter Cordes Apr 28 '22 at 05:43
  • 1
    IDK if recent GCC/clang have yet taken advantage of the recently-documented guarantee that the AVX feature bit implies atomic 16-byte aligned load/store (e.g. with `movaps`), but that's a game-changer in terms of performance for `atomic<16byte>.load()`, either in the helper function, or inlined for code that will only run on AVX CPUs. – Peter Cordes Apr 28 '22 at 05:43
  • That’s very kind of you. Thank you. – douglas Apr 28 '22 at 05:58
  • @RaymondChen: For x86 / x86-64 specifically, [Why is integer assignment on a naturally aligned variable atomic on x86?](https://stackoverflow.com/q/36624881) covers the atomicity guarantees for pure-load and pure-store, taking the common subset of guarantees provided by AMD and Intel. Code that wants to run on x86-64 CPUs in general can't look at just one vendor's manuals, because they differ in the details of their guarantees for unaligned but not crossing a cache-line boundary. (In practice compilers will naturally-align all lock-free atomic objects to the next power-of-2 alignment.) – Peter Cordes Apr 28 '22 at 13:27

1 Answers1

-2

You can try with #pragma pack(x) (x is a number) to see how it affects the size. You can look at this.

  • Where are you going to put `#pragma pack(1)` to make it affect the layout of `std::atomic` specializations for objects of size <= 16 bytes? If you do that before `#include ` and it applies to all classes defined in that header, it might "work", as in breaking the atomicity of small structs, and causing massive performance issues for larger structs. And breaking ABI compatibility for any standard-library structs/classes that have padding that get defined by it and things it includes. – Peter Cordes Apr 28 '22 at 06:13
  • Also, that's an MSVC pragma, but interestingly [clang *does* support it](https://releases.llvm.org/3.4/tools/clang/docs/UsersManual.html#microsoft-extensions). (Normally in GNU C you'd use `__attribute__((packed))` on each struct specifically.) And no, packing `struct A` doesn't change `sizeof(A)`, so `std::atomic` would still do its usual thing if you haven't broken `std::atomic` by doing this. – Peter Cordes Apr 28 '22 at 06:17
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Apr 28 '22 at 07:46
  • "packing struct A doesn't change sizeof(A)" that is not correct. Packing change sizeof(A) – karaketir16 Apr 29 '22 at 08:03