-
Notifications
You must be signed in to change notification settings - Fork 830
Improve histogram, summary performance under contention by striping observationCount #1794
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
base: main
Are you sure you want to change the base?
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 | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,7 +18,7 @@ | |||||||||||||||
| class Buffer { | ||||||||||||||||
|
|
||||||||||||||||
| private static final long bufferActiveBit = 1L << 63; | ||||||||||||||||
| private final AtomicLong observationCount = new AtomicLong(0); | ||||||||||||||||
| private final AtomicLong[] stripedObservationCounts; | ||||||||||||||||
| private double[] observationBuffer = new double[0]; | ||||||||||||||||
| private int bufferPos = 0; | ||||||||||||||||
| private boolean reset = false; | ||||||||||||||||
|
|
@@ -27,8 +27,18 @@ class Buffer { | |||||||||||||||
| ReentrantLock runLock = new ReentrantLock(); | ||||||||||||||||
| Condition bufferFilled = appendLock.newCondition(); | ||||||||||||||||
|
|
||||||||||||||||
| Buffer() { | ||||||||||||||||
| stripedObservationCounts = new AtomicLong[Runtime.getRuntime().availableProcessors()]; | ||||||||||||||||
| for (int i = 0; i < stripedObservationCounts.length; i++) { | ||||||||||||||||
| stripedObservationCounts[i] = new AtomicLong(0); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| boolean append(double value) { | ||||||||||||||||
| long count = observationCount.incrementAndGet(); | ||||||||||||||||
| AtomicLong observationCountForThread = | ||||||||||||||||
| stripedObservationCounts[ | ||||||||||||||||
| ((int) Thread.currentThread().getId()) % stripedObservationCounts.length]; | ||||||||||||||||
|
Comment on lines
+38
to
+40
|
||||||||||||||||
| AtomicLong observationCountForThread = | |
| stripedObservationCounts[ | |
| ((int) Thread.currentThread().getId()) % stripedObservationCounts.length]; | |
| int stripeIndex = | |
| (Long.hashCode(Thread.currentThread().getId()) & Integer.MAX_VALUE) | |
| % stripedObservationCounts.length; | |
| AtomicLong observationCountForThread = stripedObservationCounts[stripeIndex]; |
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.
Yeah yeah should and was planning to fix before merging
Copilot
AI
Jan 26, 2026
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 variable 'expectedCount' is only assigned values of primitive type and is never 'null', but it is declared with the boxed type 'Long'.
| Long expectedCount = 0L; | |
| long expectedCount = 0L; |
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.
Existing but will fix
Copilot
AI
Jan 26, 2026
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.
Unnecessary and potentially lossy cast to int. The observationCount values are being cast to int before being added to expectedBufferSize (which is a long). This cast truncates the value to 32 bits, which could cause incorrect calculations if any individual stripe's count exceeds Integer.MAX_VALUE. Although unlikely in practice due to striping, the cast should be removed to maintain correctness. The masking operation (~bufferActiveBit) already produces the correct count value as a long.
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.
Existing but will fix
Copilot
AI
Jan 26, 2026
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.
Unnecessary and potentially lossy cast to int. The observationCount values are being cast to int before being added to expectedBufferSize (which is a long). This cast truncates the value to 32 bits, which could cause incorrect calculations if any individual stripe's count exceeds Integer.MAX_VALUE. Although unlikely in practice due to striping, the cast should be removed to maintain correctness.
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.
One thing to consider is whether there is a better algorithm for accessing one of these striped instances. With this algorithm based on thread id, its conceivable that the thread ids could be arranged in such a way that there end up being "hot spots" (i.e. AtomicLong instances that end up receiving a disproportionate amount of the updates). LongAdder solves this by using a random number generate to select which "cell" to access.
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.
An alternative to using the thread id to strip is to use
ThreadLocalRandom.current().nextInt() % stripedObservationCounts.length. Is there any issue with usingThreadLocalRandomhere?