Skip to content

Commit 1623063

Browse files
core: speed up Status code and message parsing
This introduces the idea of a "Trusted" Ascii Marshaller, which is known to always produce valid ASCII byte arrays. This saves a surprising amount of garbage, since String conversion involves creating a new java.lang.StringCoding, and a sun.nio.cs.US_ASCII. There are other types that can be converted (notably Http2ClientStream's :status marshaller, which is particularly wasteful). Before: Benchmark Mode Cnt Score Error Units StatusBenchmark.codeDecode sample 641278 88.889 ± 9.673 ns/op StatusBenchmark.codeEncode sample 430800 73.014 ± 1.444 ns/op StatusBenchmark.messageDecodeEscape sample 433467 441.078 ± 58.373 ns/op StatusBenchmark.messageDecodePlain sample 676526 268.620 ± 7.849 ns/op StatusBenchmark.messageEncodeEscape sample 547350 1211.243 ± 29.907 ns/op StatusBenchmark.messageEncodePlain sample 419318 223.263 ± 9.673 ns/op After: Benchmark Mode Cnt Score Error Units StatusBenchmark.codeDecode sample 442241 48.310 ± 2.409 ns/op StatusBenchmark.codeEncode sample 622026 35.475 ± 0.642 ns/op StatusBenchmark.messageDecodeEscape sample 595572 312.407 ± 15.870 ns/op StatusBenchmark.messageDecodePlain sample 565581 99.090 ± 8.799 ns/op StatusBenchmark.messageEncodeEscape sample 479147 201.422 ± 10.765 ns/op StatusBenchmark.messageEncodePlain sample 560957 94.722 ± 1.187 ns/op Also fixes #2237 Before: Result "unaryCall1024": mean = 155710.268 ±(99.9%) 149.278 ns/op Percentiles, ns/op: p(0.0000) = 63552.000 ns/op p(50.0000) = 151552.000 ns/op p(90.0000) = 188672.000 ns/op p(95.0000) = 207360.000 ns/op p(99.0000) = 260608.000 ns/op p(99.9000) = 358912.000 ns/op p(99.9900) = 1851425.792 ns/op p(99.9990) = 11161178.767 ns/op p(99.9999) = 14985005.383 ns/op p(100.0000) = 17235968.000 ns/op Benchmark (direct) (transport) Mode Cnt Score Error Units TransportBenchmark.unaryCall1024 true NETTY sample 3205966 155710.268 ± 149.278 ns/op After: Result "unaryCall1024": mean = 147474.794 ±(99.9%) 128.733 ns/op Percentiles, ns/op: p(0.0000) = 59520.000 ns/op p(50.0000) = 144640.000 ns/op p(90.0000) = 176128.000 ns/op p(95.0000) = 190464.000 ns/op p(99.0000) = 236544.000 ns/op p(99.9000) = 314880.000 ns/op p(99.9900) = 1113084.723 ns/op p(99.9990) = 10783126.979 ns/op p(99.9999) = 13887153.242 ns/op p(100.0000) = 15253504.000 ns/op Benchmark (direct) (transport) Mode Cnt Score Error Units TransportBenchmark.unaryCall1024 true NETTY sample 3385015 147474.794 ± 128.733 ns/op
1 parent af24343 commit 1623063

File tree

5 files changed

+371
-59
lines changed

5 files changed

+371
-59
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright 2016, Google Inc. All rights reserved.
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
*
15+
* * Neither the name of Google Inc. nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package io.grpc;
33+
34+
import org.openjdk.jmh.annotations.Benchmark;
35+
import org.openjdk.jmh.annotations.BenchmarkMode;
36+
import org.openjdk.jmh.annotations.Mode;
37+
import org.openjdk.jmh.annotations.OutputTimeUnit;
38+
import org.openjdk.jmh.annotations.Scope;
39+
import org.openjdk.jmh.annotations.State;
40+
41+
import java.nio.charset.Charset;
42+
import java.util.concurrent.TimeUnit;
43+
44+
/** StatusBenchmark. */
45+
@State(Scope.Benchmark)
46+
public class StatusBenchmark {
47+
48+
/**
49+
* Javadoc comment.
50+
*/
51+
@Benchmark
52+
@BenchmarkMode(Mode.SampleTime)
53+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
54+
public byte[] messageEncodePlain() {
55+
return Status.MESSAGE_KEY.toBytes("Unexpected RST in stream");
56+
}
57+
58+
/**
59+
* Javadoc comment.
60+
*/
61+
@Benchmark
62+
@BenchmarkMode(Mode.SampleTime)
63+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
64+
public byte[] messageEncodeEscape() {
65+
return Status.MESSAGE_KEY.toBytes("Some Error\nWasabi and Horseradish are the same");
66+
}
67+
68+
/**
69+
* Javadoc comment.
70+
*/
71+
@Benchmark
72+
@BenchmarkMode(Mode.SampleTime)
73+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
74+
public String messageDecodePlain() {
75+
return Status.MESSAGE_KEY.parseBytes(
76+
"Unexpected RST in stream".getBytes(Charset.forName("US-ASCII")));
77+
}
78+
79+
/**
80+
* Javadoc comment.
81+
*/
82+
@Benchmark
83+
@BenchmarkMode(Mode.SampleTime)
84+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
85+
public String messageDecodeEscape() {
86+
return Status.MESSAGE_KEY.parseBytes(
87+
"Some Error%10Wasabi and Horseradish are the same".getBytes(Charset.forName("US-ASCII")));
88+
}
89+
90+
/**
91+
* Javadoc comment.
92+
*/
93+
@Benchmark
94+
@BenchmarkMode(Mode.SampleTime)
95+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
96+
public byte[] codeEncode() {
97+
return Status.CODE_KEY.toBytes(Status.DATA_LOSS);
98+
}
99+
100+
/**
101+
* Javadoc comment.
102+
*/
103+
@Benchmark
104+
@BenchmarkMode(Mode.SampleTime)
105+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
106+
public Status codeDecode() {
107+
return Status.CODE_KEY.parseBytes("15".getBytes(Charset.forName("US-ASCII")));
108+
}
109+
}
110+
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2016, Google Inc. All rights reserved.
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
*
15+
* * Neither the name of Google Inc. nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package io.grpc;
33+
34+
import io.grpc.Metadata.Key;
35+
36+
import java.nio.charset.Charset;
37+
38+
/**
39+
* Internal {@link Metadata} accessor. This is intended for use by io.grpc.internal, and the
40+
* specifically supported transport packages. If you *really* think you need to use this, contact
41+
* the gRPC team first.
42+
*/
43+
@Internal
44+
public final class InternalMetadata {
45+
46+
/**
47+
* A specialized plain ASCII marshaller. Both input and output are assumed to be valid header
48+
* ASCII.
49+
*/
50+
@Internal
51+
public interface TrustedAsciiMarshaller<T> {
52+
/**
53+
* Serialize a metadata value to a ASCII string that contains only the characters listed in the
54+
* class comment of {@link io.grpc.Metadata.AsciiMarshaller}. Otherwise the output may be
55+
* considered invalid and discarded by the transport, or the call may fail.
56+
*
57+
* @param value to serialize
58+
* @return serialized version of value, or null if value cannot be transmitted.
59+
*/
60+
byte[] toAsciiString(T value);
61+
62+
/**
63+
* Parse a serialized metadata value from an ASCII string.
64+
*
65+
* @param serialized value of metadata to parse
66+
* @return a parsed instance of type T
67+
*/
68+
T parseAsciiString(byte[] serialized);
69+
}
70+
71+
/**
72+
* Copy of StandardCharsets, which is only available on Java 1.7 and above.
73+
*/
74+
public static final Charset US_ASCII = Charset.forName("US-ASCII");
75+
76+
@Internal
77+
public static <T> Key<T> keyOf(String name, TrustedAsciiMarshaller<T> marshaller) {
78+
return Metadata.Key.of(name, marshaller);
79+
}
80+
}

core/src/main/java/io/grpc/Metadata.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import com.google.common.annotations.VisibleForTesting;
3939
import com.google.common.base.Preconditions;
4040

41+
import io.grpc.InternalMetadata.TrustedAsciiMarshaller;
42+
4143
import java.util.ArrayList;
4244
import java.util.Arrays;
4345
import java.util.BitSet;
@@ -460,6 +462,10 @@ public static <T> Key<T> of(String name, AsciiMarshaller<T> marshaller) {
460462
return new AsciiKey<T>(name, marshaller);
461463
}
462464

465+
static <T> Key<T> of(String name, TrustedAsciiMarshaller<T> marshaller) {
466+
return new TrustedAsciiKey<T>(name, marshaller);
467+
}
468+
463469
private final String originalName;
464470

465471
private final String name;
@@ -603,7 +609,7 @@ private AsciiKey(String name, AsciiMarshaller<T> marshaller) {
603609
super(name);
604610
Preconditions.checkArgument(
605611
!name.endsWith(BINARY_HEADER_SUFFIX),
606-
"ASCII header is named %s. It must not end with %s",
612+
"ASCII header is named %s. Only binary headers may end with %s",
607613
name, BINARY_HEADER_SUFFIX);
608614
this.marshaller = Preconditions.checkNotNull(marshaller, "marshaller");
609615
}
@@ -619,6 +625,32 @@ T parseBytes(byte[] serialized) {
619625
}
620626
}
621627

628+
private static final class TrustedAsciiKey<T> extends Key<T> {
629+
private final TrustedAsciiMarshaller<T> marshaller;
630+
631+
/**
632+
* Keys have a name and an ASCII marshaller used for serialization.
633+
*/
634+
private TrustedAsciiKey(String name, TrustedAsciiMarshaller<T> marshaller) {
635+
super(name);
636+
Preconditions.checkArgument(
637+
!name.endsWith(BINARY_HEADER_SUFFIX),
638+
"ASCII header is named %s. Only binary headers may end with %s",
639+
name, BINARY_HEADER_SUFFIX);
640+
this.marshaller = Preconditions.checkNotNull(marshaller, "marshaller");
641+
}
642+
643+
@Override
644+
byte[] toBytes(T value) {
645+
return marshaller.toAsciiString(value);
646+
}
647+
648+
@Override
649+
T parseBytes(byte[] serialized) {
650+
return marshaller.parseAsciiString(serialized);
651+
}
652+
}
653+
622654
private static class MetadataEntry {
623655
Object parsed;
624656

0 commit comments

Comments
 (0)