1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty5.handler.codec.http;
17
18 import io.netty5.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.netty5.handler.codec.http.HttpMethod.CONNECT;
32 import static io.netty5.handler.codec.http.HttpMethod.DELETE;
33 import static io.netty5.handler.codec.http.HttpMethod.GET;
34 import static io.netty5.handler.codec.http.HttpMethod.HEAD;
35 import static io.netty5.handler.codec.http.HttpMethod.OPTIONS;
36 import static io.netty5.handler.codec.http.HttpMethod.PATCH;
37 import static io.netty5.handler.codec.http.HttpMethod.POST;
38 import static io.netty5.handler.codec.http.HttpMethod.PUT;
39 import static io.netty5.handler.codec.http.HttpMethod.TRACE;
40 import static io.netty5.util.internal.MathUtil.findNextPositivePowerOfTwo;
41
42 @State(Scope.Benchmark)
43 @Warmup(iterations = 5)
44 @Measurement(iterations = 8)
45 @OutputTimeUnit(TimeUnit.MICROSECONDS)
46 public class HttpMethodMapBenchmark extends AbstractMicrobenchmark {
47 private static final Map<String, HttpMethod> OLD_MAP = new HashMap<>();
48 private static final SimpleStringMap<HttpMethod> NEW_MAP;
49 private static final String[] KNOWN_METHODS;
50 private static final String[] MIXED_METHODS;
51 private static final String[] UNKNOWN_METHODS;
52
53 static {
54
55
56
57 KNOWN_METHODS = new String[] {
58 "OPTIONS",
59 "GET",
60 "HEAD",
61 "POST",
62 "PUT",
63 "PATCH",
64 "DELETE",
65 "TRACE",
66 "CONNECT"
67 };
68 MIXED_METHODS = new String[] {
69 "OPTIONS",
70 "FAKEMETHOD",
71 "GET",
72 "HEAD",
73 "POST",
74 "UBERGET",
75 "PUT",
76 "PATCH",
77 "MYMETHOD",
78 "DELETE",
79 "TRACE",
80 "CONNECT",
81 "WHATMETHOD"
82 };
83 UNKNOWN_METHODS = new String[] {
84 "FAKEMETHOD",
85 "UBERGET",
86 "MYMETHOD",
87 "TESTING",
88 "WHATMETHOD",
89 "UNKNOWN",
90 "FOOBAR"
91 };
92 OLD_MAP.put(OPTIONS.toString(), OPTIONS);
93 OLD_MAP.put(GET.toString(), GET);
94 OLD_MAP.put(HEAD.toString(), HEAD);
95 OLD_MAP.put(POST.toString(), POST);
96 OLD_MAP.put(PUT.toString(), PUT);
97 OLD_MAP.put(PATCH.toString(), PATCH);
98 OLD_MAP.put(DELETE.toString(), DELETE);
99 OLD_MAP.put(TRACE.toString(), TRACE);
100 OLD_MAP.put(CONNECT.toString(), CONNECT);
101
102 NEW_MAP = new SimpleStringMap<>(
103 new SimpleStringMap.Node<>(OPTIONS.toString(), OPTIONS),
104 new SimpleStringMap.Node<>(GET.toString(), GET),
105 new SimpleStringMap.Node<>(HEAD.toString(), HEAD),
106 new SimpleStringMap.Node<>(POST.toString(), POST),
107 new SimpleStringMap.Node<>(PUT.toString(), PUT),
108 new SimpleStringMap.Node<>(PATCH.toString(), PATCH),
109 new SimpleStringMap.Node<>(DELETE.toString(), DELETE),
110 new SimpleStringMap.Node<>(TRACE.toString(), TRACE),
111 new SimpleStringMap.Node<>(CONNECT.toString(), CONNECT));
112 }
113
114 private static final class SimpleStringMap<T> {
115 private final SimpleStringMap.Node<T>[] values;
116 private final int valuesMask;
117
118 SimpleStringMap(SimpleStringMap.Node<T>... nodes) {
119 values = (SimpleStringMap.Node<T>[]) new SimpleStringMap.Node[findNextPositivePowerOfTwo(nodes.length)];
120 valuesMask = values.length - 1;
121 for (SimpleStringMap.Node<T> node : nodes) {
122 int i = hashCode(node.key) & valuesMask;
123 if (values[i] != null) {
124 throw new IllegalArgumentException("index " + i + " collision between values: [" +
125 values[i].key + ", " + node.key + "]");
126 }
127 values[i] = node;
128 }
129 }
130
131 T get(String name) {
132 SimpleStringMap.Node<T> node = values[hashCode(name) & valuesMask];
133 return node == null || !node.key.equals(name) ? null : node.value;
134 }
135
136 private static int hashCode(String name) {
137
138
139
140 return name.hashCode() >>> 6;
141 }
142
143 private static final class Node<T> {
144 final String key;
145 final T value;
146
147 Node(String key, T value) {
148 this.key = key;
149 this.value = value;
150 }
151 }
152 }
153
154 @Benchmark
155 public void oldMapKnownMethods(Blackhole bh) throws Exception {
156 for (int i = 0; i < KNOWN_METHODS.length; ++i) {
157 bh.consume(OLD_MAP.get(KNOWN_METHODS[i]));
158 }
159 }
160
161 @Benchmark
162 public void newMapKnownMethods(Blackhole bh) throws Exception {
163 for (int i = 0; i < KNOWN_METHODS.length; ++i) {
164 bh.consume(NEW_MAP.get(KNOWN_METHODS[i]));
165 }
166 }
167
168 @Benchmark
169 public void oldMapMixMethods(Blackhole bh) throws Exception {
170 for (int i = 0; i < MIXED_METHODS.length; ++i) {
171 HttpMethod method = OLD_MAP.get(MIXED_METHODS[i]);
172 if (method != null) {
173 bh.consume(method);
174 }
175 }
176 }
177
178 @Benchmark
179 public void newMapMixMethods(Blackhole bh) throws Exception {
180 for (int i = 0; i < MIXED_METHODS.length; ++i) {
181 HttpMethod method = NEW_MAP.get(MIXED_METHODS[i]);
182 if (method != null) {
183 bh.consume(method);
184 }
185 }
186 }
187
188 @Benchmark
189 public void oldMapUnknownMethods(Blackhole bh) throws Exception {
190 for (int i = 0; i < UNKNOWN_METHODS.length; ++i) {
191 HttpMethod method = OLD_MAP.get(UNKNOWN_METHODS[i]);
192 if (method != null) {
193 bh.consume(method);
194 }
195 }
196 }
197
198 @Benchmark
199 public void newMapUnknownMethods(Blackhole bh) throws Exception {
200 for (int i = 0; i < UNKNOWN_METHODS.length; ++i) {
201 HttpMethod method = NEW_MAP.get(UNKNOWN_METHODS[i]);
202 if (method != null) {
203 bh.consume(method);
204 }
205 }
206 }
207 }