Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port Striped64, LongAdder from JSR-166 #3342

Merged
merged 21 commits into from Jul 3, 2023

Conversation

mox692
Copy link
Contributor

@mox692 mox692 commented Jun 19, 2023

This PR Ports Striped64 and LongAdder, which is needed for ConcurrentSkipListMap.

@mox692 mox692 marked this pull request as draft June 19, 2023 13:19
@mox692 mox692 changed the title Port Striped64 from JSR-166 Port Striped64, LongAdder from JSR-166 Jun 27, 2023
@mox692 mox692 marked this pull request as ready for review June 27, 2023 12:42
Comment on lines 24 to 25
final private[atomic] class Cell private[atomic] (var value: Long) {
@volatile private[concurrent] var _value = value
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need value and _value? If they need to be separate, we should remove var from value, otherwise we have two fields instead of one field and a ctor argument..

Also, I think the original Java version has some padding here? I'm not sure if that trick works in Scala Native, but if it does, we should port it as well. If it doesn't, we should figure out a way to support that kind of memory layout, because it's important for concurrent code.

For a discussion about padding and how it prevents "false-sharing" on the JVM, see typelevel/cats-effect#2543 (comment).

That would create false sharing, i.e. two independent fields residing on the same cache line while different threads independently write to each variable. Each write to either field in this case invalidates the cache of the other thread, slowing everyone down.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, just realized I was looking at an old version :)

However, I see the latest code uses @jdk.internal.vm.annotation.Contended. This is an important annotation that relates to the false-sharing issue. @WojciechMazur I wonder if we can port its semantics to Scala Native?

At the VM’s discretion, those fields annotated as contended are given extra padding so they do not share cache lines with other fields that are likely to be independently accessed. This helps to avoid cache contention across multiple threads.

https://docs.azul.com/prime/Contended

Copy link
Contributor Author

@mox692 mox692 Jun 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I missed that point, thank you! (And the discussion you sited from the cats-effect is very intriguing to me)

Yes, it would be nice if we have a scala native own implementation of this annotation that adds padding to the fields of a class.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@armanbilge I've stumbled on the Contended annotation before, but I've not yet made any steps to support them. On most of the target architectures (x64, arm64, x86) the cache line size is equal to 64 bytes, so currently we might use a fixed padding. That's the most important is how to preserve this information between frontend (scalac) and backed (scala-native-codegen). Probably we should introuce a new NIR attribute which would contain this information, allowing eventually for platform-specific padding.
We definitly would need to support both single contended fields, as well as contended field groups, as the JVM does to make it memory-space efficient.
Until we'll implement Contended annotation, it would be worthy to place commented-out annotation, to allow for smooth adjustment later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +31 to +36
final private[atomic] def cas(cmp: Long, `val`: Long) =
valueAtomic().compareExchangeWeak(
cmp,
`val`,
memory_order.memory_order_release
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dumb question: in JSR166 I see the following comment, is that what you've done here with memory_order_release?

     * JVM intrinsics note: It would be possible to use a release-only
     * form of CAS here, if it were provided.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry I am looking at an old version. This matches the latest code :) woops, I see even the newest code includes that comment. But if I understand correctly, that's exactly what you've done here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I refered to VarHandle.weakCompareAndSetRelease, and implemented it for scala-native.

Copy link
Contributor

@WojciechMazur WojciechMazur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this contribution, I've added a few comments.

* additional information regarding copyright ownership.
*/
// Ported from JSR 166 revision 1.23

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also include the original header from the JSR166 sources, mentioning Doug Lea and the JSR-166 team?

Comment on lines 24 to 26
final private[atomic] class Cell private[atomic] (var value: Long) {
@volatile private[concurrent] var _value = value

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it was previously mentioned we should keep only 1 of these fields, probably to make it the most JSR-166 compliant it should be:

Suggested change
final private[atomic] class Cell private[atomic] (var value: Long) {
@volatile private[concurrent] var _value = value
final private[atomic] class Cell private[atomic] (@volatile private[atomic] var value: Long) {

Comment on lines 89 to 96
@alwaysinline private def threadProbeAtomic() = new CAtomicInt(
fromRawPtr(
Intrinsics.classFieldRawPtr(
Thread.currentThread(),
"threadLocalRandomProbe"
)
)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was can be moved to companion object. It does not reference this. Thank to that we would be also able to remove unnecessary arguments of Stripped64 passed to {get,advance}Proble

Comment on lines 117 to 118
var _index = index
var _wasUncontended = wasUncontended
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically I would recommend to use underscored variant for the parameter types. That way you can lower the risk of referring the original value, instead of current var value. When working with javalib we typically would not use named function arguments anyway

final def add(x: Long): Unit =
value = value + x
@SerialVersionUID(7249069246863182397L)
private class SerializationProxy(val a: LongAdder) extends Serializable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should a: LongAdder be only constructor argument and not a field? Otherwise it looks like a cyclic dependency for me, becouse we would again want to serialize field of LongAdder type in it's serializer.

Suggested change
private class SerializationProxy(val a: LongAdder) extends Serializable {
private class SerializationProxy(a: LongAdder) extends Serializable {

Also you can point why it's needed (I guess that's it's done to hide the implementation detail of Stripped64 transient fields)

@WojciechMazur WojciechMazur merged commit 465875c into scala-native:main Jul 3, 2023
77 checks passed
@mox692 mox692 deleted the port/jsr166_Striped64 branch July 3, 2023 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants