Skip to content

Commit 210e226

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
1 parent bdf8b01 commit 210e226

File tree

3 files changed

+277
-50
lines changed

3 files changed

+277
-50
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+

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,26 @@ public interface AsciiMarshaller<T> {
404404
T parseAsciiString(String serialized);
405405
}
406406

407+
@Internal
408+
interface TrustedAsciiMarshaller<T> {
409+
/**
410+
* Serialize a metadata value to a ASCII string that contains only the characters listed in the
411+
* class comment of {@link AsciiMarshaller}. Otherwise the output may be considered invalid and
412+
* discarded by the transport, or the call may fail.
413+
*
414+
* @param value to serialize
415+
* @return serialized version of value, or null if value cannot be transmitted.
416+
*/
417+
byte[] toAsciiString(T value);
418+
419+
/**
420+
* Parse a serialized metadata value from an ASCII string.
421+
* @param serialized value of metadata to parse
422+
* @return a parsed instance of type T
423+
*/
424+
T parseAsciiString(byte[] serialized);
425+
}
426+
407427
/**
408428
* Key for metadata entries. Allows for parsing and serialization of metadata.
409429
*
@@ -456,6 +476,10 @@ public static <T> Key<T> of(String name, AsciiMarshaller<T> marshaller) {
456476
return new AsciiKey<T>(name, marshaller);
457477
}
458478

479+
static <T> Key<T> of(String name, TrustedAsciiMarshaller<T> marshaller) {
480+
return new TrustedAsciiKey<T>(name, marshaller);
481+
}
482+
459483
private final String originalName;
460484

461485
private final String name;
@@ -615,6 +639,33 @@ T parseBytes(byte[] serialized) {
615639
}
616640
}
617641

642+
643+
private static class TrustedAsciiKey<T> extends Key<T> {
644+
private final TrustedAsciiMarshaller<T> marshaller;
645+
646+
/**
647+
* Keys have a name and an ASCII marshaller used for serialization.
648+
*/
649+
private TrustedAsciiKey(String name, TrustedAsciiMarshaller<T> marshaller) {
650+
super(name);
651+
Preconditions.checkArgument(
652+
!name.endsWith(BINARY_HEADER_SUFFIX),
653+
"ASCII header is named %s. It must not end with %s",
654+
name, BINARY_HEADER_SUFFIX);
655+
this.marshaller = Preconditions.checkNotNull(marshaller, "marshaller");
656+
}
657+
658+
@Override
659+
byte[] toBytes(T value) {
660+
return marshaller.toAsciiString(value);
661+
}
662+
663+
@Override
664+
T parseBytes(byte[] serialized) {
665+
return marshaller.parseAsciiString(serialized);
666+
}
667+
}
668+
618669
private static class MetadataEntry {
619670
Object parsed;
620671

0 commit comments

Comments
 (0)