Common Atomics and Atomic Arrays
This issue is for discussion of the proposal to introduce Common Atomic and Atomic Array types in the standard library. The full text of the proposal is here: proposals/common-atomics.md.
The KEEP presents the proposed API for Common Atomics and Atomic Arrays and focuses on the implementation options for the JVM backend.
PR: #397
I want to expand a little more on "why not value class". You mention that the objects will have no identity, but I don't see the issue with that. Couldn't we just have the value class's equals method check equality of underlying Java atomic reference? I think that equality for atomics only makes sense as reference equality. In other words, I'd be very surprised if AtomicInt(0) == AtomicInt(0)
I'm also unclear as to what this means:
Loosely defined different value semantics on different platforms
and for the point:
Value classes are not interoperable with Java, e.g., signatures of functions with inline classes get mangled
There's 2 solutions. One can use @JvmName to stop the mangling (which makes sense in an interop situation. #393 might be a potential solution as well.
I think the atomics should also come with an assign method by default that works with the assign plugin
Streamlining the kotlinx-atomicfu machinery to Kotlin is an explicit next step of this proposal and a subject of the next KEEP after atomics stabilization.
Does this mean that atomicfu would become part of the Kotlin compiler and stdlib (with one of the solutions mentioned) rather than an external plugin?
In a similar vein, are there longer term plans for multiplatform Kotlin concurrent collections (i.e. ConcurrentHashMap, maybe even ConcurrentSkipListMap like classes)?
@kyay10 thank you for your comment!
On implementing JVM atomics as value classes:
-
Referential equality operator (
===) is not defined for value classes by design, comparingAtomicIntInline(0) === AtomicIntInline(0)would lead to a compilation error. While for other platforms (Native/Js/Wasm) atomics can be compared by reference. -
On function mangling:
@JvmInline
value class AtomicIntInline(val a: AtomicInteger)
fun foo(x: AtomicIntInline) {...} // calling this function from Java results in an resolution error, since it's name is mangled by the compiler
Marking foo with a @JvmName annotation would allow calling foo from Java, though a user would have to add this annotation manually, which does not look like a good solution.
@JvmExposeBoxed described in #393 could become a solution for this issue, it would expose a boxed version of foo:
public foo(other: AtomicIntInline)
My point about referential equality is that it's unnecessary since normal equality will suffice (it'll compare the underlying references in case of JVM). In other words, I'd expect that AtomicIntInline(0) == AtomicIntInline(0) is false, and hence that I can use regular equality on all platforms (so that Native and JS aren't "losing out" on reference comparisons, they just have to use == in place of ===)
WRT function mangling, maybe we want a variant of #393 that exposes the underlying type instead of a boxed type, so that one can basically say "my value class is morally the same as its underlying type, I'm just trying to have a different API than its underlying type, but Java users don't need to know about any of that". Exposing the boxed types is fine IMO too, Java users will just have to deal with converting it.
@rnett yes, the plan is to turn kotlinx-atomicfu into a compiler plugin, which would provide optimization by inlining stdlib atomics marked with some special @InlineAtomic annotation or e.g. created using a factory function atomic(value: Int) (the final solution has not yet been designed).
At that stage the library will be removed.
Some more thoughts on implementing atomics as value classes on JVM:
- On JVM, a value class wrapping a Java atomic would introduce an extra layer of boxing when e.g. a list of atomics is created.
- If we implement atomics as value classes on JVM, atomics should be value classes on all other backends as well (currently, expected and actual declarations do not allow this distinction, and changing this would require some hacks in the compiler). And for K/N value classes would not work, because an atomic could only be passed to a function as a copy of the wrapped value, not the reference, which would allow to update the value atomically.
Re. JS, the KEEP says "implementation will be single threaded", which is fine, but will the atomic usages be inlined?
For example, wrapping a number into an AtomicInt class would mean an unnecessary level of indirection.
Introduce a special annotation (e.g. @InlineAtomic) to mark the atomics, which a user wants to inline. Then the atomicfu compiler plugin checks constraints for those atomics and inlines them.
And from https://github.com/Kotlin/kotlinx-atomicfu/issues/493
Users will have control over which atomics to inline by applying a dedicated annotation, such as @InlineAtomic
Wouldn't it be better from a DX standpoint to always try to inline, but allow a consumer to opt-out in case the compiler reports a constraint violation? E.g. with a @NoInline annotation.
Wouldn't it be better from a DX standpoint to always try to inline, but allow a consumer to opt-out in case the compiler reports a constraint violation? E.g. with a
@NoInlineannotation.
@lppedd that's actually a good point for discussion, thank you!
A little bit later I'll create an issue in kotlinx-atomicfu repo with the description of this new optimization compiler plugin in more details: the scope of features, support of JS, and with the options of the user interface (@InlineAtomic or @NoInline annotation) etc.
Re. JS, the KEEP says "implementation will be single threaded", which is fine, but will the atomic usages be inlined?
Yes, inlining of kotlin.concurrent atomics for JS will be considered in the new light atomicfu compiler plugin.
The documentation (and implementation) for compareAndSet and compareAndExchange of AtomicReference and compareAndSetAt and compareAndExchangeAt of AtomicArray seems to be inconsistent across platforms.
The documentation for Common and Native says this:
Comparison of values is done by reference.
But the documentation for JVM, JS and Wasm says this:
Comparison of values is done by value.
For JVM this seems to be wrong, the API specification mentions == (the reference equality operator in Java).
For JS and Wasm, these methods are indeed implemented using ==/!= (the value equality operators in Kotlin, which calls equals). Is this intended?
Small nitpick about the naming scheme (if it is not too late).
It seems that the names of AtomicReference<T> and AtomicArray<T> are inconsistent.
In Java, AtomicReference<T> and AtomicReferenceArray<T> follow a consistent pattern, as do other AtomicX and AtomicXArray pairs.
By analogy, Kotlin then should use either:
AtomicReference<T>andAtomicReferenceArray<T>, orAtomic<T>andAtomicArray<T>.
While using the same naming scheme for scalar and array reference types looks nice, there are a few reasons to keep the current scheme (AtomicReference and AtomicArray) untouched:
- renaming
AtomicReference<T>to justAtomic<T>may result in misunderstanding of semantics for unexperienced users, who might expect that all operations on something wrapped intoAtomicare now atomic as well. You can see that misunderstanding among, for example, some C++ developers, who expect that placing an object intostd::atomicwill make it atomic itself.AtomicReferenceexplicitly states what is atomic: the reference to an object, not the object itself, so the semantic is clear. - with the
AtomicArray, the existing name reflects the semantic: it's an array that could be updated atomically. And the name is also consistent with what you can load from that array:AtomicIntArray->Int;AtomicLongArray->Long,AtomicArray<T>->T(and notReference<T>orAtomicReference<T>).
So while aligning AtomicArray and AtomicReference names could improve aesthetic properties, we would rather keep the names asymmetric to reflect semantics better.
Opened PR that adds information about update-functions that will be introduced in Kotlin 2.2.20: https://github.com/Kotlin/KEEP/pull/432
Just wondering what's the status of the transition to the stdlib and Kotlin compiler from AtomicFU.
There are multiple places where I could have asked, so pardonne moi if it's not the ideal one.
@lppedd, the transition is planned, but nobody is working on it at the moment. Do you expect a significant performance drop after switching to common atomic types (w/o compiler support) in your projects?
@fzhinkin thanks!
Do you expect a significant performance drop after switching to common atomic types (w/o compiler support) in your projects?
I don't think so. But my question was more geared towards understanding what to expect in the coming months, so I can update our timeline for devops/refactoring activities. Obviously the less dependencies the better, but if the experience isn't as good as it is with AtomicFU, it doesn't make sense getting rid of it. I will just postone as needed.
@lppedd
but if the experience isn't as good as it is with AtomicFU
I think, it is difficult to beat AtomicFU in providing a "good" experience. ;)
Atomic types provided by the stdlib are not "inlined" the same way AFU atomics are inlined by the plugin (well, they are just boxed types), but otherwise they are functionally equivalent. Stdlib types are still experimental, but they are less experimental than the AtomicFU library in general, in some sense.