-
-
Notifications
You must be signed in to change notification settings - Fork 440
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
Faster ID generation #3818
Faster ID generation #3818
Conversation
# Conflicts: # CHANGELOG.md
# Conflicts: # CHANGELOG.md
Co-authored-by: Stefano <stefano.siano@sentry.io>
Co-authored-by: Stefano <stefano.siano@sentry.io>
Co-authored-by: Stefano <stefano.siano@sentry.io>
Co-authored-by: Stefano <stefano.siano@sentry.io>
Co-authored-by: Stefano <stefano.siano@sentry.io>
…e difference of .equals if objects are not the same
…va into feat/lazy-span-id-v8
|
|
||
/** | ||
* Utility class for faster id generation for SentryId, SpanId, and unique filenames uses throughout | ||
* the SDK It uses our vendored Random class instead of SecureRandom for improved performance It |
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.
But it doesn't 😅
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.
not yet :)
Sorry, wanted to add a comment here in the PR, that we need to first merge the main
branch to 8.x.x and then to this one
package io.sentry; | ||
|
||
import java.util.Arrays; | ||
import java.util.Random; |
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.
import java.util.Random; | |
import io.sentry.util.Random; |
* @throws IllegalArgumentException if the given character sequence does not conform to the string | ||
* representation as described in {@link UUID#toString()} | ||
*/ | ||
public static UUID parseUUID(final CharSequence uuidSequence) { |
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.
Not sure if we even need 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.
I presume no, as long as we use String values in the SDK.
randomBytes[6] &= 0x0f; /* clear version */ | ||
randomBytes[6] |= 0x40; /* set to version 4 */ | ||
randomBytes[8] &= 0x3f; /* clear variant */ | ||
randomBytes[8] |= (byte) 0x80; /* set to IETF variant */ |
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.
This method is taken from java.uitl.UUID
.
Do we need this part? Do we want/need to add those flags to our random ids?
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.
Trying to clarify internally. Will update here when I get an answer.
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 should keep it in. Also see https://develop.sentry.dev/sdk/data-model/event-payloads/#required-attributes
There's some validations for IDs, e.g.:
- https://github.com/getsentry/relay/blob/c0756f24845bbf5b2bcefa537cefc295abc46d73/relay-event-schema/src/protocol/contexts/trace.rs#L66-L67
- https://github.com/getsentry/relay/blob/c0756f24845bbf5b2bcefa537cefc295abc46d73/relay-event-schema/src/protocol/contexts/trace.rs#L17-L18
Let's verify those are compatible.
It's expecting a 16/32 char hexstring that isn't all zeroes. To avoid this even in case random generates an all 0
ID, we should keep the version / variant bits set.
* @param uuid the UUID to represent as a string | ||
* @return a string representation of the given UUID | ||
*/ | ||
public static String toString(final UUID uuid) { |
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.
This returns an acutal UUID
String including -
. Do we need this for our purposes?
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 should be able to replace all UUID usages that have -
with simply using the String value without -
as it should mostly be for our own IDs and some unique paths.
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.
Great, will remove this method then.
In addition to the proposal in #3809, which focused on the generation of a 32 char Therefore there is even more room for optimization here, as we do not actually need to generate a full UUID, e.g. 16 random bytes, but only 8 random bytes as only half of the resulting String is being used. Also, when generating only the needed 8 bytes the I did some benchmarks on this with the same parameters as the benchmarks in #3809 comparing the original way of generating a SpanId with the changes of this PR: Java 17
Java 8
|
* @param uuid the UUID to represent as a string | ||
* @return a string representation of the given UUID | ||
*/ | ||
public static String toString(final UUID uuid) { |
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 should be able to replace all UUID usages that have -
with simply using the String value without -
as it should mostly be for our own IDs and some unique paths.
randomBytes[6] &= 0x0f; /* clear version */ | ||
randomBytes[6] |= 0x40; /* set to version 4 */ | ||
randomBytes[8] &= 0x3f; /* clear variant */ | ||
randomBytes[8] |= (byte) 0x80; /* set to IETF variant */ |
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.
Trying to clarify internally. Will update here when I get an answer.
randomBytes[6] &= 0x0f; /* clear version */ | ||
randomBytes[6] |= 0x40; /* set to version 4 */ | ||
randomBytes[8] &= 0x3f; /* clear variant */ | ||
randomBytes[8] |= (byte) 0x80; /* set to IETF variant */ |
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 should keep it in. Also see https://develop.sentry.dev/sdk/data-model/event-payloads/#required-attributes
There's some validations for IDs, e.g.:
- https://github.com/getsentry/relay/blob/c0756f24845bbf5b2bcefa537cefc295abc46d73/relay-event-schema/src/protocol/contexts/trace.rs#L66-L67
- https://github.com/getsentry/relay/blob/c0756f24845bbf5b2bcefa537cefc295abc46d73/relay-event-schema/src/protocol/contexts/trace.rs#L17-L18
Let's verify those are compatible.
It's expecting a 16/32 char hexstring that isn't all zeroes. To avoid this even in case random generates an all 0
ID, we should keep the version / variant bits set.
} | ||
|
||
private static class Holder { | ||
static final Random numberGenerator = new Random(); |
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.
According to https://docs.oracle.com/javase/7/docs/api/java/util/Random.html there might be poor performance when using Random
from multiple threads.
Instances of java.util.Random are threadsafe. However, the concurrent use of the same java.util.Random instance across threads may encounter contention and consequent poor performance. Consider instead using ThreadLocalRandom in multithreaded designs.
Should we vendor ThreadLocalRandom
as well and use that?
Did you also do (performance) testing in a multi threaded environment?
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.
Hmm, good point. No, didn't do multithreaded perf testing. could try to spin up something to compare Random
to ThreadLocalRandom
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.
Yes, this does make quite a difference. Below is a comparison for UUID generation:
Benchmark | Score | Error | Units | Threads | Comparison | |
---|---|---|---|---|---|---|
uuidGenerationRandom | 23789834,814 | ± | 117697,18 | ops/s | 1 | 100% |
uuidGenerationRandomMultiThreaded | 2280766,845 | ± | 263522,36 | ops/s | 10 | 10% |
uuidGenerationThreadLocalRandomMultiThreaded | 185639075,638 | ± | 5090316,9 | ops/s | 10 | 780% |
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.
whew, what's the baseline we're comparing to here? are all of those using the FastUUID code just with a different Random
implementation?
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 from FastUUID
is only responsible for converting the UUID
to string. This was not done in this test.
I tested the SentryUUID.randomUUID()
function, which is a clone of the java.util.UUID.randomUUID()
function with java.util.Random
as the random generator.
So, Results 1 and 2 test the above mentioned function in single-threaded vs multi-threaded scenarios.
Result 3 tests the same function with java.util.Random
replaced with java.util.concurrent.ThreadLocalRandom
. I replaced Random random = SentryUUID.Holder.numberGenerator;
with Random random = ThreadLocalRandom.current();
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.
Also, the benchmark code can now be found here: https://github.com/lbloder/sentry-uuid-bench
* @throws IllegalArgumentException if the given character sequence does not conform to the string | ||
* representation as described in {@link UUID#toString()} | ||
*/ | ||
public static UUID parseUUID(final CharSequence uuidSequence) { |
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 presume no, as long as we use String values in the SDK.
Performance metrics 🚀
|
Revision | Plain | With Sentry | Diff |
---|---|---|---|
d909f73 | 455.98 ms | 472.71 ms | 16.74 ms |
6449d66 | 435.18 ms | 477.48 ms | 42.30 ms |
c46ade5 | 406.26 ms | 446.62 ms | 40.36 ms |
470431c | 431.02 ms | 469.31 ms | 38.29 ms |
70c2f67 | 426.29 ms | 460.26 ms | 33.97 ms |
App size
Revision | Plain | With Sentry | Diff |
---|---|---|---|
d909f73 | 1.65 MiB | 2.30 MiB | 672.27 KiB |
6449d66 | 1.70 MiB | 2.36 MiB | 671.75 KiB |
c46ade5 | 1.65 MiB | 2.30 MiB | 672.27 KiB |
470431c | 1.70 MiB | 2.36 MiB | 671.77 KiB |
70c2f67 | 1.70 MiB | 2.36 MiB | 671.63 KiB |
Did some additional Testing for MultiThreading based on this comment from @adinauer. The results below show Test runs for:
Results
We can see, that Secure RandomOut of curiosity, I also did a benchmark run to compare
We can see, that SecureRandom performance is also impacted when running in multiple threads, however, not nearly as much as Random. |
# Conflicts: # CHANGELOG.md # sentry/src/main/java/io/sentry/SpanId.java # sentry/src/main/java/io/sentry/protocol/SentryId.java # sentry/src/test/java/io/sentry/protocol/SentryIdTest.kt
…ce when invoking with string
…-java into feat/fast-id-generation
…to SentryId constructor
📜 Description
As proposed in #3809, this PR implements a faster way to generate IDs used in Sentry.
In addition to the proposal two methods were added to directly generate an ID String without the need to call two methods:
SentryUUID.generateSentryId()
SentryUUID.generateSpanId()
SentryUUID.generateSpanId()
only generates the first part of a UUID as before we would create a full uuid and then only use the first 16 out of 32 characters. Therefore we only need to create 8 random bytes instead of 16.💡 Motivation and Context
Implements #3809
💚 How did you test it?
📝 Checklist
sendDefaultPII
is enabled.🔮 Next steps