-
Notifications
You must be signed in to change notification settings - Fork 792
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
[sdk-metrics] Refactor and improve interlocking code for doubles in MetricPoint #5378
Merged
CodeBlanch
merged 3 commits into
open-telemetry:main
from
CodeBlanch:sdk-metrics-double-interlocking-refactor
Feb 22, 2024
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
using System.Runtime.CompilerServices; | ||
|
||
namespace OpenTelemetry.Internal; | ||
|
||
internal static class InterlockedHelper | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static void Add(ref double location, double value) | ||
{ | ||
// Note: Not calling InterlockedHelper.Read here on purpose because it | ||
// is too expensive for fast/happy-path. If the first attempt fails | ||
// we'll end up in an Interlocked.CompareExchange loop anyway. | ||
double currentValue = Volatile.Read(ref location); | ||
|
||
var returnedValue = Interlocked.CompareExchange(ref location, currentValue + value, currentValue); | ||
if (returnedValue != currentValue) | ||
{ | ||
AddRare(ref location, value, returnedValue); | ||
} | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static double Read(ref double location) | ||
=> Interlocked.CompareExchange(ref location, double.NaN, double.NaN); | ||
|
||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
private static void AddRare(ref double location, double value, double currentValue) | ||
{ | ||
var sw = default(SpinWait); | ||
while (true) | ||
{ | ||
sw.SpinOnce(); | ||
|
||
var returnedValue = Interlocked.CompareExchange(ref location, currentValue + value, currentValue); | ||
if (returnedValue == currentValue) | ||
{ | ||
break; | ||
} | ||
|
||
currentValue = returnedValue; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Why do you need this to be a CompareExchange rather than just Volatile.Read? You need a full fence for some reason?
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 code before was...
Now it is calling this helper...
What that code needs is no stale reads. Does
Interlocked.CompareExchange
(full fence) give us 100% guaranteed up-to-date values? My understanding withVolatile.Read
is it could give us back something stale.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.
Define stale. Volatile.Read will ensure it's not just using some enregistered value. But beyond that, I don't see how CompareExchange(ref location, sentinel, sentinel) would provide strong guarantees about "up-to-date". The exact moment after you read the value but before you do anything with it it could be updated concurrently, at which point the value isn't "up to date".
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.
How OpenTelemetry SDK is working is many threads are recording values. For this double case it is either of these:
There is a dedicated thread which does a collection operation periodically. Its job is to grab a snapshot of the current/running state. That is this block:
The delta case needs to compute the change since the last collect:
It's OK if the value changes right before or right after but we need to make sure whatever we got was true and not some cached or local thing. Does that help?
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 still don't understand. With a CompareExchange (that's not also updating anything atomically, just reading), you might read the value, then the thread might be context switched out, and 10 minutes later it might start running again and you continue with the 10 minute old value. What's the difference between that and "some cached" thing?
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.
@stephentoub
Here is our version for
long
measurements:Also uses
Interlocked.Read
, do you think it would be better to useVolatile.Read
there too?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.
Can you share the data you have that demonstrates using CompareExchange is actually improving this?
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.
Interlocked.Read(ref value)
is justCompareExchange(ref value, 0, 0)
, so it's effectively the same thing you're doing with the doubles. Personally, I'd just useVolatile.Read
. I'm really interested in seeing the data that suggests there's any meaningful benefit to using CompareExchange instead in these scenarios; otherwise, I'd stick with the idiomatic answer that's also cheaper.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.
We haven't tested this, and we most likely would not anytime soon. It would require elaborate testing setups with different kinds of hardware. That might not be the best use of our time. Ideally, we would like to rely on the official docs for this kind of information instead of running experiments ourselves and gathering this kind of data.
In this particular case, our understanding/assumption is in line with what you mentioned in one of your previous comments:
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 guess is you'd find it makes essentially zero difference, other than slowing down the writing code.