From 68250cc5097623cf99ce31e73ca19fa5a9b3332a Mon Sep 17 00:00:00 2001 From: Francesco Nigro Date: Mon, 8 Jan 2024 16:37:44 +0100 Subject: [PATCH] Speedup HTTP headers lowercase transformation --- .../io/vertx/core/http/impl/HttpUtils.java | 29 +++++++- .../HttpUtilsAsciiToLowerCaseBenchmark.java | 72 +++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/test/benchmarks/io/vertx/benchmarks/HttpUtilsAsciiToLowerCaseBenchmark.java diff --git a/src/main/java/io/vertx/core/http/impl/HttpUtils.java b/src/main/java/io/vertx/core/http/impl/HttpUtils.java index 6ecd9a59fcd..cfc2e9f0568 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpUtils.java +++ b/src/main/java/io/vertx/core/http/impl/HttpUtils.java @@ -704,18 +704,32 @@ static String getWebSocketLocation(HttpServerRequest req, boolean ssl) throws Ex } /** + * This is optimized for Http headers, which are usually in the form:
+ * eg Content-Length, content-length + * * @return convert the {@code sequence} to a lower case instance */ public static CharSequence toLowerCase(CharSequence sequence) { - StringBuilder buffer = null; + if (sequence instanceof AsciiString) { + return ((AsciiString) sequence).toLowerCase(); + } + return toCharSequenceLowerCase(sequence); + } + + private static CharSequence toCharSequenceLowerCase(CharSequence sequence) { int len = sequence.length(); + StringBuilder buffer = null; for (int index = 0; index < len; index++) { char c = sequence.charAt(index); if (c >= 'A' && c <= 'Z') { if (buffer == null) { + if (sequence instanceof String) { + return toLowerCase(((String) sequence).toCharArray(), index, len); + } + // just copy the lowercase part buffer = new StringBuilder(sequence); } - buffer.setCharAt(index, (char)(c + ('a' - 'A'))); + buffer.setCharAt(index, (char) (c + ('a' - 'A'))); } } if (buffer != null) { @@ -725,6 +739,17 @@ public static CharSequence toLowerCase(CharSequence sequence) { } } + private static CharSequence toLowerCase(char[] chars, int firstUpperCase, int length) { + assert chars[firstUpperCase] >= 'A' && chars[firstUpperCase] <= 'Z'; + for (int i = firstUpperCase; i < length; i++) { + char c = chars[i]; + if (c >= 'A' && c <= 'Z') { + chars[i] = (char) (c + ('a' - 'A')); + } + } + return new String(chars, 0, length == chars.length? chars.length : length); + } + static HttpVersion toNettyHttpVersion(io.vertx.core.http.HttpVersion version) { switch (version) { case HTTP_1_0: { diff --git a/src/test/benchmarks/io/vertx/benchmarks/HttpUtilsAsciiToLowerCaseBenchmark.java b/src/test/benchmarks/io/vertx/benchmarks/HttpUtilsAsciiToLowerCaseBenchmark.java new file mode 100644 index 00000000000..1dac914a571 --- /dev/null +++ b/src/test/benchmarks/io/vertx/benchmarks/HttpUtilsAsciiToLowerCaseBenchmark.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ + +package io.vertx.benchmarks; + +import io.netty.util.AsciiString; +import io.vertx.core.http.impl.HttpUtils; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +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; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +@State(Scope.Benchmark) +@Warmup(iterations = 20, time = 200, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 200, timeUnit = MILLISECONDS) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(NANOSECONDS) +@Fork(2) +public class HttpUtilsAsciiToLowerCaseBenchmark { + + @Param({"ascii", "string", "builder"}) + public String type; + + @Param({"false", "true"}) + public boolean lowerCase; + private CharSequence sequence; + + @Setup + public void init() { + sequence = create(type, lowerCase ? "content-length" : "Content-Length"); + } + + private CharSequence create(String type, String content) { + + switch (type) { + case "ascii": + return new AsciiString(content); + case "string": + return content; + case "builder": + return new StringBuilder(content); + default: + throw new IllegalStateException(); + } + } + + @Benchmark + public CharSequence toLowerCase() { + return HttpUtils.toLowerCase(sequence); + } + +}