View Javadoc
1   /*
2    * Copyright 2024 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.protobuf;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.Unpooled;
20  import io.netty.handler.codec.CorruptedFrameException;
21  import io.netty.microbench.util.AbstractMicrobenchmark;
22  import org.openjdk.jmh.annotations.Benchmark;
23  import org.openjdk.jmh.annotations.BenchmarkMode;
24  import org.openjdk.jmh.annotations.Fork;
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.Random;
34  import java.util.concurrent.TimeUnit;
35  
36  @State(Scope.Benchmark)
37  @OutputTimeUnit(TimeUnit.NANOSECONDS)
38  @BenchmarkMode(org.openjdk.jmh.annotations.Mode.AverageTime)
39  @Fork(2)
40  @Warmup(iterations = 10, time = 400, timeUnit = java.util.concurrent.TimeUnit.MILLISECONDS)
41  @Measurement(iterations = 5, time = 400, timeUnit = java.util.concurrent.TimeUnit.MILLISECONDS)
42  public class VarintDecodingBenchmark extends AbstractMicrobenchmark {
43  
44      private static final int SEED = 0;
45  
46      // Ryzen 7950X is exceptionally good to predict branches, so we need to use A LOT of inputs!
47      @Param({ "1", "128", "128000" })
48      int inputs;
49  
50      public enum InputDistribution {
51          SMALL,
52          LARGE,
53          MEDIUM,
54          ALL
55      }
56  
57      @Param
58      InputDistribution inputDistribution;
59      ByteBuf[] data;
60      int index;
61  
62      @Setup
63      public void init() {
64          ByteBuf[] dataSet;
65          switch (inputDistribution) {
66          case SMALL:
67              dataSet = new ByteBuf[] {
68                      generateData(1, 1),
69                      generateData(2, 2),
70                      generateData(3, 3)
71              };
72              break;
73          case LARGE:
74              dataSet = new ByteBuf[] {
75                      generateData(5, 5)
76              };
77              if (inputs > 1) {
78                  System.exit(1);
79              }
80              break;
81          case MEDIUM:
82              dataSet = new ByteBuf[] {
83                      generateData(1, 5),
84                      generateData(2, 5),
85                      generateData(3, 5),
86                      generateData(4, 5)
87              };
88              break;
89          case ALL:
90              dataSet = new ByteBuf[] {
91                      generateData(1, 1),
92                      generateData(2, 2),
93                      generateData(3, 3),
94                      generateData(1, 5),
95                      generateData(2, 5),
96                      generateData(3, 5),
97                      generateData(4, 5),
98                      generateData(5, 5)
99              };
100             break;
101         default:
102             throw new RuntimeException("Unknown distribution");
103         }
104         data = new ByteBuf[inputs];
105         Random rnd = new Random(SEED);
106         for (int i = 0; i < inputs; i++) {
107             data[i] = dataSet[rnd.nextInt(dataSet.length)];
108         }
109         index = 0;
110     }
111 
112     public static ByteBuf generateData(int varintLength, int capacity) {
113         byte[] bytes = new byte[capacity];
114         for (int i = 0; i < (varintLength - 1); i++) {
115             bytes[i] = (byte) 150;
116         }
117         // delimiter
118         bytes[varintLength - 1] = (byte) 1;
119         return Unpooled.wrappedBuffer(bytes);
120     }
121 
122     public ByteBuf nextData() {
123         index++;
124         if (index == data.length) {
125             index = 0;
126         }
127         return data[index].resetReaderIndex();
128     }
129 
130     @Benchmark
131     public int oldReadRawVarint32() {
132         return oldReadRawVarint32(nextData());
133     }
134 
135     @Benchmark
136     public int readRawVarint32() {
137         return ProtobufVarint32FrameDecoder.readRawVarint32(nextData());
138     }
139 
140     /**
141      * Reads variable length 32bit int from buffer
142      *
143      * @return decoded int if buffers readerIndex has been forwarded else nonsense value
144      */
145     private static int oldReadRawVarint32(ByteBuf buffer) {
146         if (!buffer.isReadable()) {
147             return 0;
148         }
149         buffer.markReaderIndex();
150 
151         byte tmp = buffer.readByte();
152         if (tmp >= 0) {
153             return tmp;
154         } else {
155             int result = tmp & 127;
156             if (!buffer.isReadable()) {
157                 buffer.resetReaderIndex();
158                 return 0;
159             }
160             if ((tmp = buffer.readByte()) >= 0) {
161                 result |= tmp << 7;
162             } else {
163                 result |= (tmp & 127) << 7;
164                 if (!buffer.isReadable()) {
165                     buffer.resetReaderIndex();
166                     return 0;
167                 }
168                 if ((tmp = buffer.readByte()) >= 0) {
169                     result |= tmp << 14;
170                 } else {
171                     result |= (tmp & 127) << 14;
172                     if (!buffer.isReadable()) {
173                         buffer.resetReaderIndex();
174                         return 0;
175                     }
176                     if ((tmp = buffer.readByte()) >= 0) {
177                         result |= tmp << 21;
178                     } else {
179                         result |= (tmp & 127) << 21;
180                         if (!buffer.isReadable()) {
181                             buffer.resetReaderIndex();
182                             return 0;
183                         }
184                         result |= (tmp = buffer.readByte()) << 28;
185                         if (tmp < 0) {
186                             throw new CorruptedFrameException("malformed varint.");
187                         }
188                     }
189                 }
190             }
191             return result;
192         }
193     }
194 
195 }