* tangentially related to something that I was working on which if far
more perverse but mostly in a solution that is looking for a problem
phase so it probably won't go anywhere.
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent- sequential-rcu/
The name doesn't really mean anything. I was just generalizing something that's been around forever.*˙ It's simple enough that it must be in use somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
seqlock documentation and it specifically says not to use pointers in
seqlock protected data and no idea why that is unless the linux kernel
is doing something weird with memory mapping.
Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
that would be a better choice.˙ It might be good for libraries where
the memory reclamation is hidden from the application.
* tangentially related to something that I was working on which if far
more perverse but mostly in a solution that is looking for a problem
phase so it probably won't go anywhere.
On 9/16/2026 3:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
sequential-rcu/
The name doesn't really mean anything. I was just generalizing something
that's been around forever.*˙ It's simple enough that it must be in use
somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
seqlock documentation and it specifically says not to use pointers in
seqlock protected data and no idea why that is unless the linux kernel
is doing something weird with memory mapping.
Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
that would be a better choice.˙ It might be good for libraries where
the memory reclamation is hidden from the application.
* tangentially related to something that I was working on which if far
more perverse but mostly in a solution that is looking for a problem
phase so it probably won't go anywhere.
Fwiw, I have used seqlocks for the writer side and pure RCU for the
readers. Actually, there is a way to use seqlocks to gain DWCAS on a
system that does not support it.
https://groups.google.com/g/lock-free/c/X3fuuXknQF0/m/zfHnoFi-VXgJ
https://pastebin.com/raw/TgTcfYtR
On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
On 9/16/2026 3:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
sequential-rcu/
The name doesn't really mean anything. I was just generalizing something >>> that's been around forever.*˙ It's simple enough that it must be in use
somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
seqlock documentation and it specifically says not to use pointers in
seqlock protected data and no idea why that is unless the linux kernel
is doing something weird with memory mapping.
Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
that would be a better choice.˙ It might be good for libraries where
the memory reclamation is hidden from the application.
* tangentially related to something that I was working on which if far
more perverse but mostly in a solution that is looking for a problem
phase so it probably won't go anywhere.
Fwiw, I have used seqlocks for the writer side and pure RCU for the
readers. Actually, there is a way to use seqlocks to gain DWCAS on a
system that does not support it.
https://groups.google.com/g/lock-free/c/X3fuuXknQF0/m/zfHnoFi-VXgJ
https://pastebin.com/raw/TgTcfYtR
Java did its AtomicStampedReference by creating an object to hold
the stamp and the reference and then doing a single word compare
and swap on a reference to it.˙ All this so you can run on
hardware which effectively doesn't exist anymore and even if it
did you would need to backport the os to support obsolete
hardware so the jvm could even run on it.
C++ is sort of in the same boat, which is why we still need to
use inline assembly to implement half century old algorithms
efficiently.
Re the OP, I did think of a really good POC for this but it is
way too much work.˙ I have better things to waste my time
with. :)
On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
On 9/16/2026 3:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
sequential-rcu/
The name doesn't really mean anything. I was just generalizing something >>> that's been around forever.*˙ It's simple enough that it must be in use
somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
seqlock documentation and it specifically says not to use pointers in
seqlock protected data and no idea why that is unless the linux kernel
is doing something weird with memory mapping.
Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
that would be a better choice.˙ It might be good for libraries where
the memory reclamation is hidden from the application.
* tangentially related to something that I was working on which if far
more perverse but mostly in a solution that is looking for a problem
phase so it probably won't go anywhere.
Fwiw, I have used seqlocks for the writer side and pure RCU for the
readers. Actually, there is a way to use seqlocks to gain DWCAS on a
system that does not support it.
https://groups.google.com/g/lock-free/c/X3fuuXknQF0/m/zfHnoFi-VXgJ
https://pastebin.com/raw/TgTcfYtR
Java did its AtomicStampedReference by creating an object to hold
the stamp and the reference and then doing a single word compare
and swap on a reference to it.˙ All this so you can run on
hardware which effectively doesn't exist anymore and even if it
did you would need to backport the os to support obsolete
hardware so the jvm could even run on it.
C++ is sort of in the same boat, which is why we still need to
use inline assembly to implement half century old algorithms
efficiently.
Re the OP, I did think of a really good POC for this but it is
way too much work.˙ I have better things to waste my time
with. :)
On 9/20/2026 7:58 AM, Joseph Seigh wrote:
On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
On 9/16/2026 3:25 PM, Joseph Seigh wrote:
C++ is sort of in the same boat, which is why we still need to
use inline assembly to implement half century old algorithms
efficiently.
˙never really got into inline asm. Well. I said f it and used good ol
GAS. Or MASM.
GAS.
https://web.archive.org/web/20060214112345/http:// appcore.home.comcast.net/appcore/src/cpu/i686/ac_i686_gcc_asm.html
https://web.archive.org/web/20060214112539/http:// appcore.home.comcast.net/appcore/src/cpu/i686/ac_i686_masm_asm.html
GAS was kind to me.
On 9/20/26 7:09 PM, Chris M. Thomasson wrote:
On 9/20/2026 7:58 AM, Joseph Seigh wrote:
On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
On 9/16/2026 3:25 PM, Joseph Seigh wrote:
C++ is sort of in the same boat, which is why we still need to
use inline assembly to implement half century old algorithms
efficiently.
˙˙never really got into inline asm. Well. I said f it and used good ol
GAS. Or MASM.
GAS.
https://web.archive.org/web/20060214112345/http://
appcore.home.comcast.net/appcore/src/cpu/i686/ac_i686_gcc_asm.html
https://web.archive.org/web/20060214112539/http://
appcore.home.comcast.net/appcore/src/cpu/i686/ac_i686_masm_asm.html
GAS was kind to me.
https://github.com/jseigh/queues/blob/main/include/atomix.h
On 9/20/2026 7:58 AM, Joseph Seigh wrote:
On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
On 9/16/2026 3:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
sequential-rcu/
C++ is sort of in the same boat, which is why we still need to
use inline assembly to implement half century old algorithms
efficiently.
˙never really got into inline asm. Well. I said f it and used good ol
GAS. Or MASM.
https://web.archive.org/web/20060214112345/http:// appcore.home.comcast.net/appcore/src/cpu/i686/ac_i686_gcc_asm.html
https://web.archive.org/web/20060214112539/http:// appcore.home.comcast.net/appcore/src/cpu/i686/ac_i686_masm_asm.html
GAS was kind to me.
# Copyright 2005 Chris Thomasson
.align 16
.globl np_ac_i686_atomic_dwcas_fence
np_ac_i686_atomic_dwcas_fence:
˙ pushl %esi
˙ pushl %ebx
˙ movl 16(%esp), %esi
˙ movl (%esi), %eax
˙ movl 4(%esi), %edx
˙ movl 20(%esp), %esi
˙ movl (%esi), %ebx
˙ movl 4(%esi), %ecx
˙ movl 12(%esp), %esi
˙ lock cmpxchg8b (%esi)
˙ jne np_ac_i686_atomic_dwcas_fence_fail
˙ xorl %eax, %eax
˙ popl %ebx
˙ popl %esi
˙ ret
np_ac_i686_atomic_dwcas_fence_fail:
˙ movl 16(%esp), %esi
˙ movl %eax, (%esi)
˙ movl %edx, 4(%esi)
˙ movl $1, %eax
˙ popl %ebx
˙ popl %esi
ret
On 21/09/2026 01:09, Chris M. Thomasson wrote:
On 9/20/2026 7:58 AM, Joseph Seigh wrote:
On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
On 9/16/2026 3:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
sequential-rcu/
C++ is sort of in the same boat, which is why we still need to
use inline assembly to implement half century old algorithms
efficiently.
˙never really got into inline asm. Well. I said f it and used good ol
GAS. Or MASM.
You really should learn about inline assembly in gcc. It can be >significantly more efficient than using an external call in a separately >assembled file. And I am confident that you'd find it fun! (Of course,
On Mon, 21 Sep 2026 14:50:19 +0200
David Brown <david.brown@hesbynett.no> gabbled:
On 21/09/2026 01:09, Chris M. Thomasson wrote:
On 9/20/2026 7:58 AM, Joseph Seigh wrote:
On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
On 9/16/2026 3:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent- >>>>>> sequential-rcu/
C++ is sort of in the same boat, which is why we still need to
use inline assembly to implement half century old algorithms
efficiently.
˙˙never really got into inline asm. Well. I said f it and used good
ol GAS. Or MASM.
You really should learn about inline assembly in gcc.˙ It can be
significantly more efficient than using an external call in a
separately assembled file.˙ And I am confident that you'd find it
fun!˙ (Of course,
Unless the assembler is just a few opcodes you're better off leaving it to
a compiler as it'll usually do a better job and probably knows opcodes you've
never even heard of.
On 21/09/2026 01:09, Chris M. Thomasson wrote:
On 9/20/2026 7:58 AM, Joseph Seigh wrote:
On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
On 9/16/2026 3:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
sequential-rcu/
C++ is sort of in the same boat, which is why we still need to
use inline assembly to implement half century old algorithms
efficiently.
˙˙never really got into inline asm. Well. I said f it and used good ol
GAS. Or MASM.
You really should learn about inline assembly in gcc.˙ It can be significantly more efficient than using an external call in a separately assembled file.˙ And I am confident that you'd find it fun!˙ (Of course, [...]
On Mon, 21 Sep 2026 14:50:19 +0200
David Brown <david.brown@hesbynett.no> gabbled:
On 21/09/2026 01:09, Chris M. Thomasson wrote:
On 9/20/2026 7:58 AM, Joseph Seigh wrote:
On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
On 9/16/2026 3:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent- >>>>>> sequential-rcu/
C++ is sort of in the same boat, which is why we still need to
use inline assembly to implement half century old algorithms
efficiently.
˙˙never really got into inline asm. Well. I said f it and used good
ol GAS. Or MASM.
You really should learn about inline assembly in gcc.˙ It can be
significantly more efficient than using an external call in a
separately assembled file.˙ And I am confident that you'd find it
fun!˙ (Of course,
Unless the assembler is just a few opcodes you're better off leaving it to
a compiler as it'll usually do a better job and probably knows opcodes you've
never even heard of.
On 9/21/2026 5:50 AM, David Brown wrote:
On 21/09/2026 01:09, Chris M. Thomasson wrote:
On 9/20/2026 7:58 AM, Joseph Seigh wrote:
On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
On 9/16/2026 3:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent- >>>>>> sequential-rcu/
C++ is sort of in the same boat, which is why we still need to
use inline assembly to implement half century old algorithms
efficiently.
˙˙never really got into inline asm. Well. I said f it and used good
ol GAS. Or MASM.
You really should learn about inline assembly in gcc.˙ It can be
significantly more efficient than using an external call in a
separately assembled file.˙ And I am confident that you'd find it
fun!˙ (Of course, [...]
Yeah. Well. Shit. Back then I did not want any compiler to mess with
things, and my inline skills were rather lacking! I felt way better
making a header the structs, and externally assembling things. Keep in
mind this was before C/C++ 11. I even said woe to LTO.
The point here was an opcode - the 16 byte compare-and-exchange - that
the compiler does /not/ know about, and is not supported by the C++ (or
C) standard library for atomics.
Am 21.09.2026 um 18:45 schrieb David Brown:
The point here was an opcode - the 16 byte compare-and-exchange - that
the compiler does /not/ know about, and is not supported by the C++
(or C) standard library for atomics.
Use atomic_ref with a trivial structure that is 16 bytes with x64 or
8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for inline-assembly.
Am 21.09.2026 um 18:45 schrieb David Brown:
The point here was an opcode - the 16 byte compare-and-exchange - that
the compiler does /not/ know about, and is not supported by the C++
(or C) standard library for atomics.
Use atomic_ref with a trivial structure that is 16 bytes with x64 or
8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for inline-assembly.
On 9/25/26 12:25 PM, Bonita Montero wrote:
Am 21.09.2026 um 18:45 schrieb David Brown:
The point here was an opcode - the 16 byte compare-and-exchange -
that the compiler does /not/ know about, and is not supported by the
C++ (or C) standard library for atomics.
Use atomic_ref with a trivial structure that is 16 bytes with x64 or
8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for
inline-assembly.
You need some way to avoid the ABA problem.
On 25/09/2026 19:47, Joseph Seigh wrote:
On 9/25/26 12:25 PM, Bonita Montero wrote:
Use atomic_ref with a trivial structure that is 16 bytes with x64 or
8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for
inline-assembly.
You need some way to avoid the ABA problem.
That's entirely possible - and I expect you have more experience in that than I do.˙ I have no experience with this on x86 - I work in microcontrollers, which are often simpler for such things.˙ I haven't considered anything other than implementing the instruction - issues
like ABA are no different when you use optimal inline assembly, less
optimal inline assembly, or external assembly.
On 9/25/26 2:54 PM, David Brown wrote:
On 25/09/2026 19:47, Joseph Seigh wrote:
On 9/25/26 12:25 PM, Bonita Montero wrote:
The 16 byte version lets you combine a memory reference w/ a number as aUse atomic_ref with a trivial structure that is 16 bytes with x64 or
8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for
inline-assembly.
You need some way to avoid the ABA problem.
That's entirely possible - and I expect you have more experience in
that than I do.˙ I have no experience with this on x86 - I work in
microcontrollers, which are often simpler for such things.˙ I haven't
considered anything other than implementing the instruction - issues
like ABA are no different when you use optimal inline assembly, less
optimal inline assembly, or external assembly.
fat pointer to let you distinguish distinguish between two instances
of an object sharing the same memory address (due to the memory being reallocated).*˙ Basically the ABA problem.
The example I posted is from a lock-free ring buffer, a bounded queue.
Most of the lock-free ring buffers you see out there aren't really
lock-free and/or an actual queue unless you qualify with "for some
definition of lock-free" and "for some definition of queue".
For linked queues, you have the Michael-Scott lock-free queue where
you can get away with a single wide CAS but you need something like
hazard pointers or RCU to avoid the ABA problem and to avoid doing
CAS on a memory location with undefined state.˙ LL/SC doesn't have
this problem however.
Double wide CAS support would be nice to have, but I'm not holding
my breath.
* In the 70's a double wide CAS on an IBM mainframe was 64 bits.
Using a 32 bit number and 32 bit address, it was estimated that
a 32 bit number would take about 100 years to wrap on the
current hardware then.
On 25/09/2026 23:20, Joseph Seigh wrote:
Double wide CAS support would be nice to have, but I'm not holding
my breath.
Doesn't the cmpxchg16b count as a double-wide CAS on x86-64 ?
On 9/26/26 10:23 AM, David Brown wrote:
On 25/09/2026 23:20, Joseph Seigh wrote:
Double wide CAS support would be nice to have, but I'm not holding
my breath.
Doesn't the cmpxchg16b count as a double-wide CAS on x86-64 ?
I meant as part of c/c++.˙ There really no legitimate technical
reason it's not in the standard now, it's just they can't put
it in the way they would like too, so they refuse to put it in
at all.˙ The way they would like to is an atomic<T> where T would
be an 8 byte type.˙ But they can't since there are no atomic 8 byte
load and store instructions on most existing x86-64 processors
and that's the only way they are willing to do it.
I have a pre c++11 implementation of atomic reference counting
(actually atomic, not like Rust's ARC which is merely thread-safe)
and there's no reason to rewrite it post c++11.˙ I'd still need
assembly.
Anyway, there's solutions to more interesting concurrency problems to consider.
On 25/09/2026 00:28, Chris M. Thomasson wrote:
On 9/21/2026 5:50 AM, David Brown wrote:
On 21/09/2026 01:09, Chris M. Thomasson wrote:
On 9/20/2026 7:58 AM, Joseph Seigh wrote:
On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
On 9/16/2026 3:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent- >>>>>>> sequential-rcu/
C++ is sort of in the same boat, which is why we still need to
use inline assembly to implement half century old algorithms
efficiently.
˙˙never really got into inline asm. Well. I said f it and used good
ol GAS. Or MASM.
You really should learn about inline assembly in gcc.˙ It can be
significantly more efficient than using an external call in a
separately assembled file.˙ And I am confident that you'd find it
fun!˙ (Of course, [...]
Yeah. Well. Shit. Back then I did not want any compiler to mess with
things, and my inline skills were rather lacking! I felt way better
making a header the structs, and externally assembling things. Keep in
mind this was before C/C++ 11. I even said woe to LTO.
I was not trying to show pre-C++11 Chris how to write inline assembly
for atomics.˙ I was trying to show 2026 Chris how to write inline
assembly /today/, because it is still useful for some things (like a 16- byte compare-and-swap).˙ I was also trying to show Joseph how to do a
better job than he had so far.˙ (Again, it's on the inline assembly
part, not the choice of instructions or atomic handling.)
You didn't know the details of gcc inline assembly back then - fair
enough, it's not easy, and has subtly challenges.˙ Now you know a bit
more going forward.˙ And I really do think it is something that you
would enjoy playing with.
On 9/25/26 2:54 PM, David Brown wrote:
On 25/09/2026 19:47, Joseph Seigh wrote:
On 9/25/26 12:25 PM, Bonita Montero wrote:
The 16 byte version lets you combine a memory reference w/ a number as aUse atomic_ref with a trivial structure that is 16 bytes with x64 or
8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for
inline-assembly.
You need some way to avoid the ABA problem.
That's entirely possible - and I expect you have more experience in
that than I do.˙ I have no experience with this on x86 - I work in
microcontrollers, which are often simpler for such things.˙ I haven't
considered anything other than implementing the instruction - issues
like ABA are no different when you use optimal inline assembly, less
optimal inline assembly, or external assembly.
fat pointer to let you distinguish distinguish between two instances
of an object sharing the same memory address (due to the memory being reallocated).*˙ Basically the ABA problem.
The example I posted is from a lock-free ring buffer, a bounded queue.
Most of the lock-free ring buffers you see out there aren't really
lock-free and/or an actual queue unless you qualify with "for some
definition of lock-free" and "for some definition of queue".
For linked queues, you have the Michael-Scott lock-free queue where
you can get away with a single wide CAS but you need something like
hazard pointers or RCU to avoid the ABA problem and to avoid doing
CAS on a memory location with undefined state.˙ LL/SC doesn't have
this problem however.
Double wide CAS support would be nice to have, but I'm not holding
my breath.
* In the 70's a double wide CAS on an IBM mainframe was 64 bits.
Using a 32 bit number and 32 bit address, it was estimated that
a 32 bit number would take about 100 years to wrap on the
current hardware then.
Am 21.09.2026 um 18:45 schrieb David Brown:
The point here was an opcode - the 16 byte compare-and-exchange - that
the compiler does /not/ know about, and is not supported by the C++
(or C) standard library for atomics.
Use atomic_ref with a trivial structure that is 16 bytes with x64 or
8 bytes with x86. This works with MSVC, g++ and clang++ on x86 / x64.
With that you have CMPXCHG16B (x64) or CMPXCHG8B (x86). No need for inline-assembly.
On 26/09/2026 21:36, Joseph Seigh wrote:
On 9/26/26 10:23 AM, David Brown wrote:
On 25/09/2026 23:20, Joseph Seigh wrote:
Double wide CAS support would be nice to have, but I'm not holding
my breath.
Doesn't the cmpxchg16b count as a double-wide CAS on x86-64 ?
I meant as part of c/c++.˙ There really no legitimate technical
reason it's not in the standard now, it's just they can't put
it in the way they would like too, so they refuse to put it in
at all.˙ The way they would like to is an atomic<T> where T would
be an 8 byte type.˙ But they can't since there are no atomic 8 byte
load and store instructions on most existing x86-64 processors
and that's the only way they are willing to do it.
Okay.
There is a strong reluctance to put things into the standards if they
can't be realistically and practically implemented on most platforms.
But C++ /does/ support atomic<T> types, where T is 16-bytes - or any
size at all.˙ And it supports compare_exchange() methods (strong and
weak) on those types.
As far as I can see, it's up to the implementation how those are
handled.˙ gcc on x86-64 does so with a library call "__atomic_compare_exchange_16".˙ (Bigger sizes use a more general __atomic_compare_exchange call.)˙ If the target supports a dedicated instruction here, and the instruction is more efficient than the library call (that's usually the case!), then this is just a compiler quality of implementation issue.˙ When someone adds this to the compiler and/or gcc
C++ standard library implementation, it should just work when
appropriate target selection flags are used.
I have a pre c++11 implementation of atomic reference counting
(actually atomic, not like Rust's ARC which is merely thread-safe)
and there's no reason to rewrite it post c++11.˙ I'd still need
assembly.
Anyway, there's solutions to more interesting concurrency problems to
consider.
On 9/27/2026 5:56 AM, David Brown wrote:
On 26/09/2026 21:36, Joseph Seigh wrote:
On 9/26/26 10:23 AM, David Brown wrote:
On 25/09/2026 23:20, Joseph Seigh wrote:
Double wide CAS support would be nice to have, but I'm not holding
my breath.
Doesn't the cmpxchg16b count as a double-wide CAS on x86-64 ?
I meant as part of c/c++.˙ There really no legitimate technical
reason it's not in the standard now, it's just they can't put
it in the way they would like too, so they refuse to put it in
at all.˙ The way they would like to is an atomic<T> where T would
be an 8 byte type.˙ But they can't since there are no atomic 8 byte
load and store instructions on most existing x86-64 processors
and that's the only way they are willing to do it.
Okay.
There is a strong reluctance to put things into the standards if they
can't be realistically and practically implemented on most platforms.
But C++ /does/ support atomic<T> types, where T is 16-bytes - or any
size at all.˙ And it supports compare_exchange() methods (strong and
weak) on those types.
As far as I can see, it's up to the implementation how those are
handled.˙ gcc on x86-64 does so with a library call
"__atomic_compare_exchange_16".˙ (Bigger sizes use a more general
__atomic_compare_exchange call.)˙ If the target supports a dedicated
instruction here, and the instruction is more efficient than the
library call (that's usually the case!), then this is just a compiler
quality of implementation issue.˙ When someone adds this to the
compiler and/or gcc C++ standard library implementation, it should
just work when appropriate target selection flags are used.
It is a QOI, as far as I can tell. Checked it a while ago, and it says
that the DWCAS is not lock free. That scares me. If its not lock free,
then there is no reason to use it for these types of things. ;^o
On 9/24/2026 11:33 PM, David Brown wrote:
On 25/09/2026 00:28, Chris M. Thomasson wrote:
On 9/21/2026 5:50 AM, David Brown wrote:
On 21/09/2026 01:09, Chris M. Thomasson wrote:
On 9/20/2026 7:58 AM, Joseph Seigh wrote:
On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
On 9/16/2026 3:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-
concurrent- sequential-rcu/
C++ is sort of in the same boat, which is why we still need to
use inline assembly to implement half century old algorithms
efficiently.
˙˙never really got into inline asm. Well. I said f it and used good >>>>> ol GAS. Or MASM.
You really should learn about inline assembly in gcc.˙ It can be
significantly more efficient than using an external call in a
separately assembled file.˙ And I am confident that you'd find it
fun!˙ (Of course, [...]
Yeah. Well. Shit. Back then I did not want any compiler to mess with
things, and my inline skills were rather lacking! I felt way better
making a header the structs, and externally assembling things. Keep
in mind this was before C/C++ 11. I even said woe to LTO.
I was not trying to show pre-C++11 Chris how to write inline assembly
for atomics.˙ I was trying to show 2026 Chris how to write inline
assembly /today/, because it is still useful for some things (like a
16- byte compare-and-swap).˙ I was also trying to show Joseph how to
do a better job than he had so far.˙ (Again, it's on the inline
assembly part, not the choice of instructions or atomic handling.)
You didn't know the details of gcc inline assembly back then - fair
enough, it's not easy, and has subtly challenges.˙ Now you know a bit
more going forward.˙ And I really do think it is something that you
would enjoy playing with.
Fwiw, I felt way better with externally assembled ASM back then. The
syntax for GCC inline was/is a bit "bitter" to me, MASM was a little
better, alas. But, I understand your main point.
I still want to put it into a separate file, GAS it into an .o file and
link the little shit.
On 29/09/2026 00:42, Chris M. Thomasson wrote:Difficult to totally disagree with that David. Shit. Its my own personal problem with the syntax of GCC inline asm. I am a bit scared of it.
On 9/24/2026 11:33 PM, David Brown wrote:
On 25/09/2026 00:28, Chris M. Thomasson wrote:
On 9/21/2026 5:50 AM, David Brown wrote:
On 21/09/2026 01:09, Chris M. Thomasson wrote:
On 9/20/2026 7:58 AM, Joseph Seigh wrote:
On 9/19/26 4:34 PM, Chris M. Thomasson wrote:
On 9/16/2026 3:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-
concurrent- sequential-rcu/
C++ is sort of in the same boat, which is why we still need to
use inline assembly to implement half century old algorithms
efficiently.
˙˙never really got into inline asm. Well. I said f it and used
good ol GAS. Or MASM.
You really should learn about inline assembly in gcc.˙ It can be
significantly more efficient than using an external call in a
separately assembled file.˙ And I am confident that you'd find it
fun!˙ (Of course, [...]
Yeah. Well. Shit. Back then I did not want any compiler to mess with
things, and my inline skills were rather lacking! I felt way better
making a header the structs, and externally assembling things. Keep
in mind this was before C/C++ 11. I even said woe to LTO.
I was not trying to show pre-C++11 Chris how to write inline assembly
for atomics.˙ I was trying to show 2026 Chris how to write inline
assembly /today/, because it is still useful for some things (like a
16- byte compare-and-swap).˙ I was also trying to show Joseph how to
do a better job than he had so far.˙ (Again, it's on the inline
assembly part, not the choice of instructions or atomic handling.)
You didn't know the details of gcc inline assembly back then - fair
enough, it's not easy, and has subtly challenges.˙ Now you know a bit
more going forward.˙ And I really do think it is something that you
would enjoy playing with.
Fwiw, I felt way better with externally assembled ASM back then. The
syntax for GCC inline was/is a bit "bitter" to me, MASM was a little
better, alas. But, I understand your main point.
I still want to put it into a separate file, GAS it into an .o file
and link the little shit.
External assembly can be better for some types of code, but it is
definitely inappropriate here.˙ With external assembly you have to stick rigidly to the ABI for external calls, but it is clearer for large
blocks of assembly (or other occasionally useful advanced stuff).
However, the only reason you need large blocks of assembly for wrapping
a single instruction is because you are using external assembly and the
ABI for function calls!˙ Inline assembly not only avoids that overhead,
but lets the compiler integrate it much better with the rest of the code.
The whole point of using lock-free algorithms here, and the double compare-and-swap, is speed.˙ If you use external assembly so that all
your important data has to be pushed from registers to the stack, then
you have a call, then you pull the data off the stack again - you've a
dozen extra instructions at the call site, a dozen extra instructions at
the cas16 site, doing nothing except spoiling your efficient code.
gcc inline assembly is a /much/ better solution here.˙ It's okay if you don't know how to write it - and it is certainly okay if you did not
know how to do so a decade ago.˙ But you should understand why it is
better, and then decide if you are going to learn about it.
(Of course for this particular case, if you can persuade the gcc C++
library folk to make their standard cas16 lock-free, then using the
standard function is best.)
On 9/28/2026 11:40 PM, David Brown wrote:
gcc inline assembly is a /much/ better solution here.˙ It's okay ifDifficult to totally disagree with that David. Shit. Its my own personal problem with the syntax of GCC inline asm. I am a bit scared of it.
you don't know how to write it - and it is certainly okay if you did
not know how to do so a decade ago.˙ But you should understand why it
is better, and then decide if you are going to learn about it.
(Of course for this particular case, if you can persuade the gcc C++
library folk to make their standard cas16 lock-free, then using the
standard function is best.)
Sigh. Clobbers what? When and where? cdecl for the ABI and externally assembled functions are fine for me, LTO aside for a moment, can be bad
do not go into my asm! BUT. Again. Shit. Its a personal problem wrt me saying inline bad! Sorry!
Also, if C++ gains a LOCK CMPXCHG16B for a double word, two contiguous
64 bit words on a 64 bit machine, well GOOD! Show me. I have had some experiences with it trying to tell me its not lock free.
On 30/09/2026 01:30, Chris M. Thomasson wrote:
On 9/28/2026 11:40 PM, David Brown wrote:
gcc inline assembly is a /much/ better solution here.˙ It's okay ifDifficult to totally disagree with that David. Shit. Its my own
you don't know how to write it - and it is certainly okay if you did
not know how to do so a decade ago.˙ But you should understand why it
is better, and then decide if you are going to learn about it.
(Of course for this particular case, if you can persuade the gcc C++
library folk to make their standard cas16 lock-free, then using the
standard function is best.)
personal problem with the syntax of GCC inline asm. I am a bit scared
of it. Sigh. Clobbers what? When and where? cdecl for the ABI and
externally assembled functions are fine for me, LTO aside for a
moment, can be bad do not go into my asm! BUT. Again. Shit. Its a
personal problem wrt me saying inline bad! Sorry!
Not knowing the syntax for gcc inline assembly is not a "problem" as
such - most C programmers never need it.˙ It is just a strong
recommendation that /if/ you want to write assembly code, then your
results would be much improved by using inline assembly format rather
than external assembly functions.˙ And you are already familiar with the hard part, the actual assembly.˙ So don't look at this as a problem of
any sort - look at it as a fun, challenging and useful new direction for your existing skills and interests.
Also, if C++ gains a LOCK CMPXCHG16B for a double word, two contiguous
64 bit words on a 64 bit machine, well GOOD! Show me. I have had some
experiences with it trying to tell me its not lock free.
The C++ standard library supports 16 byte compare-and-exchange.˙ The gcc implementation of that library does not use that instruction, and thus
does not support lock-free 16 byte compare-and-exchange.˙ C++ has
supported this since C++11 - this is a QOI issue, not a language or
standard library issue.˙ File an issue on the gcc bugzilla - that's how
you get progress (or how we will learn if there are good reasons not to
use it.)
On 29/09/2026 00:50, Chris M. Thomasson wrote:
On 9/27/2026 5:56 AM, David Brown wrote:
On 26/09/2026 21:36, Joseph Seigh wrote:
On 9/26/26 10:23 AM, David Brown wrote:
On 25/09/2026 23:20, Joseph Seigh wrote:
Double wide CAS support would be nice to have, but I'm not holding >>>>>> my breath.
Doesn't the cmpxchg16b count as a double-wide CAS on x86-64 ?
I meant as part of c/c++.˙ There really no legitimate technical
reason it's not in the standard now, it's just they can't put
it in the way they would like too, so they refuse to put it in
at all.˙ The way they would like to is an atomic<T> where T would
be an 8 byte type.˙ But they can't since there are no atomic 8 byte
load and store instructions on most existing x86-64 processors
and that's the only way they are willing to do it.
Okay.
There is a strong reluctance to put things into the standards if they
can't be realistically and practically implemented on most platforms.
But C++ /does/ support atomic<T> types, where T is 16-bytes - or any
size at all.˙ And it supports compare_exchange() methods (strong and
weak) on those types.
As far as I can see, it's up to the implementation how those are
handled.˙ gcc on x86-64 does so with a library call
"__atomic_compare_exchange_16".˙ (Bigger sizes use a more general
__atomic_compare_exchange call.)˙ If the target supports a dedicated
instruction here, and the instruction is more efficient than the
library call (that's usually the case!), then this is just a compiler
quality of implementation issue.˙ When someone adds this to the
compiler and/or gcc C++ standard library implementation, it should
just work when appropriate target selection flags are used.
It is a QOI, as far as I can tell. Checked it a while ago, and it says
that the DWCAS is not lock free. That scares me. If its not lock free,
then there is no reason to use it for these types of things. ;^o
The answer then would be to file an issue with the gcc bugzilla (since
it's their C++ standard library implementation you are looking at). Feel free to post my inline assembly there in the issue, and post the issue
link here.
On 9/28/2026 11:40 PM, David Brown wrote:...
On 29/09/2026 00:42, Chris M. Thomasson wrote:
Difficult to totally disagree with that David. Shit. Its my own personal problem with the syntax of GCC inline asm. I am a bit scared of it.
(Of course for this particular case, if you can persuade the gcc C++
library folk to make their standard cas16 lock-free, then using the
standard function is best.)
Sigh. Clobbers what? When and where? cdecl for the ABI and externally assembled functions are fine for me, LTO aside for a moment, can be bad
do not go into my asm! BUT. Again. Shit. Its a personal problem wrt me saying inline bad! Sorry!
Also, if C++ gains a LOCK CMPXCHG16B for a double word, two contiguous
64 bit words on a 64 bit machine, well GOOD! Show me. I have had some experiences with it trying to tell me its not lock free.
On 9/28/2026 11:31 PM, David Brown wrote:
On 29/09/2026 00:50, Chris M. Thomasson wrote:
On 9/27/2026 5:56 AM, David Brown wrote:
On 26/09/2026 21:36, Joseph Seigh wrote:
On 9/26/26 10:23 AM, David Brown wrote:
On 25/09/2026 23:20, Joseph Seigh wrote:
Double wide CAS support would be nice to have, but I'm not holding >>>>>>> my breath.
Doesn't the cmpxchg16b count as a double-wide CAS on x86-64 ?
I meant as part of c/c++.˙ There really no legitimate technical
reason it's not in the standard now, it's just they can't put
it in the way they would like too, so they refuse to put it in
at all.˙ The way they would like to is an atomic<T> where T would
be an 8 byte type.˙ But they can't since there are no atomic 8 byte
load and store instructions on most existing x86-64 processors
and that's the only way they are willing to do it.
Okay.
There is a strong reluctance to put things into the standards if
they can't be realistically and practically implemented on most
platforms.
But C++ /does/ support atomic<T> types, where T is 16-bytes - or any
size at all.˙ And it supports compare_exchange() methods (strong and
weak) on those types.
As far as I can see, it's up to the implementation how those are
handled.˙ gcc on x86-64 does so with a library call
"__atomic_compare_exchange_16".˙ (Bigger sizes use a more general
__atomic_compare_exchange call.)˙ If the target supports a dedicated
instruction here, and the instruction is more efficient than the
library call (that's usually the case!), then this is just a
compiler quality of implementation issue.˙ When someone adds this to
the compiler and/or gcc C++ standard library implementation, it
should just work when appropriate target selection flags are used.
It is a QOI, as far as I can tell. Checked it a while ago, and it
says that the DWCAS is not lock free. That scares me. If its not lock
free, then there is no reason to use it for these types of things. ;^o
The answer then would be to file an issue with the gcc bugzilla (since
it's their C++ standard library implementation you are looking at).
Feel free to post my inline assembly there in the issue, and post the
issue link here.
It should be something akin to, why do I feel the need to code this is
ASM when the C/C++11 should already do it for me. That type of angle?
On 9/30/2026 12:54 AM, David Brown wrote:
On 30/09/2026 01:30, Chris M. Thomasson wrote:
On 9/28/2026 11:40 PM, David Brown wrote:
gcc inline assembly is a /much/ better solution here.˙ It's okay ifDifficult to totally disagree with that David. Shit. Its my own
you don't know how to write it - and it is certainly okay if you did
not know how to do so a decade ago.˙ But you should understand why
it is better, and then decide if you are going to learn about it.
(Of course for this particular case, if you can persuade the gcc C++
library folk to make their standard cas16 lock-free, then using the
standard function is best.)
personal problem with the syntax of GCC inline asm. I am a bit scared
of it. Sigh. Clobbers what? When and where? cdecl for the ABI and
externally assembled functions are fine for me, LTO aside for a
moment, can be bad do not go into my asm! BUT. Again. Shit. Its a
personal problem wrt me saying inline bad! Sorry!
Not knowing the syntax for gcc inline assembly is not a "problem" as
such - most C programmers never need it.˙ It is just a strong
recommendation that /if/ you want to write assembly code, then your
results would be much improved by using inline assembly format rather
than external assembly functions.˙ And you are already familiar with
the hard part, the actual assembly.˙ So don't look at this as a
problem of any sort - look at it as a fun, challenging and useful new
direction for your existing skills and interests.
Also, if C++ gains a LOCK CMPXCHG16B for a double word, two
contiguous 64 bit words on a 64 bit machine, well GOOD! Show me. I
have had some experiences with it trying to tell me its not lock free.
The C++ standard library supports 16 byte compare-and-exchange.˙ The
gcc implementation of that library does not use that instruction, and
thus does not support lock-free 16 byte compare-and-exchange.˙ C++ has
supported this since C++11 - this is a QOI issue, not a language or
standard library issue.˙ File an issue on the gcc bugzilla - that's
how you get progress (or how we will learn if there are good reasons
not to use it.)
I first need to show why its a qoi to begin with. If the arch supports a single instruction for a double word atomic rmw op (DWCAS), it should be using that instruction. And! it needs to report it as always lock-free.
Then the user has all the info they need for a fallback to asm. Then
show a bunch of examples where its needed. But that should not be all
that hard. Say okay, if the C/C++ does not make this atomic RMW lock-
free on an arch that supports it, then its useless for anything lock-
free on said arch. ASM to the rescue?
On 02/10/2026 20:13, Chris M. Thomasson wrote:
It should be something akin to, why do I feel the need to code this is
ASM when the C/C++11 should already do it for me. That type of angle?
Something along these lines should be fine. There may be some
underlying reason why this is implemented as a library call in gcc's C++ >standard library, rather than a direct lock-free opcode. I don't know
the details of any of this - maybe there are flaws in some chips, maybe
the single op-code actually takes longer than a lock-based
implementation. Maybe it's just that no one has got round to
implementing it yet.
On Sat, 3 Oct 2026 14:19:56 +0200
David Brown <david.brown@hesbynett.no> gabbled:
On 02/10/2026 20:13, Chris M. Thomasson wrote:
It should be something akin to, why do I feel the need to code this
is ASM when the C/C++11 should already do it for me. That type of angle?
Something along these lines should be fine.˙ There may be some
underlying reason why this is implemented as a library call in gcc's
C++ standard library, rather than a direct lock-free opcode.˙ I don't
know the details of any of this - maybe there are flaws in some chips,
maybe the single op-code actually takes longer than a lock-based
implementation.˙ Maybe it's just that no one has got round to
implementing it yet.
I would guess the reason is consistent behaviour across different architectures.
On 10/3/2026 8:50 AM, boltar@caprica.universe wrote:
On Sat, 3 Oct 2026 14:19:56 +0200
David Brown <david.brown@hesbynett.no> gabbled:
On 02/10/2026 20:13, Chris M. Thomasson wrote:
It should be something akin to, why do I feel the need to code this
is ASM when the C/C++11 should already do it for me. That type of
angle?
Something along these lines should be fine.˙ There may be some
underlying reason why this is implemented as a library call in gcc's
C++ standard library, rather than a direct lock-free opcode.˙ I don't
know the details of any of this - maybe there are flaws in some
chips, maybe the single op-code actually takes longer than a lock-
based implementation.˙ Maybe it's just that no one has got round to
implementing it yet.
I would guess the reason is consistent behaviour across different
architectures.
Well, if the library call goes to LOCK CMPXCHG16B for what we need it
for then that would be okay. We need to step into the library call to
find out.
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent- sequential-rcu/RCU isn't necessary. If shared_ptr<X> would have an assignment operator
Am 17.09.2026 um 00:25 schrieb Joseph Seigh:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-RCU isn't necessary. If shared_ptr<X> would have an assignment operator
sequential-rcu/
that takes an atomic<shared_ptr<X>> on the right side and this assign-
ment operator would first compare its own pointer vs the current poin-
ter of the atomic. If both are the same nothing further happens - and
the cachelines holding the atomic<shared_ptr<X>> could remain in shared
mode.
I implemented that and the following code is the assignment-operator
of shared_obj<T> taking a tshared_obj<T>. The fast path is in the first method and the slow path is in the second method:[...]
On 10/5/2026 10:00 AM, Bonita Montero wrote:
Am 17.09.2026 um 00:25 schrieb Joseph Seigh:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-RCU isn't necessary. If shared_ptr<X> would have an assignment operator
sequential-rcu/
that takes an atomic<shared_ptr<X>> on the right side and this assign-
ment operator would first compare its own pointer vs the current poin-
ter of the atomic. If both are the same nothing further happens - and
the cachelines holding the atomic<shared_ptr<X>> could remain in shared
mode.
I implemented that and the following code is the assignment-operator
of shared_obj<T> taking a tshared_obj<T>. The fast path is in the first
method and the slow path is in the second method:[...]
Not sure what you mean. Traditional RCU reads do not need any atomic
RMW's and/or memory barriers (alpha aside for a moment).
Not sure what you mean. Traditional RCU reads do not need any atomic
RMW's and/or memory barriers (alpha aside for a moment).
The whole point of using lock-free algorithms here, and the double compare-and-swap, is speed.˙ If you use external assembly so that all
your important data has to be pushed from registers to the stack, then
you have a call, then you pull the data off the stack again - you've a
dozen extra instructions at the call site, a dozen extra instructions at
the cas16 site, doing nothing except spoiling your efficient code.
Am 29.09.2026 um 08:40 schrieb David Brown:
The whole point of using lock-free algorithms here, and the double
compare-and-swap, is speed.˙ If you use external assembly so that all
your important data has to be pushed from registers to the stack, then
you have a call, then you pull the data off the stack again - you've a
dozen extra instructions at the call site, a dozen extra instructions
at the cas16 site, doing nothing except spoiling your efficient code.
A CAS16 is that slow that the additional call doesn't hurt.
And usually a CAS involves remote cacheline re-loading which
is a magnitude more expensive than a trivial function call.
And equally, while a function call can have low overheads if everything
is in the instruction cache, if the code for it has to be pulled in from main memory then it will add significant extra time.
Am 06.10.2026 um 08:36 schrieb David Brown:
And equally, while a function call can have low overheads if
everything is in the instruction cache, if the code for it has to be
pulled in from main memory then it will add significant extra time.
Absolutely not, theres an instruction and a uOp cache.
Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:
On 10/5/2026 10:00 AM, Bonita Montero wrote:
Am 17.09.2026 um 00:25 schrieb Joseph Seigh:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-RCU isn't necessary. If shared_ptr<X> would have an assignment operator
sequential-rcu/
that takes an atomic<shared_ptr<X>> on the right side and this assign-
ment operator would first compare its own pointer vs the current poin-
ter of the atomic. If both are the same nothing further happens - and
the cachelines holding the atomic<shared_ptr<X>> could remain in shared
mode.
I implemented that and the following code is the assignment-operator
of shared_obj<T> taking a tshared_obj<T>. The fast path is in the first
method and the slow path is in the second method:[...]
Not sure what you mean. Traditional RCU reads do not need any atomic
RMW's and/or memory barriers (alpha aside for a moment).
If I try to update my shared_obj<T> from a tshared_obj<T> and both are
the same there are no atomic RMW-operations. Just a read of the pointer
in memory which is in shared state in the cache. The update is just
about a nanosecond if the tshared_obj<T> is still the same as at the
last time I updated my shared_obj<T>.
RCU is rather silly compared to that.
Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:
Not sure what you mean. Traditional RCU reads do not need any atomic
RMW's and/or memory barriers (alpha aside for a moment).
My idea is that a thread periodically polls for updates on shared data. Between these updates the data may become stale. The thread-local poin-
ter is inside a shared_obj<T>. The central threaded analogy for an atomic<shared_ptr<T>> is my tshared_obj<T>.
As long as the shared tshared_obj<T> doesn't change the updates are
nearly for free. If the object changes the access is locked, but not
with a separate futex; the pointer itself is the futex, meaning that
it goes to -1 while there's a deferred update of the reference coun-
ter the object points to. The pointer changes back to its initial
value once the change has completed.
The following diagram shows the micro-benchmark erformance of updating
a shared_obj<T> periodically from a tshred_obj<T>, which itself doesn't change. The scaling is logarithmic. As you see the update is nearly for
free. The atomic shared_ptr's update is up to more than 10.000 times slower:[...]
On 06/10/2026 10:38, Bonita Montero wrote:
Am 06.10.2026 um 08:36 schrieb David Brown:
And equally, while a function call can have low overheads if
everything is in the instruction cache, if the code for it has to be
pulled in from main memory then it will add significant extra time.
Absolutely not, theres an instruction and a uOp cache.
Please re-read what I wrote.
It seems like you are trying to compare using RCU to actually implement
a strong atomic ref counted pointer. Of course RCU s not needed for
that. Afaict, you might be missing the forest for the trees?
Afaict, you are into the internals of implementing a strong atomic
refcount. ...
Am 06.10.2026 um 22:15 schrieb Chris M. Thomasson:
It seems like you are trying to compare using RCU to actually
implement a strong atomic ref counted pointer. Of course RCU s not
needed for that. Afaict, you might be missing the forest for the trees?
You don't understand my idea.
Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:[...]
Not sure what you mean. Traditional RCU reads do not need any atomic
RMW's and/or memory barriers (alpha aside for a moment).
My idea is that a thread periodically polls for updates on shared data. Between these updates the data may become stale. The thread-local poin-
ter is inside a shared_obj<T>. The central threaded analogy for an atomic<shared_ptr<T>> is my tshared_obj<T>.
As long as the shared tshared_obj<T> doesn't change the updates are
nearly for free. If the object changes the access is locked, but not
with a separate futex; the pointer itself is the futex, meaning that
it goes to -1 while there's a deferred update of the reference coun-
ter the object points to. The pointer changes back to its initial
value once the change has completed.
The following diagram shows the micro-benchmark erformance of updating
a shared_obj<T> periodically from a tshred_obj<T>, which itself doesn't change. The scaling is logarithmic. As you see the update is nearly for
free. The atomic shared_ptr's update is up to more than 10.000 times
slower:
˙˙˙˙https://ibb.co/nqGHv9pL
This is the complete source:
Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:Bench mark it against Joes atomic ptr that uses DWCAS. Keep in mind that
Not sure what you mean. Traditional RCU reads do not need any atomic
RMW's and/or memory barriers (alpha aside for a moment).
My idea is that a thread periodically polls for updates on shared data. Between these updates the data may become stale. The thread-local poin-
ter is inside a shared_obj<T>. The central threaded analogy for an atomic<shared_ptr<T>> is my tshared_obj<T>.
As long as the shared tshared_obj<T> doesn't change the updates are
nearly for free. If the object changes the access is locked, but not
with a separate futex; the pointer itself is the futex, meaning that
it goes to -1 while there's a deferred update of the reference coun-
ter the object points to. The pointer changes back to its initial
value once the change has completed.
The following diagram shows the micro-benchmark erformance of updating
a shared_obj<T> periodically from a tshred_obj<T>, which itself doesn't change. The scaling is logarithmic. As you see the update is nearly for
free. The atomic shared_ptr's update is up to more than 10.000 times
slower:
On 10/5/2026 12:23 PM, Bonita Montero wrote:
Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:Bench mark it against Joes atomic ptr that uses DWCAS. Keep in mind that
Not sure what you mean. Traditional RCU reads do not need any atomic
RMW's and/or memory barriers (alpha aside for a moment).
My idea is that a thread periodically polls for updates on shared data.
Between these updates the data may become stale. The thread-local poin-
ter is inside a shared_obj<T>. The central threaded analogy for an
atomic<shared_ptr<T>> is my tshared_obj<T>.
As long as the shared tshared_obj<T> doesn't change the updates are
nearly for free. If the object changes the access is locked, but not
with a separate futex; the pointer itself is the futex, meaning that
it goes to -1 while there's a deferred update of the reference coun-
ter the object points to. The pointer changes back to its initial
value once the change has completed.
The following diagram shows the micro-benchmark erformance of updating
a shared_obj<T> periodically from a tshred_obj<T>, which itself doesn't
change. The scaling is logarithmic. As you see the update is nearly for
free. The atomic shared_ptr's update is up to more than 10.000 times
slower:
we can use it to make a proxy collector that has RCU like performance as well. Keep in mind that it involves a thread being able to take a
reference to something it does no already own. No locks. Once a
local_ptr is gained its basically equal to basic thread saftey normal
atomic refcount.
On 10/6/2026 1:42 AM, David Brown wrote:
On 06/10/2026 10:38, Bonita Montero wrote:
Am 06.10.2026 um 08:36 schrieb David Brown:
And equally, while a function call can have low overheads if
everything is in the instruction cache, if the code for it has to be
pulled in from main memory then it will add significant extra time.
Absolutely not, theres an instruction and a uOp cache.
Please re-read what I wrote.
I "think" he is confusing things... Using RCU for hyper fast read side,
vs implementing the guts of a strong atomic ref counter.
Bench mark it against Joes atomic ptr that uses DWCAS. ...
Are you trying to make a strong atomic ref count safe or not?
RCU really does not have to have anything to do with that.
On 06/10/2026 22:19, Chris M. Thomasson wrote:
On 10/6/2026 1:42 AM, David Brown wrote:
On 06/10/2026 10:38, Bonita Montero wrote:
Am 06.10.2026 um 08:36 schrieb David Brown:
And equally, while a function call can have low overheads ifAbsolutely not, theres an instruction and a uOp cache.
everything is in the instruction cache, if the code for it has to
be pulled in from main memory then it will add significant extra time. >>>>
Please re-read what I wrote.
I "think" he is confusing things... Using RCU for hyper fast read
side, vs implementing the guts of a strong atomic ref counter.
I am not sure you read the posts either.
I wrote "while a function call can have low overheads if everything is
in the instruction cache ...", and Bonita replied "Absolutely not,
there's an instruction cache".
Then you followed up with something completely different.
Am 07.10.2026 um 06:29 schrieb Chris M. Thomasson:
Are you trying to make a strong atomic ref count safe or not?
RCU really does not have to have anything to do with that.
My idea is totally different from RCU, but you can do the same
thing with it with nearly no overhad.
Am 07.10.2026 um 06:33 schrieb Chris M. Thomasson:
Bench mark it against Joes atomic ptr that uses DWCAS. ...
You still dont' understand my idea. Imagine you've got a shared_ptr<X>
and you constantly poll an atomic<shared_ptr<X>> with this to ger a
up to date version of the shared data. With the current atomic impl.
the at<sp<X>> is converted to a sp<X> and then this value is move
-assigned to the sp<X>. This has a big overhad through the atomic
increments of the reference counter.
Imagine now that the at<sp<X>> has an assignment operator that takes
an sp<X> reference directly and not only for it's own type. This ope-
rator could simply compare its own value with the at<sp<X>>'s value
and if this value hasn't changed it does nothing further. This would
mean as long as there are no updates of the at<sp<X>> this operation
would proceed in a nanosecond since the shared data and it's reference counter's cache lines would remain in shared mode and there would be
no additional cachline loads.
With RCU you take a snapshot of some shared data, read it, mark it
as discadable and the cleanup would take place asynchronously.
my idea that's not much different since polling and updating the
thread-local "sp<X>" nearly costs nothing.
This is an example where I used my shared_obj<T>, which is constantly poll-updated from a tshared_obj<T>. As long as the latter doesn't change
this is nearly for free.
template<bool Utc, bool AlsoEqual>
auto date_1601::lookUpLeap( int64_t ts ) noexcept -> lulp_ret
{
˙˙˙˙thread_local shared_obj<leaps_set, sobj_mode::All> pLeaps;
˙˙˙˙pLeaps = g_leapSeconds; // update
˙˙˙˙span leaps = pLeaps->m_leaps;
˙˙˙˙auto
˙˙˙˙˙˙˙ itBegin = leaps.rbegin(),
˙˙˙˙˙˙˙ itEnd = leaps.rend(),
˙˙˙˙˙˙˙ itBefore = itBegin;
˙˙˙˙if constexpr( AlsoEqual )
˙˙˙˙˙˙˙ itBefore = lower_bound( itBegin, itEnd, ts, []( const
dual_deaps &leap, int64_t ref ) { return leap[Utc] > ref; } );
˙˙˙˙else
˙˙˙˙˙˙˙ itBefore = upper_bound( itBegin, itEnd, ts, []( int64_t ref,
const dual_deaps &leap ) { return ref > leap[Utc]; } );
˙˙˙˙if( itBefore == itEnd )
˙˙˙˙˙˙˙ return lulp_ret( -1, 0 );
˙˙˙˙return lulp_ret( leaps.rend() - itBefore - 1, (*itBefore)[Utc] );
}
In this example I'm dealing with a central storage of leap seconds which
may been updated asynchronously. This updates woudln't happen so often, meaning the commented update assingment is nearly for free.
˙thread_local shared_obj<leaps_set, sobj_mode::All> pLeaps;Like with RCU no thread owns the data once its published since it is
? So, are you taking a reference to something the thread does not own?
Huh? You are either making a strong atomic refcount or not.
So, you are making a proxy collector? I am quite fond of them. Joe knows
all about them.
On 9/29/26 7:30 PM, Chris M. Thomasson wrote:
On 9/28/2026 11:40 PM, David Brown wrote:...
On 29/09/2026 00:42, Chris M. Thomasson wrote:
Difficult to totally disagree with that David. Shit. Its my own
(Of course for this particular case, if you can persuade the gcc C++
library folk to make their standard cas16 lock-free, then using the
standard function is best.)
personal problem with the syntax of GCC inline asm. I am a bit scared
of it. Sigh. Clobbers what? When and where? cdecl for the ABI and
externally assembled functions are fine for me, LTO aside for a
moment, can be bad do not go into my asm! BUT. Again. Shit. Its a
personal problem wrt me saying inline bad! Sorry!
Also, if C++ gains a LOCK CMPXCHG16B for a double word, two contiguous
64 bit words on a 64 bit machine, well GOOD! Show me. I have had some
experiences with it trying to tell me its not lock free.
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878
The rationale seems to be you can mix a lock-free operation
with locked operations since __atomic_always_lock_free and __atomic_is_lock_free apply to the data type not specific
builtins.
Given that there seems to be no shortage of x86 specific
gcc builtins, I can't think of any reason why there's
no cmpxchg16b builtin.
Am 07.10.2026 um 22:12 schrieb Chris M. Thomasson:
So, you are making a proxy collector? I am quite fond of them. Joe
knows all about them.
I think Joe understood my idea and you didn't.
Am 07.10.2026 um 22:17 schrieb Chris M. Thomasson:
˙thread_local shared_obj<leaps_set, sobj_mode::All> pLeaps;
? So, are you taking a reference to something the thread does not own?Like with RCU no thread owns the data once its published since it is
read only.
On 10/7/2026 8:55 PM, Bonita Montero wrote:
Am 07.10.2026 um 22:17 schrieb Chris M. Thomasson:
˙thread_local shared_obj<leaps_set, sobj_mode::All> pLeaps;Like with RCU no thread owns the data once its published since it is
? So, are you taking a reference to something the thread does not own?
read only.
Well, your work sure seems to act like a proxy. But it uses CAS and
futex. Therefore its out of scope with anything lock-free.
On 03/10/2026 23:12, Chris M. Thomasson wrote:
On 10/3/2026 8:50 AM, boltar@caprica.universe wrote:
On Sat, 3 Oct 2026 14:19:56 +0200
David Brown <david.brown@hesbynett.no> gabbled:
On 02/10/2026 20:13, Chris M. Thomasson wrote:
It should be something akin to, why do I feel the need to code this >>>>> is ASM when the C/C++11 should already do it for me. That type of
angle?
Something along these lines should be fine.˙ There may be some
underlying reason why this is implemented as a library call in gcc's
C++ standard library, rather than a direct lock-free opcode.˙ I
don't know the details of any of this - maybe there are flaws in
some chips, maybe the single op-code actually takes longer than a
lock- based implementation.˙ Maybe it's just that no one has got
round to implementing it yet.
I would guess the reason is consistent behaviour across different
architectures.
Well, if the library call goes to LOCK CMPXCHG16B for what we need it
for then that would be okay. We need to step into the library call to
find out.
From what I have looked at, including the bugzilla link Joseph posted, there seem to be a number of complications involved.˙ A particular
sticking point is that you can't have lock-free C++ standard atomic
types without also having atomic load and store operations of the same
size.˙ And while the 16-byte CAS can be used for loads and stores, it
can't be used for 16-byte atomic loads on read-only memory.˙ This means
that on older 64-bit x86 processors, 16-byte atomic types cannot be lock-free even if they support 16-byte CAS.
It seems that the libatomic implementation does, in some cases, use the CMPXCHG16B instruction rather than a mutex.Thank GOD! Then they can make their C++ impl report that this is always lock-free?
The exact code run in the
library depends on the cpuid of the processor, as well as compiler
flags.˙ (The default "lowest common denominator" x86-64 instruction set
does not support CMPXCHG16B.)
So the C++ standard library atomics do not lead directly to the use of
the lock-free instruction.˙ But if you have the "-mxc16" compiler flag
(or use a -march flag for a cpu family that supports it), then you can
use the low-level builtin __sync_bool_compare_and_swap to generate it.
Better still, in gcc 17 (in development) you can use the "-m128bit-
atomic" flag to get full 16-byte atomics on processors for which the GCC folks have assurances from Intel or AMD that the 128-bit SSE2 vector
loads and stores are atomic.˙ You can try this on godbolt.org.
On 03/10/2026 23:12, Chris M. Thomasson wrote:
On 10/3/2026 8:50 AM, boltar@caprica.universe wrote:
On Sat, 3 Oct 2026 14:19:56 +0200
David Brown <david.brown@hesbynett.no> gabbled:
On 02/10/2026 20:13, Chris M. Thomasson wrote:
It should be something akin to, why do I feel the need to code this >>>>> is ASM when the C/C++11 should already do it for me. That type of
angle?
Something along these lines should be fine.˙ There may be some
underlying reason why this is implemented as a library call in gcc's
C++ standard library, rather than a direct lock-free opcode.˙ I
don't know the details of any of this - maybe there are flaws in
some chips, maybe the single op-code actually takes longer than a
lock- based implementation.˙ Maybe it's just that no one has got
round to implementing it yet.
I would guess the reason is consistent behaviour across different
architectures.
Well, if the library call goes to LOCK CMPXCHG16B for what we need it
for then that would be okay. We need to step into the library call to
find out.
From what I have looked at, including the bugzilla link Joseph posted, there seem to be a number of complications involved.˙ A particular
sticking point is that you can't have lock-free C++ standard atomic
types without also having atomic load and store operations of the same
size.˙ And while the 16-byte CAS can be used for loads and stores, it
can't be used for 16-byte atomic loads on read-only memory.˙ This means
that on older 64-bit x86 processors, 16-byte atomic types cannot be lock-free even if they support 16-byte CAS.
It seems that the libatomic implementation does, in some cases, use the CMPXCHG16B instruction rather than a mutex.˙ The exact code run in the library depends on the cpuid of the processor, as well as compiler
flags.˙ (The default "lowest common denominator" x86-64 instruction set
does not support CMPXCHG16B.)
So the C++ standard library atomics do not lead directly to the use of
the lock-free instruction.˙ But if you have the "-mxc16" compiler flag
(or use a -march flag for a cpu family that supports it), then you can
use the low-level builtin __sync_bool_compare_and_swap to generate it.
Better still, in gcc 17 (in development) you can use the "-m128bit-
atomic" flag to get full 16-byte atomics on processors for which the GCC folks have assurances from Intel or AMD that the 128-bit SSE2 vector
loads and stores are atomic.˙ You can try this on godbolt.org.
Am 06.10.2026 um 22:18 schrieb Chris M. Thomasson:
Afaict, you are into the internals of implementing a strong atomic
refcount. ...
You don't understand my idea - check the code.
Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:
Not sure what you mean. Traditional RCU reads do not need any atomic
RMW's and/or memory barriers (alpha aside for a moment).
My idea is that a thread periodically polls for updates on shared data. Between these updates the data may become stale. The thread-local poin-
ter is inside a shared_obj<T>. The central threaded analogy for an atomic<shared_ptr<T>> is my tshared_obj<T>.
As long as the shared tshared_obj<T> doesn't change the updates are
nearly for free. If the object changes the access is locked, but not
with a separate futex; the pointer itself is the futex, meaning that
it goes to -1 while there's a deferred update of the reference coun-
ter the object points to. The pointer changes back to its initial
value once the change has completed.
The following diagram shows the micro-benchmark erformance of updating
a shared_obj<T> periodically from a tshred_obj<T>, which itself doesn't change. The scaling is logarithmic. As you see the update is nearly for
free. The atomic shared_ptr's update is up to more than 10.000 times
slower:
˙˙˙˙https://ibb.co/nqGHv9pL
This is the complete source:[...]
Am 05.10.2026 um 19:57 schrieb Chris M. Thomasson:[...]
Well, your work sure seems to act like a proxy. But it uses CAS and
futex. Therefore its out of scope with anything lock-free.
I am not sure if you understand your own idea. ...
I think you should use his atomic_ptr and be done with it. It pure lock- free. Yours is not: ...
On 10/4/2026 2:34 AM, David Brown wrote:
On 03/10/2026 23:12, Chris M. Thomasson wrote:
On 10/3/2026 8:50 AM, boltar@caprica.universe wrote:
On Sat, 3 Oct 2026 14:19:56 +0200
David Brown <david.brown@hesbynett.no> gabbled:
On 02/10/2026 20:13, Chris M. Thomasson wrote:
It should be something akin to, why do I feel the need to code
this is ASM when the C/C++11 should already do it for me. That
type of angle?
Something along these lines should be fine.˙ There may be some
underlying reason why this is implemented as a library call in
gcc's C++ standard library, rather than a direct lock-free opcode. >>>>> I don't know the details of any of this - maybe there are flaws in
some chips, maybe the single op-code actually takes longer than a
lock- based implementation.˙ Maybe it's just that no one has got
round to implementing it yet.
I would guess the reason is consistent behaviour across different
architectures.
Well, if the library call goes to LOCK CMPXCHG16B for what we need it
for then that would be okay. We need to step into the library call to
find out.
˙From what I have looked at, including the bugzilla link Joseph
posted, there seem to be a number of complications involved.˙ A
particular sticking point is that you can't have lock-free C++
standard atomic types without also having atomic load and store
operations of the same size.˙ And while the 16-byte CAS can be used
for loads and stores, it can't be used for 16-byte atomic loads on
read-only memory.˙ This means that on older 64-bit x86 processors, 16-
byte atomic types cannot be lock-free even if they support 16-byte CAS.
Shit... Well, damn that kind of ruins things. Well, because I do not
even need atomic loads/stores of the DWCAS (aka, two contiguous words)!
Sigh. I just want C++ to have a always lock-free DWCAS on systems that support it. The loads and stores do not even need to be there. But, see, that is for my personal use. Sigh. Not good enough for the std?
Thank GOD! Then they can make their C++ impl report that this is always lock-free?
It seems that the libatomic implementation does, in some cases, use
the CMPXCHG16B instruction rather than a mutex.
The exact code run in the library depends on the cpuid of the
processor, as well as compiler flags.˙ (The default "lowest common
denominator" x86-64 instruction set does not support CMPXCHG16B.)
So the C++ standard library atomics do not lead directly to the use of
the lock-free instruction.˙ But if you have the "-mxc16" compiler flag
(or use a -march flag for a cpu family that supports it), then you can
use the low-level builtin __sync_bool_compare_and_swap to generate it.
Better still, in gcc 17 (in development) you can use the "-m128bit-
atomic" flag to get full 16-byte atomics on processors for which the
GCC folks have assurances from Intel or AMD that the 128-bit SSE2
vector loads and stores are atomic.˙ You can try this on godbolt.org.
Am 08.10.2026 um 21:19 schrieb Chris M. Thomasson:
Well, your work sure seems to act like a proxy. But it uses CAS and
futex. Therefore its out of scope with anything lock-free.
You don't understand it.
Am 07.10.2026 um 22:13 schrieb Chris M. Thomasson:
Huh? You are either making a strong atomic refcount or not.
With RCU a newer version of the dater may have been published and a
thread is working with an older version. With my idea it's the same.
Afaict, you idea is ridden with bugs.
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent- sequential-rcu/
The name doesn't really mean anything. I was just generalizing something that's been around forever.*˙ It's simple enough that it must be in use somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
seqlock documentation and it specifically says not to use pointers in
seqlock protected data and no idea why that is unless the linux kernel
is doing something weird with memory mapping.
Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
that would be a better choice.˙ It might be good for libraries where
the memory reclamation is hidden from the application.
* tangentially related to something that I was working on which if far
more perverse but mostly in a solution that is looking for a problem
phase so it probably won't go anywhere.
Am 10.10.2026 um 23:20 schrieb Chris M. Thomasson:
Afaict, you idea is ridden with bugs.
I've written unit- and performance-tests.
On 9/16/26 6:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
sequential-rcu/
The name doesn't really mean anything. I was just generalizing something
that's been around forever.*˙ It's simple enough that it must be in use
somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
seqlock documentation and it specifically says not to use pointers in
seqlock protected data and no idea why that is unless the linux kernel
is doing something weird with memory mapping.
Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
that would be a better choice.˙ It might be good for libraries where
the memory reclamation is hidden from the application.
* tangentially related to something that I was working on which if far
more perverse but mostly in a solution that is looking for a problem
phase so it probably won't go anywhere.
I threw together a really quick and dirty test to see how much
overhead all those extra loads would cause.˙ Doesn't appear to
add any.˙ I will have to add it to the proxies project testcase
for more extensive testing but it will take a bit of work since
all the other proxies implement BasicLock and consequential RCU
readers do not.
If you get some free time to burn, add my proxy to your test bench. Its simply not meant to beat fancy asymmetric RCU+SMR type algos, but it
works in 100% std C++. Which is fairly decent.
https://pastebin.com/raw/CYZ78gVj
It should just compile right up and run.
On 10/11/26 5:10 PM, Chris M. Thomasson wrote:
If you get some free time to burn, add my proxy to your test bench.
Its simply not meant to beat fancy asymmetric RCU+SMR type algos, but
it works in 100% std C++. Which is fairly decent.
https://pastebin.com/raw/CYZ78gVj
It should just compile right up and run.
SMRProxy read lock is just an atomic load and store w/o membar.
I did a quick and dirty perf test of what would be the typical
user space RCU read lock logic, incrementing a local counter.
An atomic load, increment of loaded value, and atomic store
of that value is basically just as fast as SMRProxy, despite
that increment.˙˙ An atomic increment is quite a bit slower
on x86 since you have a full memory barrier there.˙ You
can't use asymmetric memory barriers to avoid that in this
case.
QSBR and EBR are basically the same thing.˙ Grace periods are
epochs.˙ The only difference is the QSBR logic to decide what
grace period each reader is referencing is a bit more indirect.
You are looking at events rather than conditions.˙ I didn't
do a user space RCU implementation since I didn't want to
deal with that extra logic.˙ And there are enough user space
RCU implementations out there already.
On 10/11/2026 6:44 PM, Joseph Seigh wrote:[...]
On 10/11/26 5:10 PM, Chris M. Thomasson wrote:
Sometimes I feel like getting back into these. Too busy with vector
fields and fractals now. Having a lot of fun with compute and frag
shaders, etc...
Have you ran them against any race detectors?
I've ran the code with a competition of 32 threads.
On 10/11/2026 8:04 PM, Chris M. Thomasson wrote:
On 10/11/2026 6:44 PM, Joseph Seigh wrote:[...]
On 10/11/26 5:10 PM, Chris M. Thomasson wrote:
Sometimes I feel like getting back into these. Too busy with vector
fields and fractals now. Having a lot of fun with compute and frag
shaders, etc...
Actually, I need to find out if the current Relacy can simulate async membars, akin to FlushProcessWriteBuffers. If it can, wow! I should just
ask Dmitriy. Have not conversed with him for a while.
Nice, 32 threads is a good load! Actually, can you tell me a bit morestatic void timedTest( char const *what, auto fn, bool forTbl );
about what the test did? Mainly... did the shared pointer actually
change during the run (and how often), and did anything check the
results at the end, like total allocated vs. total freed?
On 09/10/2026 02:13, Chris M. Thomasson wrote:
On 10/4/2026 2:34 AM, David Brown wrote:
On 03/10/2026 23:12, Chris M. Thomasson wrote:
On 10/3/2026 8:50 AM, boltar@caprica.universe wrote:
On Sat, 3 Oct 2026 14:19:56 +0200
David Brown <david.brown@hesbynett.no> gabbled:
On 02/10/2026 20:13, Chris M. Thomasson wrote:
It should be something akin to, why do I feel the need to code
this is ASM when the C/C++11 should already do it for me. That
type of angle?
Something along these lines should be fine.˙ There may be some
underlying reason why this is implemented as a library call in
gcc's C++ standard library, rather than a direct lock-free opcode. >>>>>> I don't know the details of any of this - maybe there are flaws in >>>>>> some chips, maybe the single op-code actually takes longer than a >>>>>> lock- based implementation.˙ Maybe it's just that no one has got
round to implementing it yet.
I would guess the reason is consistent behaviour across different
architectures.
Well, if the library call goes to LOCK CMPXCHG16B for what we need
it for then that would be okay. We need to step into the library
call to find out.
˙From what I have looked at, including the bugzilla link Joseph
posted, there seem to be a number of complications involved.˙ A
particular sticking point is that you can't have lock-free C++
standard atomic types without also having atomic load and store
operations of the same size.˙ And while the 16-byte CAS can be used
for loads and stores, it can't be used for 16-byte atomic loads on
read-only memory.˙ This means that on older 64-bit x86 processors,
16- byte atomic types cannot be lock-free even if they support 16-
byte CAS.
Shit... Well, damn that kind of ruins things. Well, because I do not
even need atomic loads/stores of the DWCAS (aka, two contiguous words)!
Sigh. I just want C++ to have a always lock-free DWCAS on systems that
support it. The loads and stores do not even need to be there. But,
see, that is for my personal use. Sigh. Not good enough for the std?
Standard libraries are not about individuals.
"Always lock-free" means /always/ lock-free.˙ If the target does not
support lock-free versions of all the operations on the atomic type, the atomic type is not "always lock-free".˙ (As far as I can tell, this is a limitation of the x86 processors, perhaps also partly a failure of AMD
and Intel to document guarantees about atomic loads and stores.˙ It is
not a failure of the gcc C++ standard library.)
It seems that the libatomic implementation does, in some cases, useThank GOD! Then they can make their C++ impl report that this is
the CMPXCHG16B instruction rather than a mutex.
always lock-free?
Or perhaps thank the libatomic authors and the GCC C++ standard library implementers for making a correct implementation?
No, their library cannot say that 128-bit types are "always lock-free" unless they /are/ always lock-free.˙ You can always test gcc 17 with the "-m128bit-atomic" flag, assuming you have an appropriate target
processor (or just look at it on godbolt.org).
The exact code run in the library depends on the cpuid of the
processor, as well as compiler flags.˙ (The default "lowest common
denominator" x86-64 instruction set does not support CMPXCHG16B.)
So the C++ standard library atomics do not lead directly to the use
of the lock-free instruction.˙ But if you have the "-mxc16" compiler
flag (or use a -march flag for a cpu family that supports it), then
you can use the low-level builtin __sync_bool_compare_and_swap to
generate it.
Better still, in gcc 17 (in development) you can use the "-m128bit-
atomic" flag to get full 16-byte atomics on processors for which the
GCC folks have assurances from Intel or AMD that the 128-bit SSE2
vector loads and stores are atomic.˙ You can try this on godbolt.org.
static void timedTest( char const *what, auto fn, bool forTbl );
But I'm pretty sure you don't understand the code since you didn't
understand the basic idea.
Am 12.10.2026 um 05:31 schrieb Chris M. Thomasson:[...]
Nice, 32 threads is a good load! Actually, can you tell me a bit more
about what the test did? Mainly... did the shared pointer actually
change during the run (and how often), and did anything check the
results at the end, like total allocated vs. total freed?
On 10/11/2026 9:32 PM, Bonita Montero wrote:
Am 12.10.2026 um 05:31 schrieb Chris M. Thomasson:[...]
Nice, 32 threads is a good load! Actually, can you tell me a bit more
about what the test did? Mainly... did the shared pointer actually
change during the run (and how often), and did anything check the
results at the end, like total allocated vs. total freed?
Where is sobj_mode defined, and is that the same shared_obj header you posted before?
Your Load, Store and Xchg tests never change the pointer, so they only measure the fast path. ...
one thread republishing through StoreAlt while the others run Load, ...
Am 12.10.2026 um 22:14 schrieb Chris M. Thomasson:
Your Load, Store and Xchg tests never change the pointer, so they only
measure the fast path. ...
For some tests, yes, but not all.
one thread republishing through StoreAlt while the others run Load, ...
No, all threads do the same.
On 10/12/2026 11:14 PM, Bonita Montero wrote:
Am 12.10.2026 um 22:14 schrieb Chris M. Thomasson:
Your Load, Store and Xchg tests never change the pointer, so they
only measure the fast path. ...
For some tests, yes, but not all.
one thread republishing through StoreAlt while the others run Load, ...
No, all threads do the same.
Try to make it uses say, reader and writer threads. Multiple readers iterating linked dynamic data structures while the writers are mutating
them all at the same time.
You may find that your futex is going be put under some stress in that scenario?
Where is sobj_mode defined, and is that the same shared_obj header you posted before?
On 9/16/26 6:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
sequential-rcu/
The name doesn't really mean anything. I was just generalizing something
that's been around forever.*˙ It's simple enough that it must be in use
somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
seqlock documentation and it specifically says not to use pointers in
seqlock protected data and no idea why that is unless the linux kernel
is doing something weird with memory mapping.
Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
that would be a better choice.˙ It might be good for libraries where
the memory reclamation is hidden from the application.
* tangentially related to something that I was working on which if far
more perverse but mostly in a solution that is looking for a problem
phase so it probably won't go anywhere.
I threw together a really quick and dirty test to see how much
overhead all those extra loads would cause.˙ Doesn't appear to
add any.˙ I will have to add it to the proxies project testcase
for more extensive testing but it will take a bit of work since
all the other proxies implement BasicLock and consequential RCU
readers do not.
Am 13.10.2026 um 09:10 schrieb Chris M. Thomasson:
You may find that your futex is going be put under some stress in that
scenario?
My code is optimized for RCU-like patterns.
For all other operations futex'd synchronization is inevitable.
On 10/11/26 11:11 AM, Joseph Seigh wrote:
On 9/16/26 6:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
sequential-rcu/
The name doesn't really mean anything. I was just generalizing something >>> that's been around forever.*˙ It's simple enough that it must be in use
somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
seqlock documentation and it specifically says not to use pointers in
seqlock protected data and no idea why that is unless the linux kernel
is doing something weird with memory mapping.
Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
that would be a better choice.˙ It might be good for libraries where
the memory reclamation is hidden from the application.
* tangentially related to something that I was working on which if far
more perverse but mostly in a solution that is looking for a problem
phase so it probably won't go anywhere.
I threw together a really quick and dirty test to see how much
overhead all those extra loads would cause.˙ Doesn't appear to
add any.˙ I will have to add it to the proxies project testcase
for more extensive testing but it will take a bit of work since
all the other proxies implement BasicLock and consequential RCU
readers do not.
This is what the code I did a quick and dirty test with looked like
using csequence_t = uint64_t;
inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> & sequence)
{
˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
˙˙˙ return mark != sequence.load(std::memory_order_acquire);
}
csequence_t mark;
retry:
do {
˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
˙˙˙ node_t * item = list.load(std::memory_order_acquire);
˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
˙˙˙˙˙˙˙˙˙˙˙ goto retry;
˙˙˙ }
} while (cseq_retry(mark, cseq_count));
As you can see, not a heck of lot of room in that loop
for the cpu pipeline to deal with the extra load and
conditional logic.
On 10/13/2026 10:01 AM, Joseph Seigh wrote:
This is what the code I did a quick and dirty test with looked like
using csequence_t = uint64_t;
inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
sequence)
{
˙˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
˙˙˙˙ return mark != sequence.load(std::memory_order_acquire);
}
csequence_t mark;
retry:
do {
˙˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
˙˙˙˙ node_t * item = list.load(std::memory_order_acquire);
˙˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
˙˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
˙˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
˙˙˙˙˙˙˙˙˙˙˙˙ goto retry;
˙˙˙˙ }
} while (cseq_retry(mark, cseq_count));
As you can see, not a heck of lot of room in that loop
for the cpu pipeline to deal with the extra load and
conditional logic.
It seems like cseq_retry on "every" iteration with its, two?, #LoadStore
| #LoadLoad barriers is a bit much. Now that I have Relacy working fine
on my end, and it has all I need for simulating even advanced RCU+SMR
algos. Well, I will get back into it from time to time. Actually, can
you get it working? Its a neat way to simulate your sync experiments.
Wait I count an acquire on the item->next.load and then two acquires
from cseq_retry? Am I missing something critical here Joe?
On 10/13/26 3:36 PM, Chris M. Thomasson wrote:
On 10/13/2026 10:01 AM, Joseph Seigh wrote:
This is what the code I did a quick and dirty test with looked like
using csequence_t = uint64_t;
inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
sequence)
{
˙˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
˙˙˙˙ return mark != sequence.load(std::memory_order_acquire);
}
csequence_t mark;
retry:
do {
˙˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
˙˙˙˙ node_t * item = list.load(std::memory_order_acquire);
˙˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
˙˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
˙˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
˙˙˙˙˙˙˙˙˙˙˙˙ goto retry;
˙˙˙˙ }
} while (cseq_retry(mark, cseq_count));
As you can see, not a heck of lot of room in that loop
for the cpu pipeline to deal with the extra load and
conditional logic.
It seems like cseq_retry on "every" iteration with its, two?,
#LoadStore | #LoadLoad barriers is a bit much. Now that I have Relacy
working fine on my end, and it has all I need for simulating even
advanced RCU+SMR algos. Well, I will get back into it from time to
time. Actually, can you get it working? Its a neat way to simulate
your sync experiments.
Wait I count an acquire on the item->next.load and then two acquires
from cseq_retry? Am I missing something critical here Joe?
It would appear that way.˙˙ That's what quick and dirty means. :)
If the redundant load/load gets take out then there needs to be
some other mechanism or documentation to ensure the loads are in
order.
m_pData = pDtaRef;
m_pData->template incr<memory_order_acquire>();
tso.m_pAtmData.store( pDtaRef, memory_order_relaxed ); tso.m_pAtmData.notify_all();
Am 13.10.2026 um 21:29 schrieb Chris M. Thomasson:
m_pData = pDtaRef;
m_pData->template incr<memory_order_acquire>();
tso.m_pAtmData.store( pDtaRef, memory_order_relaxed );
tso.m_pAtmData.notify_all();
No, the code is fine.
On 10/13/26 3:36 PM, Chris M. Thomasson wrote:[...]
On 10/13/2026 10:01 AM, Joseph Seigh wrote:
This is what the code I did a quick and dirty test with looked like
using csequence_t = uint64_t;
inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
sequence)
{
˙˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
˙˙˙˙ return mark != sequence.load(std::memory_order_acquire);
}
csequence_t mark;
retry:
do {
˙˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
˙˙˙˙ node_t * item = list.load(std::memory_order_acquire);
˙˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
˙˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
˙˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
˙˙˙˙˙˙˙˙˙˙˙˙ goto retry;
˙˙˙˙ }
} while (cseq_retry(mark, cseq_count));
As you can see, not a heck of lot of room in that loop
for the cpu pipeline to deal with the extra load and
conditional logic.
On 10/13/2026 4:34 PM, Joseph Seigh wrote:Oh OUCH! Sorry Joe. This does not work in Relacy. I forgot to run that
On 10/13/26 3:36 PM, Chris M. Thomasson wrote:[...]
On 10/13/2026 10:01 AM, Joseph Seigh wrote:
This is what the code I did a quick and dirty test with looked like
using csequence_t = uint64_t;
inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
sequence)
{
˙˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
˙˙˙˙ return mark != sequence.load(std::memory_order_acquire);
}
csequence_t mark;
retry:
do {
˙˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
˙˙˙˙ node_t * item = list.load(std::memory_order_acquire);
˙˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
˙˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
˙˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
˙˙˙˙˙˙˙˙˙˙˙˙ goto retry;
˙˙˙˙ }
} while (cseq_retry(mark, cseq_count));
As you can see, not a heck of lot of room in that loop
for the cpu pipeline to deal with the extra load and
conditional logic.
Its interesting. Btw, I don't even need traditional SMR logic! This
works fine:
________________
per_node*
smr_load(
˙˙˙ unsigned long tidx
) {
˙˙˙ per_node* node0 = m_node.load(rl::mo_relaxed, $);
˙˙˙ m_threads[tidx].m_haz_0.store(node0, rl::mo_relaxed, $);
˙˙˙ return node0;
}
________________
On 10/13/26 3:36 PM, Chris M. Thomasson wrote:[...]
On 10/13/2026 10:01 AM, Joseph Seigh wrote:
This is what the code I did a quick and dirty test with looked like
using csequence_t = uint64_t;
inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
sequence)
{
˙˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
˙˙˙˙ return mark != sequence.load(std::memory_order_acquire);
}
csequence_t mark;
retry:
do {
˙˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
˙˙˙˙ node_t * item = list.load(std::memory_order_acquire);
˙˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
˙˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
˙˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
˙˙˙˙˙˙˙˙˙˙˙˙ goto retry;
˙˙˙˙ }
} while (cseq_retry(mark, cseq_count));
As you can see, not a heck of lot of room in that loop
for the cpu pipeline to deal with the extra load and
conditional logic.
On 10/13/26 3:36 PM, Chris M. Thomasson wrote:
On 10/13/2026 10:01 AM, Joseph Seigh wrote:
This is what the code I did a quick and dirty test with looked like
using csequence_t = uint64_t;
inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
sequence)
{
˙˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
˙˙˙˙ return mark != sequence.load(std::memory_order_acquire);
}
csequence_t mark;
retry:
do {
˙˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
˙˙˙˙ node_t * item = list.load(std::memory_order_acquire);
˙˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
˙˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
˙˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
˙˙˙˙˙˙˙˙˙˙˙˙ goto retry;
˙˙˙˙ }
} while (cseq_retry(mark, cseq_count));
As you can see, not a heck of lot of room in that loop
for the cpu pipeline to deal with the extra load and
conditional logic.
It seems like cseq_retry on "every" iteration with its, two?,
#LoadStore | #LoadLoad barriers is a bit much. Now that I have Relacy
working fine on my end, and it has all I need for simulating even
advanced RCU+SMR algos. Well, I will get back into it from time to
time. Actually, can you get it working? Its a neat way to simulate
your sync experiments.
Wait I count an acquire on the item->next.load and then two acquires
from cseq_retry? Am I missing something critical here Joe?
It would appear that way.˙˙ That's what quick and dirty means. :)
If the redundant load/load gets take out then there needs to be
some other mechanism or documentation to ensure the loads are in
order.
On 10/11/26 11:11 AM, Joseph Seigh wrote:
On 9/16/26 6:25 PM, Joseph Seigh wrote:
https://jseigh.wordpress.com/2026/09/07/consequential-concurrent-
sequential-rcu/
The name doesn't really mean anything. I was just generalizing something >>> that's been around forever.*˙ It's simple enough that it must be in use
somewhere.˙ I just haven't noticed it.˙ I looked up the linux kernal
seqlock documentation and it specifically says not to use pointers in
seqlock protected data and no idea why that is unless the linux kernel
is doing something weird with memory mapping.
Like seqlock, it is obstruction-free.˙ If you have qsbr (RCU) or ebr,
that would be a better choice.˙ It might be good for libraries where
the memory reclamation is hidden from the application.
* tangentially related to something that I was working on which if far
more perverse but mostly in a solution that is looking for a problem
phase so it probably won't go anywhere.
I threw together a really quick and dirty test to see how much
overhead all those extra loads would cause.˙ Doesn't appear to
add any.˙ I will have to add it to the proxies project testcase
for more extensive testing but it will take a bit of work since
all the other proxies implement BasicLock and consequential RCU
readers do not.
This is what the code I did a quick and dirty test with looked like
using csequence_t = uint64_t;
inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> & sequence)
{
˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
˙˙˙ return mark != sequence.load(std::memory_order_acquire);
}
csequence_t mark;
retry:
do {
˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
˙˙˙ node_t * item = list.load(std::memory_order_acquire);
˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
˙˙˙˙˙˙˙˙˙˙˙ goto retry;
˙˙˙ }
} while (cseq_retry(mark, cseq_count));
As you can see, not a heck of lot of room in that loop
for the cpu pipeline to deal with the extra load and
conditional logic.
On 10/13/2026 10:01 AM, Joseph Seigh wrote:
On 10/11/26 11:11 AM, Joseph Seigh wrote:
This is what the code I did a quick and dirty test with looked like
using csequence_t = uint64_t;
inline bool cseq_retry(csequence_t mark, std::atomic<csequence_t> &
sequence)
{
˙˙˙˙ std::atomic_thread_fence(std::memory_order_acquire);
˙˙˙˙ return mark != sequence.load(std::memory_order_acquire);
}
csequence_t mark;
retry:
do {
˙˙˙˙ mark = cseq_count.load(std::memory_order_acquire);
˙˙˙˙ node_t * item = list.load(std::memory_order_acquire);
˙˙˙˙ for (int ndx2 = 0; ndx2 < depth; ndx2++) {
˙˙˙˙˙˙˙˙ item = item->next.load(std::memory_order_acquire);
˙˙˙˙˙˙˙˙ if (cseq_retry(mark, cseq_count)) [[unlikely]]
˙˙˙˙˙˙˙˙˙˙˙˙ goto retry;
˙˙˙˙ }
} while (cseq_retry(mark, cseq_count));
Oh shit. This looks like it can be _potential_ "livelock" nation under
some sustained periods of "heavy enough" load? Can we iterate the whole
list before many (n) retrys have occurred? Or how many retrys on average does it take to iterate a decent sized list from head to tail? Writers churning along in the background...
It does not need to dec any prior reference before:
tso.m_pAtmData.store( pDtaRef, memory_order_relaxed );
I must be missing something here. Thanks for your patience.
| Sysop: | Tetrazocine |
|---|---|
| Location: | Melbourne, VIC, Australia |
| Users: | 8 |
| Nodes: | 8 (0 / 8) |
| Uptime: | 190:50:11 |
| Calls: | 221 |
| Files: | 21,513 |
| Messages: | 85,755 |