diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/http/message/MessageSupportBenchmark.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/http/message/MessageSupportBenchmark.java new file mode 100644 index 000000000..fa758cae0 --- /dev/null +++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/http/message/MessageSupportBenchmark.java @@ -0,0 +1,368 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.core5.http.message; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.RandomAccess; +import java.util.concurrent.TimeUnit; +import java.util.function.UnaryOperator; + +import org.apache.hc.core5.http.HeaderElement; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.util.Args; +import org.apache.hc.core5.util.CharArrayBuffer; +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; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 8, time = 1) +@Fork(3) +public class MessageSupportBenchmark { + + @State(Scope.Thread) + public static class FormatState { + + @Param({"array", "linked"}) + public String listType; + + @Param({"4", "8", "16", "32", "64", "256"}) + public int entryCount; + + private List tokens; + private List elements; + private List parameters; + private CharArrayBuffer tokenBuffer; + private CharArrayBuffer elementBuffer; + private CharArrayBuffer parameterBuffer; + + @Setup + public void setup() { + final List tokenValues = new ArrayList<>(entryCount); + final List elementValues = new ArrayList<>(entryCount); + final List parameterValues = new ArrayList<>(entryCount); + + for (int i = 0; i < entryCount; i++) { + tokenValues.add("token-" + i); + elementValues.add(new BasicHeaderElement("element-" + i, "value-" + i)); + parameterValues.add(new BasicNameValuePair("param-" + i, "value-" + i)); + } + + if ("linked".equals(listType)) { + tokens = new LinkedList<>(tokenValues); + elements = new LinkedList<>(elementValues); + parameters = new LinkedList<>(parameterValues); + } else { + tokens = tokenValues; + elements = elementValues; + parameters = parameterValues; + } + + final int capacity = entryCount * 32 + 64; + tokenBuffer = new CharArrayBuffer(capacity); + elementBuffer = new CharArrayBuffer(capacity); + parameterBuffer = new CharArrayBuffer(capacity); + } + + } + + @Benchmark + public int formatTokensIndexed(final FormatState state) { + state.tokenBuffer.clear(); + formatTokensIndexed(state.tokenBuffer, state.tokens, null); + return state.tokenBuffer.length(); + } + + @Benchmark + public int formatTokensIterator(final FormatState state) { + state.tokenBuffer.clear(); + formatTokensIterator(state.tokenBuffer, state.tokens, null); + return state.tokenBuffer.length(); + } + + @Benchmark + public int formatTokensAdaptive(final FormatState state) { + state.tokenBuffer.clear(); + formatTokensAdaptive(state.tokenBuffer, state.tokens, null); + return state.tokenBuffer.length(); + } + + @Benchmark + public int formatElementsIndexed(final FormatState state) { + state.elementBuffer.clear(); + formatElementsIndexed(state.elementBuffer, state.elements); + return state.elementBuffer.length(); + } + + @Benchmark + public int formatElementsIterator(final FormatState state) { + state.elementBuffer.clear(); + formatElementsIterator(state.elementBuffer, state.elements); + return state.elementBuffer.length(); + } + + @Benchmark + public int formatElementsAdaptive(final FormatState state) { + state.elementBuffer.clear(); + formatElementsAdaptive(state.elementBuffer, state.elements); + return state.elementBuffer.length(); + } + + @Benchmark + public int formatParametersIndexed(final FormatState state) { + state.parameterBuffer.clear(); + formatParametersIndexed(state.parameterBuffer, state.parameters); + return state.parameterBuffer.length(); + } + + @Benchmark + public int formatParametersIterator(final FormatState state) { + state.parameterBuffer.clear(); + formatParametersIterator(state.parameterBuffer, state.parameters); + return state.parameterBuffer.length(); + } + + @Benchmark + public int formatParametersAdaptive(final FormatState state) { + state.parameterBuffer.clear(); + formatParametersAdaptive(state.parameterBuffer, state.parameters); + return state.parameterBuffer.length(); + } + + private static void formatTokensIndexed( + final CharArrayBuffer dst, + final List tokens, + final UnaryOperator transformation) { + Args.notNull(dst, "Destination"); + if (tokens == null) { + return; + } + for (int i = 0; i < tokens.size(); i++) { + final String token = tokens.get(i); + final String element = transformation != null ? transformation.apply(token) : token; + if (i > 0) { + dst.append(", "); + } + dst.append(element); + } + } + + private static void formatTokensIterator( + final CharArrayBuffer dst, + final List tokens, + final UnaryOperator transformation) { + Args.notNull(dst, "Destination"); + if (tokens == null) { + return; + } + final Iterator iterator = tokens.iterator(); + boolean first = true; + while (iterator.hasNext()) { + final String token = iterator.next(); + final String element = transformation != null ? transformation.apply(token) : token; + if (!first) { + dst.append(", "); + } + dst.append(element); + first = false; + } + } + + private static void formatTokensAdaptive( + final CharArrayBuffer dst, + final List tokens, + final UnaryOperator transformation) { + Args.notNull(dst, "Destination"); + if (tokens == null) { + return; + } + if (tokens instanceof RandomAccess) { + for (int i = 0; i < tokens.size(); i++) { + final String token = tokens.get(i); + final String element = transformation != null ? transformation.apply(token) : token; + if (i > 0) { + dst.append(", "); + } + dst.append(element); + } + } else { + final Iterator iterator = tokens.iterator(); + boolean first = true; + while (iterator.hasNext()) { + final String token = iterator.next(); + final String element = transformation != null ? transformation.apply(token) : token; + if (!first) { + dst.append(", "); + } + dst.append(element); + first = false; + } + } + } + + private static void formatElementsIndexed( + final CharArrayBuffer dst, + final List elements) { + Args.notNull(dst, "Destination"); + if (elements == null) { + return; + } + for (int i = 0; i < elements.size(); i++) { + final HeaderElement element = elements.get(i); + if (i > 0) { + dst.append(", "); + } + BasicHeaderValueFormatter.INSTANCE.formatHeaderElement(dst, element, false); + } + } + + private static void formatElementsIterator( + final CharArrayBuffer dst, + final List elements) { + Args.notNull(dst, "Destination"); + if (elements == null) { + return; + } + final Iterator iterator = elements.iterator(); + boolean first = true; + while (iterator.hasNext()) { + final HeaderElement element = iterator.next(); + if (!first) { + dst.append(", "); + } + BasicHeaderValueFormatter.INSTANCE.formatHeaderElement(dst, element, false); + first = false; + } + } + + private static void formatElementsAdaptive( + final CharArrayBuffer dst, + final List elements) { + Args.notNull(dst, "Destination"); + if (elements == null) { + return; + } + if (elements instanceof RandomAccess) { + for (int i = 0; i < elements.size(); i++) { + final HeaderElement element = elements.get(i); + if (i > 0) { + dst.append(", "); + } + BasicHeaderValueFormatter.INSTANCE.formatHeaderElement(dst, element, false); + } + } else { + final Iterator iterator = elements.iterator(); + boolean first = true; + while (iterator.hasNext()) { + final HeaderElement element = iterator.next(); + if (!first) { + dst.append(", "); + } + BasicHeaderValueFormatter.INSTANCE.formatHeaderElement(dst, element, false); + first = false; + } + } + } + + private static void formatParametersIndexed( + final CharArrayBuffer dst, + final List params) { + Args.notNull(dst, "Destination"); + if (params == null) { + return; + } + for (int i = 0; i < params.size(); i++) { + final NameValuePair param = params.get(i); + if (i > 0) { + dst.append("; "); + } + BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(dst, param, false); + } + } + + private static void formatParametersIterator( + final CharArrayBuffer dst, + final List params) { + Args.notNull(dst, "Destination"); + if (params == null) { + return; + } + final Iterator iterator = params.iterator(); + boolean first = true; + while (iterator.hasNext()) { + final NameValuePair param = iterator.next(); + if (!first) { + dst.append("; "); + } + BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(dst, param, false); + first = false; + } + } + + private static void formatParametersAdaptive( + final CharArrayBuffer dst, + final List params) { + Args.notNull(dst, "Destination"); + if (params == null) { + return; + } + if (params instanceof RandomAccess) { + for (int i = 0; i < params.size(); i++) { + final NameValuePair param = params.get(i); + if (i > 0) { + dst.append("; "); + } + BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(dst, param, false); + } + } else { + final Iterator iterator = params.iterator(); + boolean first = true; + while (iterator.hasNext()) { + final NameValuePair param = iterator.next(); + if (!first) { + dst.append("; "); + } + BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(dst, param, false); + first = false; + } + } + } + +} \ No newline at end of file diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java b/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java index 71640401e..55acd6fa8 100644 --- a/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java +++ b/httpcore5/src/main/java/org/apache/hc/core5/http/message/MessageSupport.java @@ -33,6 +33,7 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.RandomAccess; import java.util.Set; import java.util.TreeSet; import java.util.function.BiConsumer; @@ -80,12 +81,27 @@ public static void formatTokens(final CharArrayBuffer dst, final List to if (tokens == null) { return; } - for (int i = 0; i < tokens.size(); i++) { - final String element = transformation != null ? transformation.apply(tokens.get(i)) : tokens.get(i); - if (i > 0) { - dst.append(", "); + if (tokens instanceof RandomAccess) { + for (int i = 0; i < tokens.size(); i++) { + final String token = tokens.get(i); + final String element = transformation != null ? transformation.apply(token) : token; + if (i > 0) { + dst.append(", "); + } + dst.append(element); + } + } else { + final Iterator iterator = tokens.iterator(); + boolean first = true; + while (iterator.hasNext()) { + final String token = iterator.next(); + final String element = transformation != null ? transformation.apply(token) : token; + if (!first) { + dst.append(", "); + } + dst.append(element); + first = false; } - dst.append(element); } } @@ -446,12 +462,25 @@ public static void formatElements(final CharArrayBuffer dst, final List 0) { - dst.append(", "); + if (elements instanceof RandomAccess) { + for (int i = 0; i < elements.size(); i++) { + final HeaderElement element = elements.get(i); + if (i > 0) { + dst.append(", "); + } + BasicHeaderValueFormatter.INSTANCE.formatHeaderElement(dst, element, false); + } + } else { + final Iterator iterator = elements.iterator(); + boolean first = true; + while (iterator.hasNext()) { + final HeaderElement element = iterator.next(); + if (!first) { + dst.append(", "); + } + BasicHeaderValueFormatter.INSTANCE.formatHeaderElement(dst, element, false); + first = false; } - BasicHeaderValueFormatter.INSTANCE.formatHeaderElement(dst, element, false); } } @@ -550,12 +579,25 @@ public static void formatParameters(final CharArrayBuffer dst, final List 0) { - dst.append("; "); + if (params instanceof RandomAccess) { + for (int i = 0; i < params.size(); i++) { + final NameValuePair param = params.get(i); + if (i > 0) { + dst.append("; "); + } + BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(dst, param, false); + } + } else { + final Iterator iterator = params.iterator(); + boolean first = true; + while (iterator.hasNext()) { + final NameValuePair param = iterator.next(); + if (!first) { + dst.append("; "); + } + BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(dst, param, false); + first = false; } - BasicHeaderValueFormatter.INSTANCE.formatNameValuePair(dst, param, false); } }