-
Notifications
You must be signed in to change notification settings - Fork 867
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
Use Caffeine for weak maps #2601
Changes from all commits
75a6e6e
bff5058
e4ad46a
d670557
b16b059
11a4d97
70a85cd
ba4fab5
5d6172a
3a86675
48d721d
bc83ca7
c634964
c9d0b47
3a4f8b5
48c6102
0aef3b1
47dd342
c0cce72
85456c7
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 |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.benchmark; | ||
|
||
import com.github.benmanes.caffeine.cache.Cache; | ||
import com.github.benmanes.caffeine.cache.Caffeine; | ||
import io.opentelemetry.context.internal.shaded.WeakConcurrentMap; | ||
import java.util.Map; | ||
import java.util.concurrent.TimeUnit; | ||
import org.openjdk.jmh.annotations.Benchmark; | ||
import org.openjdk.jmh.annotations.Fork; | ||
import org.openjdk.jmh.annotations.Measurement; | ||
import org.openjdk.jmh.annotations.OutputTimeUnit; | ||
import org.openjdk.jmh.annotations.Scope; | ||
import org.openjdk.jmh.annotations.Setup; | ||
import org.openjdk.jmh.annotations.State; | ||
import org.openjdk.jmh.annotations.Threads; | ||
import org.openjdk.jmh.annotations.Warmup; | ||
import org.openjdk.jmh.infra.Blackhole; | ||
|
||
@Fork(3) | ||
@Warmup(iterations = 10, time = 1) | ||
@Measurement(iterations = 5, time = 1) | ||
@OutputTimeUnit(TimeUnit.MICROSECONDS) | ||
@State(Scope.Thread) | ||
public class WeakMapBenchmark { | ||
|
||
private static final WeakConcurrentMap<String, String> weakConcurrentMap = | ||
new WeakConcurrentMap<>(true, true); | ||
|
||
private static final WeakConcurrentMap<String, String> weakConcurrentMapInline = | ||
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. Added inline expunction benchmark too. Found very little difference in performance so settled on it since it means not having to worry about cleaner threads which are hard to manage for library instrumentation (we were using a common task executor before) |
||
new WeakConcurrentMap.WithInlinedExpunction<>(); | ||
|
||
private static final Cache<String, String> caffeineCache = | ||
Caffeine.newBuilder().weakKeys().build(); | ||
private static final Map<String, String> caffeineMap = caffeineCache.asMap(); | ||
|
||
private String key; | ||
|
||
@Setup | ||
public void setUp() { | ||
key = new String(Thread.currentThread().getName()); | ||
} | ||
|
||
@Benchmark | ||
@Threads(1) | ||
public void threads01_weakConcurrentMap(Blackhole blackhole) { | ||
blackhole.consume(weakConcurrentMap.put(key, "foo")); | ||
blackhole.consume(weakConcurrentMap.get(key)); | ||
blackhole.consume(weakConcurrentMap.remove(key)); | ||
} | ||
|
||
@Benchmark | ||
@Threads(5) | ||
public void threads05_weakConcurrentMap(Blackhole blackhole) { | ||
blackhole.consume(weakConcurrentMap.put(key, "foo")); | ||
blackhole.consume(weakConcurrentMap.get(key)); | ||
blackhole.consume(weakConcurrentMap.remove(key)); | ||
} | ||
|
||
@Benchmark | ||
@Threads(10) | ||
public void threads10_weakConcurrentMap(Blackhole blackhole) { | ||
blackhole.consume(weakConcurrentMap.put(key, "foo")); | ||
blackhole.consume(weakConcurrentMap.get(key)); | ||
blackhole.consume(weakConcurrentMap.remove(key)); | ||
} | ||
|
||
@Benchmark | ||
@Threads(1) | ||
public void threads01_weakConcurrentMap_inline(Blackhole blackhole) { | ||
blackhole.consume(weakConcurrentMapInline.put(key, "foo")); | ||
blackhole.consume(weakConcurrentMapInline.get(key)); | ||
blackhole.consume(weakConcurrentMapInline.remove(key)); | ||
} | ||
|
||
@Benchmark | ||
@Threads(5) | ||
public void threads05_weakConcurrentMap_inline(Blackhole blackhole) { | ||
blackhole.consume(weakConcurrentMapInline.put(key, "foo")); | ||
blackhole.consume(weakConcurrentMapInline.get(key)); | ||
blackhole.consume(weakConcurrentMapInline.remove(key)); | ||
} | ||
|
||
@Benchmark | ||
@Threads(10) | ||
public void threads10_weakConcurrentMap_inline(Blackhole blackhole) { | ||
blackhole.consume(weakConcurrentMapInline.put(key, "foo")); | ||
blackhole.consume(weakConcurrentMapInline.get(key)); | ||
blackhole.consume(weakConcurrentMapInline.remove(key)); | ||
} | ||
|
||
@Benchmark | ||
@Threads(1) | ||
public void threads01_caffeine(Blackhole blackhole) { | ||
blackhole.consume(caffeineMap.put(key, "foo")); | ||
blackhole.consume(caffeineMap.get(key)); | ||
blackhole.consume(caffeineMap.remove(key)); | ||
} | ||
|
||
@Benchmark | ||
@Threads(5) | ||
public void threads05_caffeine(Blackhole blackhole) { | ||
blackhole.consume(caffeineMap.put(key, "foo")); | ||
blackhole.consume(caffeineMap.get(key)); | ||
blackhole.consume(caffeineMap.remove(key)); | ||
} | ||
|
||
@Benchmark | ||
@Threads(10) | ||
public void threads10_caffeine(Blackhole blackhole) { | ||
blackhole.consume(caffeineMap.put(key, "foo")); | ||
blackhole.consume(caffeineMap.get(key)); | ||
blackhole.consume(caffeineMap.remove(key)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.api.caching; | ||
|
||
import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap; | ||
import java.util.function.Function; | ||
|
||
final class WeakLockFreeCache<K, V> implements Cache<K, V> { | ||
|
||
private final WeakConcurrentMap<K, V> delegate; | ||
|
||
WeakLockFreeCache() { | ||
this.delegate = new WeakConcurrentMap.WithInlinedExpunction<>(); | ||
} | ||
|
||
@Override | ||
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { | ||
V value = get(key); | ||
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. Implementation mostly same as |
||
if (value != null) { | ||
return value; | ||
} | ||
// Best we can do, we don't expect high contention with this implementation. Note, this | ||
// prevents executing mappingFunction twice but it does not prevent executing mappingFunction | ||
// if there is a concurrent put operation as would be the case for ConcurrentHashMap. However, | ||
// we would never expect an order guarantee in this case anyways so it still has the same | ||
// safety. | ||
synchronized (delegate) { | ||
value = get(key); | ||
if (value != null) { | ||
return value; | ||
} | ||
value = mappingFunction.apply(key); | ||
V previous = delegate.putIfAbsent(key, value); | ||
if (previous != null) { | ||
return previous; | ||
} | ||
return value; | ||
} | ||
} | ||
|
||
@Override | ||
public V get(K key) { | ||
return delegate.getIfPresent(key); | ||
} | ||
|
||
@Override | ||
public void put(K key, V value) { | ||
delegate.put(key, value); | ||
} | ||
|
||
@Override | ||
public void remove(K key) { | ||
delegate.remove(key); | ||
} | ||
|
||
// Visible for testing | ||
int size() { | ||
return delegate.approximateSize(); | ||
} | ||
} |
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 removed these since they override annotations. Benchmarks often need specific settings so copy-pasta of annotations is better than global control
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.
👍