|
| 1 | +/* |
| 2 | + * Licensed to the Apache Software Foundation (ASF) under one or more |
| 3 | + * contributor license agreements. See the NOTICE file distributed with |
| 4 | + * this work for additional information regarding copyright ownership. |
| 5 | + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| 6 | + * (the "License"); you may not use this file except in compliance with |
| 7 | + * the License. You may obtain a copy of the License at |
| 8 | + * |
| 9 | + * https://www.apache.org/licenses/LICENSE-2.0 |
| 10 | + * |
| 11 | + * Unless required by applicable law or agreed to in writing, software |
| 12 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | + * See the License for the specific language governing permissions and |
| 15 | + * limitations under the License. |
| 16 | + */ |
| 17 | + |
| 18 | +package org.apache.commons.lang3.math; |
| 19 | + |
| 20 | +import java.util.concurrent.TimeUnit; |
| 21 | + |
| 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.Mode; |
| 27 | +import org.openjdk.jmh.annotations.OutputTimeUnit; |
| 28 | +import org.openjdk.jmh.annotations.Scope; |
| 29 | +import org.openjdk.jmh.annotations.State; |
| 30 | +import org.openjdk.jmh.annotations.Warmup; |
| 31 | + |
| 32 | +/** |
| 33 | + * {@link NumberUtils#createNumber(String)} See https://github.com/apache/commons-lang/pull/1628 |
| 34 | + * |
| 35 | + * <pre> |
| 36 | + * mvn test -P benchmark -Dbenchmark=NumberUtilsBenchmark -P '!jacoco' |
| 37 | + * </pre> |
| 38 | + * Results on my machine on 2026-04-26: |
| 39 | + // @formatter:off |
| 40 | + * <pre> |
| 41 | +# JMH version: 1.37 |
| 42 | +# VM version: JDK 21.0.11, OpenJDK 64-Bit Server VM, 21.0.11 |
| 43 | +# VM invoker: /opt/homebrew/Cellar/openjdk@21/21.0.11/libexec/openjdk.jdk/Contents/Home/bin/java |
| 44 | +# VM options: <none> |
| 45 | +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) |
| 46 | +# Warmup: 3 iterations, 10 s each |
| 47 | +# Measurement: 5 iterations, 10 s each |
| 48 | +# Timeout: 10 min per iteration |
| 49 | +# Threads: 1 thread, will synchronize iterations |
| 50 | +# Benchmark mode: Average time, time/op |
| 51 | +# Benchmark: org.apache.commons.lang3.math.NumberUtilsBenchmark.testDefaultCheck |
| 52 | +
|
| 53 | +# Run progress: 0.00% complete, ETA 00:08:00 |
| 54 | +# Fork: 1 of 3 |
| 55 | +# Warmup Iteration 1: 33.679 ns/op |
| 56 | +# Warmup Iteration 2: 32.796 ns/op |
| 57 | +# Warmup Iteration 3: 33.406 ns/op |
| 58 | +Iteration 1: 33.474 ns/op |
| 59 | +Iteration 2: 33.479 ns/op |
| 60 | +Iteration 3: 33.301 ns/op |
| 61 | +Iteration 4: 33.691 ns/op |
| 62 | +Iteration 5: 33.523 ns/op |
| 63 | +
|
| 64 | +# Run progress: 16.67% complete, ETA 00:06:40 |
| 65 | +# Fork: 2 of 3 |
| 66 | +# Warmup Iteration 1: 33.716 ns/op |
| 67 | +# Warmup Iteration 2: 33.370 ns/op |
| 68 | +# Warmup Iteration 3: 33.073 ns/op |
| 69 | +Iteration 1: 33.057 ns/op |
| 70 | +Iteration 2: 33.076 ns/op |
| 71 | +Iteration 3: 32.987 ns/op |
| 72 | +Iteration 4: 33.102 ns/op |
| 73 | +Iteration 5: 33.060 ns/op |
| 74 | +
|
| 75 | +# Run progress: 33.33% complete, ETA 00:05:20 |
| 76 | +# Fork: 3 of 3 |
| 77 | +# Warmup Iteration 1: 34.668 ns/op |
| 78 | +# Warmup Iteration 2: 33.436 ns/op |
| 79 | +# Warmup Iteration 3: 33.182 ns/op |
| 80 | +Iteration 1: 33.022 ns/op |
| 81 | +Iteration 2: 33.164 ns/op |
| 82 | +Iteration 3: 33.171 ns/op |
| 83 | +Iteration 4: 33.050 ns/op |
| 84 | +Iteration 5: 33.126 ns/op |
| 85 | +
|
| 86 | +
|
| 87 | +Result "org.apache.commons.lang3.math.NumberUtilsBenchmark.testDefaultCheck": |
| 88 | + 33.219 ±(99.9%) 0.235 ns/op [Average] |
| 89 | + (min, avg, max) = (32.987, 33.219, 33.691), stdev = 0.220 |
| 90 | + CI (99.9%): [32.984, 33.454] (assumes normal distribution) |
| 91 | +
|
| 92 | +
|
| 93 | +# JMH version: 1.37 |
| 94 | +# VM version: JDK 21.0.11, OpenJDK 64-Bit Server VM, 21.0.11 |
| 95 | +# VM invoker: /opt/homebrew/Cellar/openjdk@21/21.0.11/libexec/openjdk.jdk/Contents/Home/bin/java |
| 96 | +# VM options: <none> |
| 97 | +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) |
| 98 | +# Warmup: 3 iterations, 10 s each |
| 99 | +# Measurement: 5 iterations, 10 s each |
| 100 | +# Timeout: 10 min per iteration |
| 101 | +# Threads: 1 thread, will synchronize iterations |
| 102 | +# Benchmark mode: Average time, time/op |
| 103 | +# Benchmark: org.apache.commons.lang3.math.NumberUtilsBenchmark.testShortcircuitCheck |
| 104 | +
|
| 105 | +# Run progress: 50.00% complete, ETA 00:04:00 |
| 106 | +# Fork: 1 of 3 |
| 107 | +# Warmup Iteration 1: 0.587 ns/op |
| 108 | +# Warmup Iteration 2: 0.586 ns/op |
| 109 | +# Warmup Iteration 3: 0.586 ns/op |
| 110 | +Iteration 1: 0.586 ns/op |
| 111 | +Iteration 2: 0.586 ns/op |
| 112 | +Iteration 3: 0.586 ns/op |
| 113 | +Iteration 4: 0.586 ns/op |
| 114 | +Iteration 5: 0.586 ns/op |
| 115 | +
|
| 116 | +# Run progress: 66.67% complete, ETA 00:02:40 |
| 117 | +# Fork: 2 of 3 |
| 118 | +# Warmup Iteration 1: 0.627 ns/op |
| 119 | +# Warmup Iteration 2: 0.586 ns/op |
| 120 | +# Warmup Iteration 3: 0.587 ns/op |
| 121 | +Iteration 1: 0.586 ns/op |
| 122 | +Iteration 2: 0.629 ns/op |
| 123 | +Iteration 3: 0.586 ns/op |
| 124 | +Iteration 4: 0.585 ns/op |
| 125 | +Iteration 5: 0.587 ns/op |
| 126 | +
|
| 127 | +# Run progress: 83.33% complete, ETA 00:01:20 |
| 128 | +# Fork: 3 of 3 |
| 129 | +# Warmup Iteration 1: 0.628 ns/op |
| 130 | +# Warmup Iteration 2: 0.586 ns/op |
| 131 | +# Warmup Iteration 3: 0.587 ns/op |
| 132 | +Iteration 1: 0.587 ns/op |
| 133 | +Iteration 2: 0.587 ns/op |
| 134 | +Iteration 3: 0.621 ns/op |
| 135 | +Iteration 4: 0.624 ns/op |
| 136 | +Iteration 5: 0.587 ns/op |
| 137 | +
|
| 138 | +
|
| 139 | +Result "org.apache.commons.lang3.math.NumberUtilsBenchmark.testShortcircuitCheck": |
| 140 | + 0.594 ±(99.9%) 0.017 ns/op [Average] |
| 141 | + (min, avg, max) = (0.585, 0.594, 0.629), stdev = 0.016 |
| 142 | + CI (99.9%): [0.577, 0.611] (assumes normal distribution) |
| 143 | +
|
| 144 | +
|
| 145 | +# Run complete. Total time: 00:08:01 |
| 146 | +
|
| 147 | +REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on |
| 148 | +why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial |
| 149 | +experiments, perform baseline and negative tests that provide experimental control, make sure |
| 150 | +the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. |
| 151 | +Do not assume the numbers tell you what you want them to tell. |
| 152 | +
|
| 153 | +NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise |
| 154 | +extra caution when trusting the results, look into the generated code to check the benchmark still |
| 155 | +works, and factor in a small probability of new VM bugs. Additionally, while comparisons between |
| 156 | +different JVMs are already problematic, the performance difference caused by different Blackhole |
| 157 | +modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons. |
| 158 | +
|
| 159 | +Benchmark Mode Cnt Score Error Units |
| 160 | +NumberUtilsBenchmark.testDefaultCheck avgt 15 33.219 ± 0.235 ns/op |
| 161 | +NumberUtilsBenchmark.testShortcircuitCheck avgt 15 0.594 ± 0.017 ns/op |
| 162 | +
|
| 163 | +Benchmark result is saved to target/jmh-result.NumberUtilsBenchmark.json |
| 164 | + * </pre> |
| 165 | + // @formatter:on |
| 166 | + */ |
| 167 | +@BenchmarkMode(Mode.AverageTime) |
| 168 | +@OutputTimeUnit(TimeUnit.NANOSECONDS) |
| 169 | +@State(Scope.Thread) |
| 170 | +@Fork(3) |
| 171 | +@Warmup(iterations = 3) |
| 172 | +@Measurement(iterations = 5) |
| 173 | +public class NumberUtilsBenchmark { |
| 174 | + |
| 175 | + private static boolean isAllZeros(final String str) { |
| 176 | + if (str == null) { |
| 177 | + return true; |
| 178 | + } |
| 179 | + for (int i = str.length() - 1; i >= 0; i--) { |
| 180 | + if (str.charAt(i) != '0') { |
| 181 | + return false; |
| 182 | + } |
| 183 | + } |
| 184 | + return true; |
| 185 | + } |
| 186 | + |
| 187 | + private static boolean isZero(final String mant, final String dec) { |
| 188 | + return isAllZeros(mant) && isAllZeros(dec); |
| 189 | + } |
| 190 | + |
| 191 | + private final String str = "0.25"; |
| 192 | + |
| 193 | + private final String mant = "0"; |
| 194 | + |
| 195 | + private final String dec = "25"; |
| 196 | + |
| 197 | + Float f = Float.valueOf(str); |
| 198 | + |
| 199 | + Double d = Double.valueOf(str); |
| 200 | + |
| 201 | + @Benchmark |
| 202 | + public boolean testDefaultCheck() { |
| 203 | + return !f.isInfinite() && !(f.floatValue() == 0.0F && !isZero(mant, dec)) && f.toString().equals(d.toString()); |
| 204 | + } |
| 205 | + |
| 206 | + @Benchmark |
| 207 | + public boolean testShortcircuitCheck() { |
| 208 | + return !f.isInfinite() && !(f.floatValue() == 0.0F && !isZero(mant, dec)) && (d.floatValue() == d.doubleValue() || f.toString().equals(d.toString())); |
| 209 | + } |
| 210 | +} |
0 commit comments