-
Notifications
You must be signed in to change notification settings - Fork 11
perf: optimize EvaluatorBucketing #88
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?
perf: optimize EvaluatorBucketing #88
Conversation
|
I have conducted some JMH microbenchmarking and found that 59247ee is not worthwhile. I believe the character by character conversion is not efficient relative, hence I have reverted that. There is a still an memory and throughput lift in my original commit (newVersionExtra vs oldVersion). @Benchmark // 59247ee
public void newVersion(Blackhole blackhole) {
StringBuilder keyBuilder = new StringBuilder();
if (seed != null) {
keyBuilder.append(seed.intValue());
} else {
keyBuilder.append(flagOrSegmentKey).append('.').append(salt);
}
keyBuilder.append('.');
keyBuilder.append(USER_ID);
// turn the first 15 hex digits of this into a long
MessageDigest digest = DigestUtils.getSha1Digest();
digest.update(StandardCharsets.UTF_8.encode(CharBuffer.wrap(keyBuilder)));
byte[] hash = digest.digest();
long longVal = 0;
for (int i = 0; i < 7; i++) {
longVal <<= 8;
longVal |= (hash[i] & 0xff);
}
longVal <<= 4;
longVal |= ((hash[7] >> 4) & 0xf);
blackhole.consume(longVal);
}
@Benchmark // acd352d
public void newVersionExtra(Blackhole blackhole) {
StringBuilder keyBuilder = new StringBuilder();
if (seed != null) {
keyBuilder.append(seed.intValue());
} else {
keyBuilder.append(flagOrSegmentKey).append('.').append(salt);
}
keyBuilder.append('.');
keyBuilder.append(USER_ID);
// turn the first 15 hex digits of this into a long
byte[] hash = DigestUtils.sha1(keyBuilder.toString());
long longVal = 0;
for (int i = 0; i < 7; i++) {
longVal <<= 8;
longVal |= (hash[i] & 0xff);
}
longVal <<= 4;
longVal |= ((hash[7] >> 4) & 0xf);
blackhole.consume(longVal);
}
@Benchmark
public void oldVersion(Blackhole blackhole) {
String idHash = USER_ID;
String prefix;
if (seed != null) {
prefix = seed.toString();
} else {
prefix = flagOrSegmentKey + "." + salt;
}
String hash = DigestUtils.sha1Hex(prefix + "." + idHash).substring(0, 15);
long longVal = Long.parseLong(hash, 16);
blackhole.consume(longVal);
} |
|
Thank you for the contribution @yuzawa-san , I will try to take a look in the next few days. |
|
hello, could you please take a look at this again? |
Requirements
Related issues
n/a
Describe the solution you've provided
I have eliminated several intermediate allocations in this hot piece of code in my codebase. I used a single StringBuilder to accumulate the key to be hashed. The intermediate concatenations to make the key each had allocations. Additionally, I used StringBuilder.append(int) when possible to avoid the intermediate string conversion from int. I removed the hex and substring steps, both of which allocated strings and backing bytes. Instead, I did the equivalent calculation use bit operations on the underlying hash output bytes. I added a test to ensure the old logic and new logic are aligned.
Describe alternatives you've considered
Ideally, the StringBuilder would be able to go right from the appended Strings to utf8 bytes into the sha1, but sadly the StringBuilder creates an intermediate string that then gets converted into bytes for the digest. I do not believe I can make that better, but will sleep on it.nevermind, I found https://stackoverflow.com/questions/19472011/java-stringbuffer-to-byte-without-tostringnevermind to that, it would appear the copy is cheaper than the character conversion and wrapping
Additional context
This was the hotspot in our allocations flamegraph which I wish to improve.