-
Notifications
You must be signed in to change notification settings - Fork 3.6k
optimize the read lock caused by computeIfAbsent and synchronized in high concurrency #9980
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -69,7 +69,15 @@ public class TypeOperators | |
| public TypeOperators() | ||
| { | ||
| ConcurrentHashMap<Object, Object> cache = new ConcurrentHashMap<>(); | ||
| this.cache = (operatorConvention, supplier) -> cache.computeIfAbsent(operatorConvention, key -> supplier.get()); | ||
| // optimize the read lock caused by computeIfAbsent | ||
| // this.cache = (operatorConvention, supplier) -> cache.computeIfAbsent(operatorConvention, key -> supplier.get()); | ||
| this.cache = (operatorConvention, supplier) -> { | ||
| Object value = cache.get(operatorConvention); | ||
| if (value == null) { | ||
| value = cache.computeIfAbsent(operatorConvention, key -> supplier.get()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. according to doc: looking at Why do you think this change matters in non-benchmark executions
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. During large-scale data testing, we found that this code has performance problems. When modifying, we also refer to the transformation idea of computeifabsent method of mybatis. The following is the modification of mybatis:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
How did you do the testing? |
||
| } | ||
| return value; | ||
| }; | ||
| } | ||
|
|
||
| public TypeOperators(BiFunction<Object, Supplier<Object>, Object> cache) | ||
|
|
@@ -181,10 +189,14 @@ public OperatorAdaptor(ScalarFunctionAdapter functionAdapter, OperatorConvention | |
| this.operatorConvention = operatorConvention; | ||
| } | ||
|
|
||
| public synchronized MethodHandle get() | ||
| // optimize the read lock caused by synchronized | ||
| // public synchronized MethodHandle get() | ||
| public MethodHandle get() | ||
| { | ||
| if (adapted == null) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hello, this is to avoid locking when reading data. During the TB level data test, it is found that this code has a blocked lock. If modified, it can provide certain performance when reading data
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep. But it still needs to be
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed. without |
||
| adapted = adaptOperator(operatorConvention); | ||
| synchronized (this) { | ||
| adapted = adaptOperator(operatorConvention); | ||
| } | ||
| } | ||
| return adapted; | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised this makes a difference.
Edit: In a microbenchmark I find this indeed to be about 1/3 of the cost when the key is present. In this case we never remove any entries, so it is safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
During large-scale data testing, we found that this code has performance problems. When modifying, we also refer to the transformation idea of computeifabsent method of mybatis. The following is the modification of mybatis:
mybatis/mybatis-3#2223
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The linked JDK bug was fixed in JDK 9 (see https://bugs.openjdk.java.net/browse/JDK-8161372) Trino requires JDK 11 at-least. Is there some way to reproduce your test to observe the locking?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My microbenchmark was with JDK 11.
Edit: There is still some extra locking needed to enforce the exactly-once execution of the compute part when the key is concurrently removed.
Hence when you know keys are never removed get() followed by computeIfAbsent might be faster.