core: cache Accept-Encoding headers (#2766)

* core: cache Accept-Encoding headers

This avoids rebuilding the raw bytes for each RPC.  The decode path
is not yet optimized to avoid pulling to much into a single commit.
Decompressors may still use invalid names, but this is equivalent to
previous behavior, as cleaing happens later in the caching.

Also, internal accessors on DecompressorRegistry are now hidden.

Before:
Benchmark                                                    (extraEncodings)    Mode      Cnt        Score    Error  Units
DecompressorRegistryBenchmark.marshalOld                                    0  sample   928744      124.104 ± 11.159  ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.00                   0  sample                84.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.50                   0  sample                94.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.90                   0  sample               107.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.95                   0  sample               114.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.99                   0  sample               202.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.999                  0  sample              4944.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.9999                 0  sample             12178.008           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p1.00                   0  sample           2056192.000           ns/op
DecompressorRegistryBenchmark.marshalOld                                    1  sample  1345050      150.123 ±  6.952  ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.00                   1  sample               109.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.50                   1  sample               127.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.90                   1  sample               142.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.95                   1  sample               152.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.99                   1  sample               243.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.999                  1  sample              4640.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.9999                 1  sample             11472.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p1.00                   1  sample           2101248.000           ns/op
DecompressorRegistryBenchmark.marshalOld                                    2  sample  1130903      175.846 ±  1.392  ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.00                   2  sample               131.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.50                   2  sample               148.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.90                   2  sample               164.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.95                   2  sample               174.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.99                   2  sample               311.000           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.999                  2  sample              6048.768           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.9999                 2  sample             12349.107           ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p1.00                   2  sample            112000.000           ns/op

After:
Benchmark                                                    (extraEncodings)    Mode      Cnt        Score   Error  Units
DecompressorRegistryBenchmark.marshalOld                                    0  sample  1095005       67.555 ± 5.529  ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.00                   0  sample                42.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.50                   0  sample                52.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.90                   0  sample                69.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.95                   0  sample                84.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.99                   0  sample               133.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.999                  0  sample              3324.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.9999                 0  sample             11056.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p1.00                   0  sample           1820672.000          ns/op
DecompressorRegistryBenchmark.marshalOld                                    1  sample  1437034       78.089 ± 0.723  ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.00                   1  sample                60.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.50                   1  sample                69.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.90                   1  sample                79.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.95                   1  sample                83.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.99                   1  sample                96.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.999                  1  sample              2728.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.9999                 1  sample             11104.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p1.00                   1  sample            105344.000          ns/op
DecompressorRegistryBenchmark.marshalOld                                    2  sample  1203782       95.213 ± 0.864  ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.00                   2  sample                68.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.50                   2  sample                85.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.90                   2  sample                98.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.95                   2  sample               101.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.99                   2  sample               119.000          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.999                  2  sample              3209.736          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p0.9999                 2  sample             11257.947          ns/op
DecompressorRegistryBenchmark.marshalOld:marshalOld·p1.00                   2  sample             63168.000          ns/op
This commit is contained in:
Carl Mastrangelo 2017-03-01 15:30:28 -08:00 committed by GitHub
parent c2cbd5f29d
commit 5c37a8360c
8 changed files with 104 additions and 37 deletions

View File

@ -31,8 +31,8 @@
package io.grpc;
import static io.grpc.DecompressorRegistry.ACCEPT_ENCODING_JOINER;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.TransportFrameUtil;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
@ -84,21 +84,12 @@ public class DecompressorRegistryBenchmark {
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public String dynamicAcceptEncoding() {
if (!reg.getAdvertisedMessageEncodings().isEmpty()) {
return ACCEPT_ENCODING_JOINER.join(reg.getAdvertisedMessageEncodings());
}
return "";
}
/**
* Javadoc comment.
*/
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public String staticAcceptEncoding() {
return reg.getRawAdvertisedMessageEncodings();
public byte[][] marshalOld() {
Metadata m = new Metadata();
m.put(
GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY,
InternalDecompressorRegistry.getRawAdvertisedMessageEncodings(reg));
return TransportFrameUtil.toHttp2Headers(m);
}
}

View File

@ -35,6 +35,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Joiner;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
@ -66,7 +67,7 @@ public final class DecompressorRegistry {
}
private final Map<String, DecompressorInfo> decompressors;
private final String advertisedDecompressors;
private final byte[] advertisedDecompressors;
/**
* Registers a decompressor for both decompression and message encoding negotiation. Returns a
@ -99,12 +100,13 @@ public final class DecompressorRegistry {
newDecompressors.put(encoding, new DecompressorInfo(d, advertised));
decompressors = Collections.unmodifiableMap(newDecompressors);
advertisedDecompressors = ACCEPT_ENCODING_JOINER.join(getAdvertisedMessageEncodings());
advertisedDecompressors = ACCEPT_ENCODING_JOINER.join(getAdvertisedMessageEncodings())
.getBytes(Charset.forName("US-ASCII"));
}
private DecompressorRegistry() {
decompressors = new LinkedHashMap<String, DecompressorInfo>(0);
advertisedDecompressors = "";
advertisedDecompressors = new byte[0];
}
/**
@ -114,7 +116,8 @@ public final class DecompressorRegistry {
return decompressors.keySet();
}
public String getRawAdvertisedMessageEncodings() {
byte[] getRawAdvertisedMessageEncodings() {
return advertisedDecompressors;
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2016, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.grpc;
/**
* Private accessor for decompressor registries. Don't use this.
*/
@Internal
public final class InternalDecompressorRegistry {
private InternalDecompressorRegistry() {}
@Internal
public static byte[] getRawAdvertisedMessageEncodings(DecompressorRegistry reg) {
return reg.getRawAdvertisedMessageEncodings();
}
}

View File

@ -54,6 +54,7 @@ import io.grpc.Context;
import io.grpc.Deadline;
import io.grpc.Decompressor;
import io.grpc.DecompressorRegistry;
import io.grpc.InternalDecompressorRegistry;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.MethodDescriptor.MethodType;
@ -147,8 +148,9 @@ final class ClientCallImpl<ReqT, RespT> extends ClientCall<ReqT, RespT>
}
headers.discardAll(MESSAGE_ACCEPT_ENCODING_KEY);
String advertisedEncodings = decompressorRegistry.getRawAdvertisedMessageEncodings();
if (!advertisedEncodings.isEmpty()) {
byte[] advertisedEncodings =
InternalDecompressorRegistry.getRawAdvertisedMessageEncodings(decompressorRegistry);
if (advertisedEncodings.length != 0) {
headers.put(MESSAGE_ACCEPT_ENCODING_KEY, advertisedEncodings);
}
statsTraceCtx.propagateToHeaders(headers);

View File

@ -40,6 +40,8 @@ import com.google.common.base.Stopwatch;
import com.google.common.base.Supplier;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.grpc.InternalMetadata;
import io.grpc.InternalMetadata.TrustedAsciiMarshaller;
import io.grpc.LoadBalancer.PickResult;
import io.grpc.LoadBalancer.Subchannel;
import io.grpc.Metadata;
@ -49,6 +51,7 @@ import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@ -61,6 +64,8 @@ import javax.annotation.Nullable;
*/
public final class GrpcUtil {
public static final Charset US_ASCII = Charset.forName("US-ASCII");
// Certain production AppEngine runtimes have constraints on threading and socket handling
// that need to be accommodated.
public static final boolean IS_RESTRICTED_APPENGINE =
@ -82,8 +87,20 @@ public final class GrpcUtil {
/**
* {@link io.grpc.Metadata.Key} for the accepted message encodings header.
*/
public static final Metadata.Key<String> MESSAGE_ACCEPT_ENCODING_KEY =
Metadata.Key.of(GrpcUtil.MESSAGE_ACCEPT_ENCODING, Metadata.ASCII_STRING_MARSHALLER);
public static final Metadata.Key<byte[]> MESSAGE_ACCEPT_ENCODING_KEY =
InternalMetadata.keyOf(GrpcUtil.MESSAGE_ACCEPT_ENCODING, new AcceptEncodingMarshaller());
private static final class AcceptEncodingMarshaller implements TrustedAsciiMarshaller<byte[]> {
@Override
public byte[] toAsciiString(byte[] value) {
return value;
}
@Override
public byte[] parseAsciiString(byte[] serialized) {
return serialized;
}
}
/**
* {@link io.grpc.Metadata.Key} for the Content-Type request/response header.

View File

@ -46,6 +46,7 @@ import io.grpc.CompressorRegistry;
import io.grpc.Context;
import io.grpc.Decompressor;
import io.grpc.DecompressorRegistry;
import io.grpc.InternalDecompressorRegistry;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.MethodDescriptor.MethodType;
@ -59,7 +60,7 @@ final class ServerCallImpl<ReqT, RespT> extends ServerCall<ReqT, RespT> {
private final ServerStream stream;
private final MethodDescriptor<ReqT, RespT> method;
private final Context.CancellableContext context;
private final String messageAcceptEncoding;
private final byte[] messageAcceptEncoding;
private final DecompressorRegistry decompressorRegistry;
private final CompressorRegistry compressorRegistry;
private final StatsTraceContext statsTraceCtx;
@ -108,8 +109,9 @@ final class ServerCallImpl<ReqT, RespT> extends ServerCall<ReqT, RespT> {
compressor = Codec.Identity.NONE;
} else {
if (messageAcceptEncoding != null) {
List<String> acceptedEncodingsList =
ACCEPT_ENCODING_SPLITTER.splitToList(messageAcceptEncoding);
// TODO(carl-mastrangelo): remove the string allocation.
List<String> acceptedEncodingsList = ACCEPT_ENCODING_SPLITTER.splitToList(
new String(messageAcceptEncoding, GrpcUtil.US_ASCII));
if (!acceptedEncodingsList.contains(compressor.getMessageEncoding())) {
// resort to using no compression.
compressor = Codec.Identity.NONE;
@ -125,8 +127,9 @@ final class ServerCallImpl<ReqT, RespT> extends ServerCall<ReqT, RespT> {
stream.setCompressor(compressor);
headers.discardAll(MESSAGE_ACCEPT_ENCODING_KEY);
String advertisedEncodings = decompressorRegistry.getRawAdvertisedMessageEncodings();
if (!advertisedEncodings.isEmpty()) {
byte[] advertisedEncodings =
InternalDecompressorRegistry.getRawAdvertisedMessageEncodings(decompressorRegistry);
if (advertisedEncodings.length != 0) {
headers.put(MESSAGE_ACCEPT_ENCODING_KEY, advertisedEncodings);
}

View File

@ -304,8 +304,9 @@ public class ClientCallImplTest {
same(statsTraceCtx));
Metadata actual = metadataCaptor.getValue();
Set<String> acceptedEncodings =
ImmutableSet.copyOf(actual.getAll(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY));
// there should only be one.
Set<String> acceptedEncodings = ImmutableSet.of(
new String(actual.get(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY), GrpcUtil.US_ASCII));
assertEquals(decompressorRegistry.getAdvertisedMessageEncodings(), acceptedEncodings);
}
@ -417,8 +418,8 @@ public class ClientCallImplTest {
ClientCallImpl.prepareHeaders(m, customRegistry, Codec.Identity.NONE, statsTraceCtx);
Iterable<String> acceptedEncodings =
ACCEPT_ENCODING_SPLITTER.split(m.get(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY));
Iterable<String> acceptedEncodings = ACCEPT_ENCODING_SPLITTER.split(
new String(m.get(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY), GrpcUtil.US_ASCII));
// Order may be different, since decoder priorities have not yet been implemented.
assertEquals(ImmutableSet.of("b", "a"), ImmutableSet.copyOf(acceptedEncodings));
@ -428,7 +429,7 @@ public class ClientCallImplTest {
public void prepareHeaders_removeReservedHeaders() {
Metadata m = new Metadata();
m.put(GrpcUtil.MESSAGE_ENCODING_KEY, "gzip");
m.put(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY, "gzip");
m.put(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY, "gzip".getBytes(GrpcUtil.US_ASCII));
ClientCallImpl.prepareHeaders(m, DecompressorRegistry.emptyInstance(), Codec.Identity.NONE,
statsTraceCtx);

View File

@ -66,6 +66,7 @@ import io.grpc.testing.integration.Messages.SimpleRequest;
import io.grpc.testing.integration.Messages.SimpleResponse;
import io.grpc.testing.integration.TestServiceGrpc.TestServiceBlockingStub;
import io.grpc.testing.integration.TransportCompressionTest.Fzip;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -231,13 +232,13 @@ public class CompressionTest {
}
if (serverAcceptEncoding) {
assertEquals("fzip", clientResponseHeaders.get(MESSAGE_ACCEPT_ENCODING_KEY));
assertEqualsString("fzip", clientResponseHeaders.get(MESSAGE_ACCEPT_ENCODING_KEY));
} else {
assertNull(clientResponseHeaders.get(MESSAGE_ACCEPT_ENCODING_KEY));
}
if (clientAcceptEncoding) {
assertEquals("fzip", serverResponseHeaders.get(MESSAGE_ACCEPT_ENCODING_KEY));
assertEqualsString("fzip", serverResponseHeaders.get(MESSAGE_ACCEPT_ENCODING_KEY));
} else {
assertNull(serverResponseHeaders.get(MESSAGE_ACCEPT_ENCODING_KEY));
}
@ -323,4 +324,8 @@ public class CompressionTest {
clientResponseHeaders = headersCopy;
}
}
private static void assertEqualsString(String expected, byte[] actual) {
assertEquals(expected, new String(actual, Charset.forName("US-ASCII")));
}
}