Skip to content

Commit 635b3ec

Browse files
committed
Use optimistic lock in DDSpanContext tag handling
1 parent e16815c commit 635b3ec

2 files changed

Lines changed: 329 additions & 36 deletions

File tree

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package datadog.trace.core;
2+
3+
import static java.util.concurrent.TimeUnit.MICROSECONDS;
4+
5+
import datadog.trace.api.TagMap;
6+
import java.util.concurrent.locks.StampedLock;
7+
import org.openjdk.jmh.annotations.Benchmark;
8+
import org.openjdk.jmh.annotations.BenchmarkMode;
9+
import org.openjdk.jmh.annotations.Fork;
10+
import org.openjdk.jmh.annotations.Measurement;
11+
import org.openjdk.jmh.annotations.Mode;
12+
import org.openjdk.jmh.annotations.OutputTimeUnit;
13+
import org.openjdk.jmh.annotations.Threads;
14+
import org.openjdk.jmh.annotations.Warmup;
15+
import org.openjdk.jmh.infra.Blackhole;
16+
17+
/*
18+
Benchmark Mode Cnt Score Error Units
19+
DDSpanContextTagsBenchmark.getTag_stampedlock_1t thrpt 2 484.867 ops/us
20+
DDSpanContextTagsBenchmark.getTag_stampedlock_8t thrpt 2 3615.378 ops/us
21+
DDSpanContextTagsBenchmark.getTag_synchronized_1t thrpt 2 260.032 ops/us
22+
DDSpanContextTagsBenchmark.getTag_synchronized_8t thrpt 2 172.344 ops/us
23+
DDSpanContextTagsBenchmark.mixed_stampedlock_8t thrpt 2 70.809 ops/us
24+
DDSpanContextTagsBenchmark.mixed_synchronized_8t thrpt 2 41.302 ops/us
25+
DDSpanContextTagsBenchmark.setMetric_stampedlock_1t thrpt 2 176.073 ops/us
26+
DDSpanContextTagsBenchmark.setMetric_synchronized_1t thrpt 2 163.873 ops/us
27+
DDSpanContextTagsBenchmark.setTag_stampedlock_1t thrpt 2 177.211 ops/us
28+
DDSpanContextTagsBenchmark.setTag_stampedlock_4t thrpt 2 676.775 ops/us
29+
DDSpanContextTagsBenchmark.setTag_synchronized_1t thrpt 2 167.168 ops/us
30+
DDSpanContextTagsBenchmark.setTag_synchronized_4t thrpt 2 619.135 ops/us
31+
*/
32+
@BenchmarkMode(Mode.Throughput)
33+
@OutputTimeUnit(MICROSECONDS)
34+
@Warmup(iterations = 1)
35+
@Measurement(iterations = 2)
36+
@Fork(value = 1)
37+
public class DDSpanContextTagsBenchmark {
38+
39+
/** Simulate the way the Map is locked on DDSpanContext */
40+
static final class SynchronizedTagMap {
41+
private final TagMap tags;
42+
43+
SynchronizedTagMap(int capacity) {
44+
tags = TagMap.create(capacity);
45+
}
46+
47+
void setTag(String key, String value) {
48+
synchronized (tags) {
49+
tags.set(key, value);
50+
}
51+
}
52+
53+
void setMetric(String key, long value) {
54+
synchronized (tags) {
55+
tags.set(key, value);
56+
}
57+
}
58+
59+
Object getTag(String key) {
60+
synchronized (tags) {
61+
return tags.getObject(key);
62+
}
63+
}
64+
}
65+
66+
static final class StampedLockTagMap {
67+
private final TagMap tags;
68+
private final StampedLock lock = new StampedLock();
69+
70+
StampedLockTagMap(int capacity) {
71+
tags = TagMap.create(capacity);
72+
}
73+
74+
void setTag(String key, String value) {
75+
long stamp = lock.writeLock();
76+
try {
77+
tags.set(key, value);
78+
} finally {
79+
lock.unlockWrite(stamp);
80+
}
81+
}
82+
83+
void setMetric(String key, long value) {
84+
long stamp = lock.writeLock();
85+
try {
86+
tags.set(key, value);
87+
} finally {
88+
lock.unlockWrite(stamp);
89+
}
90+
}
91+
92+
Object getTag(String key) {
93+
long stamp = lock.tryOptimisticRead();
94+
Object value = tags.getObject(key);
95+
if (!lock.validate(stamp)) {
96+
stamp = lock.readLock();
97+
try {
98+
value = tags.getObject(key);
99+
} finally {
100+
lock.unlockRead(stamp);
101+
}
102+
}
103+
return value;
104+
}
105+
}
106+
107+
static final SynchronizedTagMap SYNC_TAGS = populate(new SynchronizedTagMap(8));
108+
static final StampedLockTagMap STAMPED_TAGS = populate(new StampedLockTagMap(8));
109+
110+
// Per-thread write targets: no shared state between threads to isolate lock overhead
111+
static final ThreadLocal<SynchronizedTagMap> SYNC_THREAD =
112+
ThreadLocal.withInitial(() -> new SynchronizedTagMap(8));
113+
static final ThreadLocal<StampedLockTagMap> STAMPED_THREAD =
114+
ThreadLocal.withInitial(() -> new StampedLockTagMap(8));
115+
116+
private static <T extends SynchronizedTagMap> T populate(T m) {
117+
m.setTag("http.url", "https://example.com/api/v1/users");
118+
m.setTag("http.method", "GET");
119+
m.setMetric("http.status_code", 200L);
120+
m.setMetric("_sampling_priority_v1", 1L);
121+
return m;
122+
}
123+
124+
private static <T extends StampedLockTagMap> T populate(T m) {
125+
m.setTag("http.url", "https://example.com/api/v1/users");
126+
m.setTag("http.method", "GET");
127+
m.setMetric("http.status_code", 200L);
128+
m.setMetric("_sampling_priority_v1", 1L);
129+
return m;
130+
}
131+
132+
// single threads
133+
134+
@Benchmark
135+
@Threads(1)
136+
public void setTag_synchronized_1t() {
137+
SYNC_THREAD.get().setTag("db.statement", "SELECT 1");
138+
}
139+
140+
@Benchmark
141+
@Threads(1)
142+
public void setTag_stampedlock_1t() {
143+
STAMPED_THREAD.get().setTag("db.statement", "SELECT 1");
144+
}
145+
146+
@Benchmark
147+
@Threads(1)
148+
public void setMetric_synchronized_1t() {
149+
SYNC_THREAD.get().setMetric("http.status_code", 200L);
150+
}
151+
152+
@Benchmark
153+
@Threads(1)
154+
public void setMetric_stampedlock_1t() {
155+
STAMPED_THREAD.get().setMetric("http.status_code", 200L);
156+
}
157+
158+
@Benchmark
159+
@Threads(1)
160+
public Object getTag_synchronized_1t(Blackhole bh) {
161+
return SYNC_TAGS.getTag("http.url");
162+
}
163+
164+
@Benchmark
165+
@Threads(1)
166+
public Object getTag_stampedlock_1t(Blackhole bh) {
167+
return STAMPED_TAGS.getTag("http.url");
168+
}
169+
170+
@Benchmark
171+
@Threads(8)
172+
public Object getTag_synchronized_8t(Blackhole bh) {
173+
return SYNC_TAGS.getTag("http.url");
174+
}
175+
176+
@Benchmark
177+
@Threads(8)
178+
public Object getTag_stampedlock_8t(Blackhole bh) {
179+
return STAMPED_TAGS.getTag("http.url");
180+
}
181+
182+
// 4 concurrent writers
183+
184+
@Benchmark
185+
@Threads(4)
186+
public void setTag_synchronized_4t() {
187+
SYNC_THREAD.get().setTag("db.statement", "SELECT 1");
188+
}
189+
190+
@Benchmark
191+
@Threads(4)
192+
public void setTag_stampedlock_4t() {
193+
STAMPED_THREAD.get().setTag("db.statement", "SELECT 1");
194+
}
195+
196+
// Mixed read+write, 8 threads
197+
198+
@Benchmark
199+
@Threads(8)
200+
public Object mixed_synchronized_8t(Blackhole bh) {
201+
SYNC_TAGS.setTag("db.statement", "SELECT 1");
202+
return SYNC_TAGS.getTag("http.url");
203+
}
204+
205+
@Benchmark
206+
@Threads(8)
207+
public Object mixed_stampedlock_8t(Blackhole bh) {
208+
STAMPED_TAGS.setTag("db.statement", "SELECT 1");
209+
return STAMPED_TAGS.getTag("http.url");
210+
}
211+
}

0 commit comments

Comments
 (0)