View Javadoc
1   /*
2    * Copyright 2019 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.http;
17  
18  import io.netty.microbench.util.AbstractMicrobenchmark;
19  import org.openjdk.jmh.annotations.Benchmark;
20  import org.openjdk.jmh.annotations.Measurement;
21  import org.openjdk.jmh.annotations.OutputTimeUnit;
22  import org.openjdk.jmh.annotations.Scope;
23  import org.openjdk.jmh.annotations.State;
24  import org.openjdk.jmh.annotations.Warmup;
25  import org.openjdk.jmh.infra.Blackhole;
26  
27  import java.util.HashMap;
28  import java.util.Map;
29  import java.util.concurrent.TimeUnit;
30  
31  import static io.netty.handler.codec.http.HttpMethod.CONNECT;
32  import static io.netty.handler.codec.http.HttpMethod.DELETE;
33  import static io.netty.handler.codec.http.HttpMethod.GET;
34  import static io.netty.handler.codec.http.HttpMethod.HEAD;
35  import static io.netty.handler.codec.http.HttpMethod.OPTIONS;
36  import static io.netty.handler.codec.http.HttpMethod.PATCH;
37  import static io.netty.handler.codec.http.HttpMethod.POST;
38  import static io.netty.handler.codec.http.HttpMethod.PUT;
39  import static io.netty.handler.codec.http.HttpMethod.TRACE;
40  import static io.netty.handler.codec.http.HttpMethod.QUERY;
41  import static io.netty.util.internal.MathUtil.findNextPositivePowerOfTwo;
42  
43  @State(Scope.Benchmark)
44  @Warmup(iterations = 5)
45  @Measurement(iterations = 8)
46  @OutputTimeUnit(TimeUnit.MICROSECONDS)
47  public class HttpMethodMapBenchmark extends AbstractMicrobenchmark {
48      private static final Map<String, HttpMethod> OLD_MAP = new HashMap<String, HttpMethod>();
49      private static final SimpleStringMap<HttpMethod> NEW_MAP;
50      private static final String[] KNOWN_METHODS;
51      private static final String[] MIXED_METHODS;
52      private static final String[] UNKNOWN_METHODS;
53  
54      static {
55          // We intentionally don't use HttpMethod.toString() here to avoid the equals(..) comparison method from being
56          // able to short circuit due to reference equality checks and being biased toward the new approach. This
57          // simulates the behavior of HttpObjectDecoder which will build new String objects during the decode operation.
58          KNOWN_METHODS = new String[] {
59                  "OPTIONS",
60                  "GET",
61                  "HEAD",
62                  "POST",
63                  "PUT",
64                  "PATCH",
65                  "QUERY",
66                  "DELETE",
67                  "TRACE",
68                  "CONNECT"
69          };
70          MIXED_METHODS = new String[] {
71                  "OPTIONS",
72                  "FAKEMETHOD",
73                  "GET",
74                  "HEAD",
75                  "POST",
76                  "UBERGET",
77                  "PUT",
78                  "PATCH",
79                  "MYMETHOD",
80                  "DELETE",
81                  "TRACE",
82                  "CONNECT",
83                  "WHATMETHOD"
84          };
85          UNKNOWN_METHODS = new String[] {
86                  "FAKEMETHOD",
87                  "UBERGET",
88                  "MYMETHOD",
89                  "TESTING",
90                  "WHATMETHOD",
91                  "UNKNOWN",
92                  "FOOBAR"
93          };
94          OLD_MAP.put(OPTIONS.toString(), OPTIONS);
95          OLD_MAP.put(GET.toString(), GET);
96          OLD_MAP.put(HEAD.toString(), HEAD);
97          OLD_MAP.put(POST.toString(), POST);
98          OLD_MAP.put(PUT.toString(), PUT);
99          OLD_MAP.put(PATCH.toString(), PATCH);
100         OLD_MAP.put(DELETE.toString(), DELETE);
101         OLD_MAP.put(TRACE.toString(), TRACE);
102         OLD_MAP.put(CONNECT.toString(), CONNECT);
103         OLD_MAP.put(QUERY.toString(), QUERY);
104 
105         NEW_MAP = new SimpleStringMap<HttpMethod>(
106                 new SimpleStringMap.Node<HttpMethod>(OPTIONS.toString(), OPTIONS),
107                 new SimpleStringMap.Node<HttpMethod>(GET.toString(), GET),
108                 new SimpleStringMap.Node<HttpMethod>(HEAD.toString(), HEAD),
109                 new SimpleStringMap.Node<HttpMethod>(POST.toString(), POST),
110                 new SimpleStringMap.Node<HttpMethod>(PUT.toString(), PUT),
111                 new SimpleStringMap.Node<HttpMethod>(PATCH.toString(), PATCH),
112                 new SimpleStringMap.Node<HttpMethod>(QUERY.toString(), QUERY),
113                 new SimpleStringMap.Node<HttpMethod>(DELETE.toString(), DELETE),
114                 new SimpleStringMap.Node<HttpMethod>(TRACE.toString(), TRACE),
115                 new SimpleStringMap.Node<HttpMethod>(CONNECT.toString(), CONNECT));
116     }
117 
118     private static final class SimpleStringMap<T> {
119         private final SimpleStringMap.Node<T>[] values;
120         private final int valuesMask;
121 
122         SimpleStringMap(SimpleStringMap.Node<T>... nodes) {
123             values = (SimpleStringMap.Node<T>[]) new SimpleStringMap.Node[findNextPositivePowerOfTwo(nodes.length)];
124             valuesMask = values.length - 1;
125             for (SimpleStringMap.Node<T> node : nodes) {
126                 int i = hashCode(node.key) & valuesMask;
127                 if (values[i] != null) {
128                     throw new IllegalArgumentException("index " + i + " collision between values: [" +
129                             values[i].key + ", " + node.key + "]");
130                 }
131                 values[i] = node;
132             }
133         }
134 
135         T get(String name) {
136             SimpleStringMap.Node<T> node = values[hashCode(name) & valuesMask];
137             return node == null || !node.key.equals(name) ? null : node.value;
138         }
139 
140         private static int hashCode(String name) {
141             // This hash code needs to produce a unique index for each HttpMethod. If new methods are added this
142             // algorithm will need to be adjusted. The goal is to have each enum name's hash value correlate to a unique
143             // index in the values array.
144             return name.hashCode() >>> 6;
145         }
146 
147         private static final class Node<T> {
148             final String key;
149             final T value;
150 
151             Node(String key, T value) {
152                 this.key = key;
153                 this.value = value;
154             }
155         }
156     }
157 
158     @Benchmark
159     public void oldMapKnownMethods(Blackhole bh) throws Exception {
160         for (int i = 0; i < KNOWN_METHODS.length; ++i) {
161             bh.consume(OLD_MAP.get(KNOWN_METHODS[i]));
162         }
163     }
164 
165     @Benchmark
166     public void newMapKnownMethods(Blackhole bh) throws Exception {
167         for (int i = 0; i < KNOWN_METHODS.length; ++i) {
168             bh.consume(NEW_MAP.get(KNOWN_METHODS[i]));
169         }
170     }
171 
172     @Benchmark
173     public void oldMapMixMethods(Blackhole bh) throws Exception {
174         for (int i = 0; i < MIXED_METHODS.length; ++i) {
175             HttpMethod method = OLD_MAP.get(MIXED_METHODS[i]);
176             if (method != null) {
177                 bh.consume(method);
178             }
179         }
180     }
181 
182     @Benchmark
183     public void newMapMixMethods(Blackhole bh) throws Exception {
184         for (int i = 0; i < MIXED_METHODS.length; ++i) {
185             HttpMethod method = NEW_MAP.get(MIXED_METHODS[i]);
186             if (method != null) {
187                 bh.consume(method);
188             }
189         }
190     }
191 
192     @Benchmark
193     public void oldMapUnknownMethods(Blackhole bh) throws Exception {
194         for (int i = 0; i < UNKNOWN_METHODS.length; ++i) {
195             HttpMethod method = OLD_MAP.get(UNKNOWN_METHODS[i]);
196             if (method != null) {
197                 bh.consume(method);
198             }
199         }
200     }
201 
202     @Benchmark
203     public void newMapUnknownMethods(Blackhole bh) throws Exception {
204         for (int i = 0; i < UNKNOWN_METHODS.length; ++i) {
205             HttpMethod method = NEW_MAP.get(UNKNOWN_METHODS[i]);
206             if (method != null) {
207                 bh.consume(method);
208             }
209         }
210     }
211 }