diff --git a/core/src/jmh/java/io/grpc/AttributesBenchmark.java b/core/src/jmh/java/io/grpc/AttributesBenchmark.java new file mode 100644 index 0000000000..0cb605d727 --- /dev/null +++ b/core/src/jmh/java/io/grpc/AttributesBenchmark.java @@ -0,0 +1,80 @@ +/* + * Copyright 2017, gRPC Authors All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc; + + +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +/** + * Javadoc. + */ +@State(Scope.Benchmark) +public class AttributesBenchmark { + + public Attributes base = Attributes.EMPTY; + + public Attributes.Key[] keys; + public Attributes withValue = base; + + /** + * Javadoc. + */ + @Setup + @SuppressWarnings({"unchecked", "rawtypes"}) + public void setUp() { + keys = new Attributes.Key[iterations]; + for (int i = 0; i < iterations; i++) { + keys[i] = Attributes.Key.of("any"); + withValue = Attributes.newBuilder(withValue).set(keys[i], "yes").build(); + } + } + + @Param({"1", "2", "10"}) + public int iterations; + + /** + * Javadoc. + */ + @Benchmark + @BenchmarkMode(Mode.SampleTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public Attributes chain() { + Attributes attr = base; + for (int i = 0; i < iterations; i++) { + attr = Attributes.newBuilder(attr).set(keys[i], new Object()).build(); + } + return attr; + } + + /** + * Javadoc. + */ + @Benchmark + @BenchmarkMode(Mode.SampleTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public Object lookup() { + return withValue.get(keys[0]); + } +} diff --git a/core/src/main/java/io/grpc/Attributes.java b/core/src/main/java/io/grpc/Attributes.java index fc86d16f2c..647a4e42cc 100644 --- a/core/src/main/java/io/grpc/Attributes.java +++ b/core/src/main/java/io/grpc/Attributes.java @@ -16,10 +16,13 @@ package io.grpc; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.base.Objects; -import com.google.common.base.Preconditions; import java.util.Collections; -import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -31,11 +34,13 @@ import javax.annotation.concurrent.Immutable; @Immutable public final class Attributes { - private final HashMap, Object> data = new HashMap, Object>(); + private final Map, Object> data; - public static final Attributes EMPTY = new Attributes(); + public static final Attributes EMPTY = new Attributes(Collections., Object>emptyMap()); - private Attributes() { + private Attributes(Map, Object> data) { + assert data != null; + this.data = data; } /** @@ -60,14 +65,15 @@ public final class Attributes { * Create a new builder that is pre-populated with the content from a given container. */ public static Builder newBuilder(Attributes base) { - return newBuilder().setAll(base); + checkNotNull(base, "base"); + return new Builder(base); } /** * Create a new builder. */ public static Builder newBuilder() { - return new Builder(); + return new Builder(EMPTY); } /** @@ -144,30 +150,45 @@ public final class Attributes { * The helper class to build an Attributes instance. */ public static final class Builder { - private Attributes product; + private Attributes base; + private Map, Object> newdata; - private Builder() { - this.product = new Attributes(); + private Builder(Attributes base) { + assert base != null; + this.base = base; + } + + private Map, Object> data(int size) { + if (newdata == null) { + newdata = new IdentityHashMap, Object>(size); + } + return newdata; } public Builder set(Key key, T value) { - product.data.put(key, value); + data(1).put(key, value); return this; } public Builder setAll(Attributes other) { - product.data.putAll(other.data); + data(other.data.size()).putAll(other.data); return this; } /** - * Build the attributes. Can only be called once. + * Build the attributes. */ public Attributes build() { - Preconditions.checkState(product != null, "Already built"); - Attributes result = product; - product = null; - return result; + if (newdata != null) { + for (Entry, Object> entry : base.data.entrySet()) { + if (!newdata.containsKey(entry.getKey())) { + newdata.put(entry.getKey(), entry.getValue()); + } + } + base = new Attributes(newdata); + newdata = null; + } + return base; } } }