Date: Wed, 08 May 2024 09:39:00 -0700
On Tuesday 7 May 2024 22:19:57 GMT-7 Nate Eldredge via Std-Discussion wrote:
> Along the lines of std::atomic<T>::fetch_add() and friends, I have been
> wondering about the possibility of an interface that would let you
> atomically apply an *arbitrary* function to an atomic object. I'm
> wondering if such an interface has ever been considered for inclusion in
> C++.
Hello Nate
Not that I've seen it, but I did plan on submitting a paper on "atomic_mutate"
that would generalise fetch_xxx with some extra operations and operands. Some
hardware can do them more efficiently than others.
But I haven't even begun writing the draft yet. I wrote the prototype API for
it in https://codereview.qt-project.org/c/qt/qtbase/+/489704 and
https://codereview.qt-project.org/c/qt/qtbase/+/490016
> std::atomic<int> x;
> int f(int);
> x.fetch_update(f);
>
> would atomically perform the equivalent of
>
> x.store(f(x.load());
>
> Currently, one would normally accomplish this with a compare-exchange
> loop:
>
> int tmp = x.load();
> while (!x.compare_exchange_weak(tmp, f(tmp))) { }
>
> and this would be a valid implementation. However, many architectures
> have load-link/store-conditional instructions (e.g. ARM64 with LDRX/STRX)
> and could implement this more efficiently by executing f() in between the
> LL and SC. So in effect, fetch_update would expose the LL/SC
> functionality for use at the level of C++. It could also be implemented
> with transactional memory where available.
That's interesting, but f needs to be extremely simple, otherwise that
machinery can't be used and the store-conditional will always fail. On some
architectures (and my LL/SC is very rusty), you cannot place function calls
between the two, so f must be always inlined.
What's more, it would be difficult for the compiler to determine what you meant
by simply analysing the code, in order to implement things like x86 XADD
(fetch_add). Not impossible, just difficult. But impossible for the Standard
Library to do so. That's why in my implementation I propose that, instead of
passing a lambda mutator, you pass a description of the operation you want
done. You do that either via enums, as in:
x.mutate(QAtomicModify::Xor, 0x1000);
or in the nicer shorthand
x.mutate(_A ^= 0x1000);
> For atomic types that are not lock-free, fetch_update could improve on
> compare-exchange by simply executing f() with the lock held. And even on
> systems where compare-exchange is the only possible implementation, it
> would be simpler and less error-prone for the programmer to write
> x.fetch_update(f) instead of writing the compare-exchange loop every time.
Executing user code with the lock held is usually a big red flag. It goes back
to the same problem of "the lambda must be really really simple" I mentioned
above. I prefer to have the comparison and mutating operations be described
instead of being passed as a lambda.
> Along the lines of std::atomic<T>::fetch_add() and friends, I have been
> wondering about the possibility of an interface that would let you
> atomically apply an *arbitrary* function to an atomic object. I'm
> wondering if such an interface has ever been considered for inclusion in
> C++.
Hello Nate
Not that I've seen it, but I did plan on submitting a paper on "atomic_mutate"
that would generalise fetch_xxx with some extra operations and operands. Some
hardware can do them more efficiently than others.
But I haven't even begun writing the draft yet. I wrote the prototype API for
it in https://codereview.qt-project.org/c/qt/qtbase/+/489704 and
https://codereview.qt-project.org/c/qt/qtbase/+/490016
> std::atomic<int> x;
> int f(int);
> x.fetch_update(f);
>
> would atomically perform the equivalent of
>
> x.store(f(x.load());
>
> Currently, one would normally accomplish this with a compare-exchange
> loop:
>
> int tmp = x.load();
> while (!x.compare_exchange_weak(tmp, f(tmp))) { }
>
> and this would be a valid implementation. However, many architectures
> have load-link/store-conditional instructions (e.g. ARM64 with LDRX/STRX)
> and could implement this more efficiently by executing f() in between the
> LL and SC. So in effect, fetch_update would expose the LL/SC
> functionality for use at the level of C++. It could also be implemented
> with transactional memory where available.
That's interesting, but f needs to be extremely simple, otherwise that
machinery can't be used and the store-conditional will always fail. On some
architectures (and my LL/SC is very rusty), you cannot place function calls
between the two, so f must be always inlined.
What's more, it would be difficult for the compiler to determine what you meant
by simply analysing the code, in order to implement things like x86 XADD
(fetch_add). Not impossible, just difficult. But impossible for the Standard
Library to do so. That's why in my implementation I propose that, instead of
passing a lambda mutator, you pass a description of the operation you want
done. You do that either via enums, as in:
x.mutate(QAtomicModify::Xor, 0x1000);
or in the nicer shorthand
x.mutate(_A ^= 0x1000);
> For atomic types that are not lock-free, fetch_update could improve on
> compare-exchange by simply executing f() with the lock held. And even on
> systems where compare-exchange is the only possible implementation, it
> would be simpler and less error-prone for the programmer to write
> x.fetch_update(f) instead of writing the compare-exchange loop every time.
Executing user code with the lock held is usually a big red flag. It goes back
to the same problem of "the lambda must be really really simple" I mentioned
above. I prefer to have the comparison and mutating operations be described
instead of being passed as a lambda.
-- Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org Principal Engineer - Intel DCAI Cloud Engineering
Received on 2024-05-08 16:39:05