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 io.netty.util.internal.MathUtil;
20  import io.netty.util.internal.PlatformDependent;
21  import io.netty.util.internal.StringUtil;
22  import org.openjdk.jmh.annotations.Benchmark;
23  import org.openjdk.jmh.annotations.CompilerControl;
24  import org.openjdk.jmh.annotations.CompilerControl.Mode;
25  import org.openjdk.jmh.annotations.Measurement;
26  import org.openjdk.jmh.annotations.OutputTimeUnit;
27  import org.openjdk.jmh.annotations.Param;
28  import org.openjdk.jmh.annotations.Scope;
29  import org.openjdk.jmh.annotations.Setup;
30  import org.openjdk.jmh.annotations.State;
31  import org.openjdk.jmh.annotations.Warmup;
32  
33  import java.util.Arrays;
34  import java.util.Random;
35  import java.util.concurrent.TimeUnit;
36  
37  @State(Scope.Benchmark)
38  @Warmup(iterations = 5, time = 1)
39  @Measurement(iterations = 5, time = 1)
40  @OutputTimeUnit(TimeUnit.MICROSECONDS)
41  public class DecodeHexBenchmark extends AbstractMicrobenchmark {
42  
43      @Param({
44              //with HEX chars
45              "135aBa9BBCEA030b947d79fCcaf48Bde",
46              //with HEX chars + 'g'
47              "4DDeA5gDD1C6fE567E1b6gf0C40FEcDg",
48      })
49      private String hex;
50      // Needs to specify a high number of inputs to allow the current strategy
51      // on nextHexDigits to produce enough branch-misses
52      @Param({ "2048" })
53      private int inputs;
54      private char[][] hexDigits;
55      private static final long SEED = 1578675524L;
56      private long next;
57  
58      @Setup
59      public void init() {
60          final char[] hexCh = hex.toCharArray();
61          next = 0;
62          inputs = MathUtil.findNextPositivePowerOfTwo(inputs);
63          hexDigits = new char[inputs][];
64          hexDigits[0] = hexCh;
65          if (inputs > 1) {
66              final Random rnd = new Random(SEED);
67              for (int i = 1; i < inputs; i++) {
68                  hexDigits[i] = shuffle(Arrays.copyOf(hexCh, hexCh.length), rnd);
69              }
70          }
71      }
72  
73      // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
74      private static char[] shuffle(char[] chars, Random rnd) {
75          int index;
76          char tmp;
77          for (int i = chars.length - 1; i > 0; i--) {
78              index = rnd.nextInt(i + 1);
79              tmp = chars[index];
80              chars[index] = chars[i];
81              chars[i] = tmp;
82          }
83          return chars;
84      }
85  
86      private int nextHexDigits() {
87          final int idx = (int) (next & (inputs - 1));
88          next++;
89          return idx;
90      }
91  
92      @Benchmark
93      @CompilerControl(Mode.DONT_INLINE)
94      public long hexDigits() {
95          long v = 0;
96          final char[] hexDigits = this.hexDigits[nextHexDigits()];
97          for (int i = 0, size = hexDigits.length; i < size; i++) {
98              v += StringUtil.decodeHexNibble(hexDigits[i]);
99          }
100         return v;
101     }
102 
103     @Benchmark
104     @CompilerControl(Mode.DONT_INLINE)
105     public long hexDigitsWithChecks() {
106         long v = 0;
107         final char[] hexDigits = this.hexDigits[nextHexDigits()];
108         for (int i = 0, size = hexDigits.length; i < size; i++) {
109             v += decodeHexNibbleWithCheck(hexDigits[i]);
110         }
111         return v;
112     }
113 
114     @Benchmark
115     @CompilerControl(Mode.DONT_INLINE)
116     public long hexDigitsOriginal() {
117         long v = 0;
118         final char[] hexDigits = this.hexDigits[nextHexDigits()];
119         for (int i = 0, size = hexDigits.length; i < size; i++) {
120             v += decodeHexNibble(hexDigits[i]);
121         }
122         return v;
123     }
124 
125     private static int decodeHexNibble(final char c) {
126         if (c >= '0' && c <= '9') {
127             return c - '0';
128         }
129         if (c >= 'A' && c <= 'F') {
130             return c - ('A' - 0xA);
131         }
132         if (c >= 'a' && c <= 'f') {
133             return c - ('a' - 0xA);
134         }
135         return -1;
136     }
137 
138     private static final byte[] HEX2B;
139 
140     static {
141         HEX2B = new byte['f' + 1];
142         Arrays.fill(HEX2B, (byte) -1);
143         HEX2B['0'] = 0;
144         HEX2B['1'] = 1;
145         HEX2B['2'] = 2;
146         HEX2B['3'] = 3;
147         HEX2B['4'] = 4;
148         HEX2B['5'] = 5;
149         HEX2B['6'] = 6;
150         HEX2B['7'] = 7;
151         HEX2B['8'] = 8;
152         HEX2B['9'] = 9;
153         HEX2B['A'] = 10;
154         HEX2B['B'] = 11;
155         HEX2B['C'] = 12;
156         HEX2B['D'] = 13;
157         HEX2B['E'] = 14;
158         HEX2B['F'] = 15;
159         HEX2B['a'] = 10;
160         HEX2B['b'] = 11;
161         HEX2B['c'] = 12;
162         HEX2B['d'] = 13;
163         HEX2B['e'] = 14;
164         HEX2B['f'] = 15;
165     }
166 
167     private static int decodeHexNibbleWithCheck(final char c) {
168         if (c >= HEX2B.length) {
169             return -1;
170         }
171         if (PlatformDependent.hasUnsafe()) {
172             return PlatformDependent.getByte(HEX2B, c);
173         }
174         return HEX2B[c];
175     }
176 
177 }