1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
56
57
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
142
143
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 }