Skip to content

Commit e092c44

Browse files
committed
Reducing allocation in interceptCompleteTrace
Introduced TraceList which is just an ArrayList that exposes the internal modCount Being able to access the modCount allows interceptCompleteTrace to check if the list post-interception has been modified If the list is the same & is unmodified, then interceptCompleteTrace is able to bypass making a defensive copy As a further optimization, use of TraceList is pushed up to callers of interceptCompleteTrace and TraceCollector.write That does mean StreamingTraceCollector can no longer used a singletonList, but the savings throughput the pipeline outweigh the cost of the extra Object[] in that case
1 parent 8a33be4 commit e092c44

4 files changed

Lines changed: 50 additions & 14 deletions

File tree

dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,7 +1202,7 @@ public AgentDataStreamsMonitoring getDataStreamsMonitoring() {
12021202
*
12031203
* @param trace a list of the spans related to the same trace
12041204
*/
1205-
void write(final List<DDSpan> trace) {
1205+
void write(final TraceList trace) {
12061206
if (trace.isEmpty() || !trace.get(0).traceConfig().isTraceEnabled()) {
12071207
return;
12081208
}
@@ -1239,9 +1239,22 @@ void write(final List<DDSpan> trace) {
12391239
}
12401240
}
12411241

1242-
private List<DDSpan> interceptCompleteTrace(List<DDSpan> trace) {
1243-
if (!interceptors.isEmpty() && !trace.isEmpty()) {
1244-
Collection<? extends MutableSpan> interceptedTrace = new ArrayList<>(trace);
1242+
private List<DDSpan> interceptCompleteTrace(TraceList originalTrace) {
1243+
if (!interceptors.isEmpty() && !originalTrace.isEmpty()) {
1244+
// Using TraceList to optimize the common case where the interceptors,
1245+
// don't alter the list. If the interceptors just return the provided
1246+
// List, then no need to copy to another List.
1247+
1248+
// As an extra precaution, also check the modCount before and after on
1249+
// the TraceList, since TraceInterceptor could put some other type of
1250+
// object into the List.
1251+
1252+
// There is still a risk that a TraceInterceptor holds onto the provided
1253+
// List and modifies it later on, but we cannot safeguard against
1254+
// every possible misuse.
1255+
Collection<? extends MutableSpan> interceptedTrace = originalTrace;
1256+
int originalModCount = originalTrace.modCount();
1257+
12451258
for (final TraceInterceptor interceptor : interceptors) {
12461259
try {
12471260
// If one TraceInterceptor throws an exception, then continue with the next one
@@ -1255,18 +1268,20 @@ private List<DDSpan> interceptCompleteTrace(List<DDSpan> trace) {
12551268
}
12561269
}
12571270

1258-
// DQH - common case is that the list is that we return the same list (usually unaltered)
1259-
// In that case, there's no need to copy into a new list
1260-
if ( interceptedTrace != trace ) {
1261-
trace = new ArrayList<>(interceptedTrace.size());
1271+
if (interceptedTrace != originalTrace || originalTrace.modCount() != originalModCount) {
1272+
TraceList trace = new TraceList(interceptedTrace.size());
12621273
for (final MutableSpan span : interceptedTrace) {
12631274
if (span instanceof DDSpan) {
12641275
trace.add((DDSpan) span);
12651276
}
12661277
}
1278+
return trace;
1279+
} else {
1280+
return originalTrace;
12671281
}
1282+
} else {
1283+
return originalTrace;
12681284
}
1269-
return trace;
12701285
}
12711286

12721287
@Override

dd-trace-core/src/main/java/datadog/trace/core/PendingTrace.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ private int write(boolean isPartial) {
327327
if (!spans.isEmpty()) {
328328
try (Recording recording = tracer.writeTimer()) {
329329
// Only one writer at a time
330-
final List<DDSpan> trace;
330+
final TraceList trace;
331331
int completedSpans = 0;
332332
synchronized (this) {
333333
if (!isPartial) {
@@ -346,10 +346,10 @@ private int write(boolean isPartial) {
346346
// count(s) will be incremented, and any new spans added during the period that the count
347347
// was negative will be written by someone even if we don't write them right now.
348348
if (size > 0 && (!isPartial || size >= tracer.getPartialFlushMinSpans())) {
349-
trace = new ArrayList<>(size);
349+
trace = new TraceList(size);
350350
completedSpans = enqueueSpansToWrite(trace, writeRunningSpans);
351351
} else {
352-
trace = EMPTY;
352+
trace = TraceList.EMPTY;
353353
}
354354
}
355355
if (!trace.isEmpty()) {

dd-trace-core/src/main/java/datadog/trace/core/StreamingTraceCollector.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import datadog.trace.api.time.TimeSource;
55
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
66
import datadog.trace.core.monitor.HealthMetrics;
7-
import java.util.Collections;
87
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
98
import javax.annotation.Nonnull;
109

@@ -72,7 +71,7 @@ PublishState onPublish(DDSpan span) {
7271
tracer.onRootSpanPublished(rootSpan);
7372
}
7473
healthMetrics.onFinishSpan();
75-
tracer.write(Collections.singletonList(span));
74+
tracer.write(TraceList.of(span));
7675
return PublishState.WRITTEN;
7776
}
7877

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package datadog.trace.core;
2+
3+
import java.util.ArrayList;
4+
5+
/** ArrayList that exposes modCount to allow for an optimization in TraceInterceptor handling */
6+
final class TraceList extends ArrayList<DDSpan> {
7+
static final TraceList EMPTY = new TraceList(0);
8+
9+
static final TraceList of(DDSpan span) {
10+
TraceList list = new TraceList(1);
11+
list.add(span);
12+
return list;
13+
}
14+
15+
TraceList(int capacity) {
16+
super(capacity);
17+
}
18+
19+
int modCount() {
20+
return this.modCount;
21+
}
22+
}

0 commit comments

Comments
 (0)