• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package examples;
18 
19 
20 import com.google.caliper.BeforeExperiment;
21 import com.google.caliper.Benchmark;
22 
23 import java.util.BitSet;
24 import java.util.Random;
25 
26 /**
27  * A simple example of a benchmark for BitSet showing some of the issues with
28  * micro-benchmarking.
29  *
30  * <p>The following is a discussion of how the benchmarks evolved and what they
31  * may (or may not) tell us. This discussion is based on the following set of
32  * results:
33  *
34  * <p><pre>
35  *  0% Scenario{vm=java, benchmark=SetBitSetX64} 233.45ns; σ=0.31ns @ 3 trials
36  * 20% Scenario{vm=java, benchmark=SetMaskX64} 116.62ns; σ=0.09ns @ 3 trials
37  * 40% Scenario{vm=java, benchmark=CharsToBitSet} 748.40ns; σ=23.52ns @ 10 trials
38  * 60% Scenario{vm=java, benchmark=CharsToMask} 198.55ns; σ=9.46ns @ 10 trials
39  * 80% Scenario{vm=java, benchmark=BaselineIteration} 67.85ns; σ=0.44ns @ 3 trials
40  *
41  *         benchmark   ns logarithmic runtime
42  *      SetBitSetX64  233 XXXXXXXXX|||||||||||||||
43  *        SetMaskX64  117 XXXX|||||||||||||||||
44  *     CharsToBitSet  748 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
45  *       CharsToMask  199 XXXXXXX||||||||||||||||
46  * BaselineIteration   68 XX|||||||||||||||||
47  * </pre>
48  *
49  * <p>Initially things look simple. The {@link #setBitSetX64(int)} benchmark
50  * takes approximately twice as long as {@link #setMaskX64(int)}. However
51  * the inner loops in these benchmarks have almost no content, so a more
52  * 'real world' benchmark was devised in an attempt to back up these results.
53  *
54  * <p>The {@link #charsToMask(int)} and {@link #charsToBitSet(int)}
55  * benchmarks convert a simple char[] of '1's and '0's to a corresponding BitSet
56  * or bit mask. These also processes 64 bits per iteration and so appears to be
57  * doing the same amount of work as the first benchmarks.
58  *
59  * <p>Additionally the {@link BitSetBenchmark#baselineIteration(int)}
60  * benchmark attempts to measure the raw cost of looping through and reading the
61  * source data.
62  *
63  * <p>When comparing the benchmarks that use bit masking, we see that the
64  * measured time of the SetMaskX64 benchmark (117ns) is roughly the same
65  * as the CharsToMask benchmark (199ns) with the BaselineIteration time (68ms)
66  * subtracted from it. This gives us some confidence that both benchmarks are
67  * resulting in the same underlying work on the CPU.
68  *
69  * <p>However the CharsToBitSet and the SetBitSetX64 benchmarks differ very
70  * significantly (approximately 3x) even when accounting for the
71  * BaselineIteration result. This suggests that the performance of
72  * {@link BitSet#set} is quite dependent on the surrounding code and how
73  * it is optimized by the JVM.
74  *
75  * <p>The conclusions we can draw from this are:
76  *
77  * <p><b>1:</b> Using BitSet is slower than using bit masks directly. At best it
78  * seems about 2x slower than a bit mask, but could easily be 5x slower in real
79  * applications.
80  *
81  * <p>While these are only estimates, we can conclude that when performance is
82  * important and where bit set operations occur in tight loops, bit masks
83  * should be used in favor of BitSets.
84  *
85  * <p><b>2:</b>Overly simplistic benchmarks can give a very false impression of
86  * performance.
87  */
88 public class BitSetBenchmark {
89   private BitSet bitSet;
90   private char[] bitString;
91 
setUp()92   @BeforeExperiment void setUp() throws Exception {
93     bitSet = new BitSet(64);
94     bitString = new char[64];
95     Random r = new Random();
96     for (int n = 0; n < 64; n++) {
97       bitString[n] = r.nextBoolean() ? '1' : '0';
98     }
99   }
100 
101   /**
102    * This benchmark attempts to measure performance of {@link BitSet#set}.
103    */
setBitSetX64(int reps)104   @Benchmark int setBitSetX64(int reps) {
105     long count = 64L * reps;
106     for (int i = 0; i < count; i++) {
107       bitSet.set(i & 0x3F, true);
108     }
109     return bitSet.hashCode();
110   }
111 
112   /**
113    * This benchmark attempts to measure performance of direct bit-manipulation.
114    */
setMaskX64(int reps)115   @Benchmark long setMaskX64(int reps) {
116     long count = 64L * reps;
117     long bitMask = 0L;
118     for (int i = 0; i < count; i++) {
119       bitMask |= 1 << (i & 0x3F);
120     }
121     return bitMask;
122   }
123 
124   /**
125    * This benchmark parses a char[] of 1's and 0's into a BitSet. Results from
126    * this benchmark should be comparable with those from
127    * {@link #charsToMask(int)}.
128    */
charsToBitSet(int reps)129   @Benchmark String charsToBitSet(int reps) {
130     /*
131      * This benchmark now measures the complete parsing of a char[] rather than
132      * a single invocation of {@link BitSet#set}. However this fine because
133      * it is intended to be a comparative benchmark.
134      */
135     for (int i = 0; i < reps; i++) {
136       for (int n = 0; n < bitString.length; n++) {
137         bitSet.set(n, bitString[n] == '1');
138       }
139     }
140     return bitSet.toString();
141   }
142 
143   /**
144    * This benchmark parses a char[] of 1's and 0's into a bit mask. Results from
145    * this benchmark should be comparable with those from
146    * {@link #charsToBitSet(int)}.
147    */
charsToMask(int reps)148   @Benchmark long charsToMask(int reps) {
149     /*
150      * Comparing results we see a far more realistic sounding result whereby
151      * using a bit mask is a little over 4x faster than using BitSet.
152      */
153     long bitMask = 0;
154     for (int i = 0; i < reps; i++) {
155       for (int n = 0; n < bitString.length; n++) {
156         long m = 1 << n;
157         if (bitString[n] == '1') {
158           bitMask |= m;
159         } else {
160           bitMask &= ~m;
161         }
162       }
163     }
164     return bitMask;
165   }
166 
167   /**
168    * This benchmark attempts to measure the baseline cost of both
169    * {@link #charsToBitSet(int)} and {@link #charsToMask(int)}.
170    * It does this by unconditionally summing the character values of the char[].
171    * This is as close to a no-op case as we can expect to get without unwanted
172    * over-optimization.
173    */
baselineIteration(int reps)174   @Benchmark long baselineIteration(int reps) {
175     int badHash = 0;
176     for (int i = 0; i < reps; i++) {
177       for (int n = 0; n < bitString.length; n++) {
178         badHash += bitString[n];
179       }
180     }
181     return badHash;
182   }
183 }
184