Have AttributesMap extend HashMap instead of contain LinkedHashMap (#2906)

* Reduce HashMp allocations for AttributeMap.

* Finish

* Add comment on why wrapper is actually needed.
This commit is contained in:
Anuraag Agrawal 2021-02-25 02:24:25 +09:00 committed by GitHub
parent 0b973d9a3c
commit b393a587dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 87 additions and 93 deletions

View File

@ -79,7 +79,7 @@ public interface Baggage extends ImplicitContextKeyed {
} }
/** Iterates over all the entries in this {@link Baggage}. */ /** Iterates over all the entries in this {@link Baggage}. */
void forEach(BiConsumer<String, BaggageEntry> consumer); void forEach(BiConsumer<? super String, ? super BaggageEntry> consumer);
/** Returns a read-only view of this {@link Baggage} as a {@link Map}. */ /** Returns a read-only view of this {@link Baggage} as a {@link Map}. */
Map<String, BaggageEntry> asMap(); Map<String, BaggageEntry> asMap();

View File

@ -36,7 +36,7 @@ public interface Attributes {
<T> T get(AttributeKey<T> key); <T> T get(AttributeKey<T> key);
/** Iterates over all the key-value pairs of attributes contained by this instance. */ /** Iterates over all the key-value pairs of attributes contained by this instance. */
void forEach(BiConsumer<AttributeKey<?>, Object> consumer); void forEach(BiConsumer<? super AttributeKey<?>, ? super Object> consumer);
/** The number of attributes contained in this. */ /** The number of attributes contained in this. */
int size(); int size();

View File

@ -81,7 +81,7 @@ public abstract class ImmutableKeyValuePairs<K, V> {
/** Iterates over all the key-value pairs of labels contained by this instance. */ /** Iterates over all the key-value pairs of labels contained by this instance. */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public final void forEach(BiConsumer<K, V> consumer) { public final void forEach(BiConsumer<? super K, ? super V> consumer) {
if (consumer == null) { if (consumer == null) {
return; return;
} }

View File

@ -102,7 +102,7 @@ public interface Labels {
} }
/** Iterates over all the key-value pairs of labels contained by this instance. */ /** Iterates over all the key-value pairs of labels contained by this instance. */
void forEach(BiConsumer<String, String> consumer); void forEach(BiConsumer<? super String, ? super String> consumer);
/** The number of key-value pairs of labels in this instance. */ /** The number of key-value pairs of labels in this instance. */
int size(); int size();

View File

@ -127,20 +127,22 @@ subprojects {
withType(JavaCompile::class) { withType(JavaCompile::class) {
options.release.set(8) options.release.set(8)
options.compilerArgs.addAll(listOf( if (name != "jmhCompileGeneratedClasses") {
"-Xlint:all", options.compilerArgs.addAll(listOf(
// We suppress the "try" warning because it disallows managing an auto-closeable with "-Xlint:all",
// try-with-resources without referencing the auto-closeable within the try block. // We suppress the "try" warning because it disallows managing an auto-closeable with
"-Xlint:-try", // try-with-resources without referencing the auto-closeable within the try block.
// We suppress the "processing" warning as suggested in "-Xlint:-try",
// https://groups.google.com/forum/#!topic/bazel-discuss/_R3A9TJSoPM // We suppress the "processing" warning as suggested in
"-Xlint:-processing", // https://groups.google.com/forum/#!topic/bazel-discuss/_R3A9TJSoPM
// We suppress the "options" warning because it prevents compilation on modern JDKs "-Xlint:-processing",
"-Xlint:-options", // We suppress the "options" warning because it prevents compilation on modern JDKs
"-Xlint:-options",
// Fail build on any warning // Fail build on any warning
"-Werror" "-Werror"
)) ))
}
options.encoding = "UTF-8" options.encoding = "UTF-8"
@ -383,6 +385,7 @@ subprojects {
// Always include the jmhreport plugin and run it after jmh task. // Always include the jmhreport plugin and run it after jmh task.
plugins.apply("io.morethan.jmhreport") plugins.apply("io.morethan.jmhreport")
dependencies { dependencies {
add("jmh", platform(project(":dependencyManagement")))
add("jmh", "org.openjdk.jmh:jmh-core") add("jmh", "org.openjdk.jmh:jmh-core")
add("jmh", "org.openjdk.jmh:jmh-generator-bytecode") add("jmh", "org.openjdk.jmh:jmh-generator-bytecode")
} }

View File

@ -55,7 +55,7 @@ val DEPENDENCY_SETS = listOf(
), ),
DependencySet( DependencySet(
"org.openjdk.jmh", "org.openjdk.jmh",
"1.26", "1.27",
listOf("jmh-core", "jmh-generator-bytecode") listOf("jmh-core", "jmh-generator-bytecode")
), ),
DependencySet( DependencySet(

View File

@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.trace;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.SpanBuilder;
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.Threads;
import org.openjdk.jmh.annotations.Warmup;
@Threads(value = 1)
@Fork(3)
@Warmup(iterations = 10, time = 1)
@Measurement(iterations = 20, time = 1)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class FillSpanBenchmark {
private static final SpanBuilder spanBuilder =
SdkTracerProvider.builder().build().get("benchmark").spanBuilder("benchmark");
private static final AttributeKey<String> KEY1 = AttributeKey.stringKey("key1");
private static final AttributeKey<String> KEY2 = AttributeKey.stringKey("key2");
private static final AttributeKey<String> KEY3 = AttributeKey.stringKey("key3");
private static final AttributeKey<String> KEY4 = AttributeKey.stringKey("key4");
@Benchmark
public void setFourAttributes() {
spanBuilder
.startSpan()
.setAttribute(KEY1, "value1")
.setAttribute(KEY2, "value2")
.setAttribute(KEY3, "value3")
.setAttribute(KEY4, "value4");
}
}

View File

@ -9,40 +9,32 @@ import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.common.AttributesBuilder;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
/** /** A map with a fixed capacity that drops attributes when the map gets full. */
* A map with a fixed capacity that drops attributes when the map gets full.
*
* <p>Note: this doesn't implement the Map interface, but behaves very similarly to one.
*/
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
final class AttributesMap implements Attributes { final class AttributesMap extends HashMap<AttributeKey<?>, Object> implements Attributes {
private final Map<AttributeKey<?>, Object> data;
private static final long serialVersionUID = -5072696312123632376L;
private final long capacity; private final long capacity;
private int totalAddedValues = 0; private int totalAddedValues = 0;
private AttributesMap(long capacity, Map<AttributeKey<?>, Object> data) {
this.capacity = capacity;
this.data = data;
}
AttributesMap(long capacity) { AttributesMap(long capacity) {
this(capacity, new LinkedHashMap<>()); this.capacity = capacity;
} }
public <T> void put(AttributeKey<T> key, T value) { <T> void put(AttributeKey<T> key, T value) {
if (key == null || key.getKey() == null || value == null) { if (key == null || key.getKey() == null || value == null) {
return; return;
} }
totalAddedValues++; totalAddedValues++;
if (data.size() >= capacity && !data.containsKey(key)) { if (size() >= capacity && !containsKey(key)) {
return; return;
} }
data.put(key, value); super.put(key, value);
} }
int getTotalAddedValues() { int getTotalAddedValues() {
@ -52,32 +44,27 @@ final class AttributesMap implements Attributes {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public <T> T get(AttributeKey<T> key) { public <T> T get(AttributeKey<T> key) {
return (T) data.get(key); return (T) super.get(key);
}
@Override
public int size() {
return data.size();
}
@Override
public boolean isEmpty() {
return data.isEmpty();
} }
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
@Override @Override
public void forEach(BiConsumer<AttributeKey<?>, Object> consumer) { public void forEach(BiConsumer<? super AttributeKey<?>, ? super Object> consumer) {
for (Map.Entry<AttributeKey<?>, Object> entry : data.entrySet()) { for (Map.Entry<AttributeKey<?>, Object> entry : entrySet()) {
AttributeKey key = entry.getKey(); AttributeKey key = entry.getKey();
Object value = entry.getValue(); Object value = entry.getValue();
consumer.accept(key, value); consumer.accept((AttributeKey<?>) key, value);
} }
} }
@Override @Override
public Map<AttributeKey<?>, Object> asMap() { public Map<AttributeKey<?>, Object> asMap() {
return Collections.unmodifiableMap(data); // Because Attributes is marked Immutable, IDEs may recognize this as redundant usage. However,
// this class is private and is actually mutable, so we need to wrap with unmodifiableMap
// anyways. We implement the immutable Attributes for this class to support the
// Attributes.builder().putAll usage - it is tricky but an implementation detail of this private
// class.
return Collections.unmodifiableMap(this);
} }
@Override @Override
@ -89,7 +76,7 @@ final class AttributesMap implements Attributes {
public String toString() { public String toString() {
return "AttributesMap{" return "AttributesMap{"
+ "data=" + "data="
+ data + super.toString()
+ ", capacity=" + ", capacity="
+ capacity + capacity
+ ", totalAddedValues=" + ", totalAddedValues="
@ -98,7 +85,6 @@ final class AttributesMap implements Attributes {
} }
Attributes immutableCopy() { Attributes immutableCopy() {
Map<AttributeKey<?>, Object> dataCopy = new LinkedHashMap<>(data); return Attributes.builder().putAll(this).build();
return new AttributesMap(capacity, Collections.unmodifiableMap(dataCopy));
} }
} }

View File

@ -9,29 +9,9 @@ import static io.opentelemetry.api.common.AttributeKey.longKey;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.api.Assertions.entry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
class AttributesMapTest { class AttributesMapTest {
@Test
void attributesAreInOrder() {
AttributesMap attributesMap = new AttributesMap(5);
attributesMap.put(longKey("one"), 1L);
attributesMap.put(longKey("three"), 3L);
attributesMap.put(longKey("two"), 2L);
attributesMap.put(longKey("four"), 4L);
attributesMap.put(longKey("five"), 5L);
List<String> expectedKeyOrder = Arrays.asList("one", "three", "two", "four", "five");
List<Long> expectedValueOrder = Arrays.asList(1L, 3L, 2L, 4L, 5L);
assertOrdering(attributesMap, expectedKeyOrder, expectedValueOrder);
assertOrdering(attributesMap.immutableCopy(), expectedKeyOrder, expectedValueOrder);
}
@Test @Test
void asMap() { void asMap() {
@ -40,22 +20,6 @@ class AttributesMapTest {
attributesMap.put(longKey("two"), 2L); attributesMap.put(longKey("two"), 2L);
assertThat(attributesMap.asMap()) assertThat(attributesMap.asMap())
.containsExactly(entry(longKey("one"), 1L), entry(longKey("two"), 2L)); .containsOnly(entry(longKey("one"), 1L), entry(longKey("two"), 2L));
}
private void assertOrdering(
Attributes attributes, List<String> expectedKeyOrder, List<Long> expectedValueOrder) {
attributes.forEach(
new BiConsumer<AttributeKey<?>, Object>() {
private int counter = 0;
@Override
public void accept(AttributeKey<?> key, Object value) {
String k = key.getKey();
Long val = (Long) value;
assertThat(val).isEqualTo(expectedValueOrder.get(counter));
assertThat(k).isEqualTo(expectedKeyOrder.get(counter++));
}
});
} }
} }

View File

@ -875,8 +875,7 @@ class SdkSpanBuilderTest {
+ "kind=INTERNAL, " + "kind=INTERNAL, "
+ "startEpochNanos=[0-9]+, " + "startEpochNanos=[0-9]+, "
+ "endEpochNanos=[0-9]+, " + "endEpochNanos=[0-9]+, "
+ "attributes=AttributesMap\\{data=\\{http.status_code=500, " + "attributes=AttributesMap\\{data=\\{[^}]*}, capacity=128, totalAddedValues=2}, "
+ "http.url=https://opentelemetry.io}, capacity=128, totalAddedValues=2}, "
+ "totalAttributeCount=2, " + "totalAttributeCount=2, "
+ "events=\\[], " + "events=\\[], "
+ "totalRecordedEvents=0, " + "totalRecordedEvents=0, "

View File

@ -8,7 +8,7 @@ pluginManagement {
id("de.undercouch.download") version "4.1.1" id("de.undercouch.download") version "4.1.1"
id("io.codearte.nexus-staging") version "0.22.0" id("io.codearte.nexus-staging") version "0.22.0"
id("io.morethan.jmhreport") version "0.9.0" id("io.morethan.jmhreport") version "0.9.0"
id("me.champeau.gradle.jmh") version "0.5.2" id("me.champeau.gradle.jmh") version "0.5.3"
id("nebula.release") version "15.3.0" id("nebula.release") version "15.3.0"
id("net.ltgt.errorprone") version "1.3.0" id("net.ltgt.errorprone") version "1.3.0"
id("org.jetbrains.kotlin.jvm") version "1.4.21" id("org.jetbrains.kotlin.jvm") version "1.4.21"