This is an automated email from the ASF dual-hosted git repository. garydgregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-lang.git
commit 45db2840b0d7a4ed23b0659343f0a388eb2bb78e Author: Gary Gregory <[email protected]> AuthorDate: Sun Apr 26 19:02:32 2026 -0400 Add NumberUtilsBenchmark See https://github.com/apache/commons-lang/pull/1628 --- .../commons/lang3/math/NumberUtilsBenchmark.java | 210 +++++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/src/test/java/org/apache/commons/lang3/math/NumberUtilsBenchmark.java b/src/test/java/org/apache/commons/lang3/math/NumberUtilsBenchmark.java new file mode 100644 index 000000000..07b2dcfdc --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/math/NumberUtilsBenchmark.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.lang3.math; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +/** + * {@link NumberUtils#createNumber(String)} See https://github.com/apache/commons-lang/pull/1628 + * + * <pre> + * mvn test -P benchmark -Dbenchmark=NumberUtilsBenchmark -P '!jacoco' + * </pre> + * Results on my machine on 2026-04-26: + // @formatter:off + * <pre> +# JMH version: 1.37 +# VM version: JDK 21.0.11, OpenJDK 64-Bit Server VM, 21.0.11 +# VM invoker: /opt/homebrew/Cellar/openjdk@21/21.0.11/libexec/openjdk.jdk/Contents/Home/bin/java +# VM options: <none> +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 3 iterations, 10 s each +# Measurement: 5 iterations, 10 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: org.apache.commons.lang3.math.NumberUtilsBenchmark.testDefaultCheck + +# Run progress: 0.00% complete, ETA 00:08:00 +# Fork: 1 of 3 +# Warmup Iteration 1: 33.679 ns/op +# Warmup Iteration 2: 32.796 ns/op +# Warmup Iteration 3: 33.406 ns/op +Iteration 1: 33.474 ns/op +Iteration 2: 33.479 ns/op +Iteration 3: 33.301 ns/op +Iteration 4: 33.691 ns/op +Iteration 5: 33.523 ns/op + +# Run progress: 16.67% complete, ETA 00:06:40 +# Fork: 2 of 3 +# Warmup Iteration 1: 33.716 ns/op +# Warmup Iteration 2: 33.370 ns/op +# Warmup Iteration 3: 33.073 ns/op +Iteration 1: 33.057 ns/op +Iteration 2: 33.076 ns/op +Iteration 3: 32.987 ns/op +Iteration 4: 33.102 ns/op +Iteration 5: 33.060 ns/op + +# Run progress: 33.33% complete, ETA 00:05:20 +# Fork: 3 of 3 +# Warmup Iteration 1: 34.668 ns/op +# Warmup Iteration 2: 33.436 ns/op +# Warmup Iteration 3: 33.182 ns/op +Iteration 1: 33.022 ns/op +Iteration 2: 33.164 ns/op +Iteration 3: 33.171 ns/op +Iteration 4: 33.050 ns/op +Iteration 5: 33.126 ns/op + + +Result "org.apache.commons.lang3.math.NumberUtilsBenchmark.testDefaultCheck": + 33.219 ±(99.9%) 0.235 ns/op [Average] + (min, avg, max) = (32.987, 33.219, 33.691), stdev = 0.220 + CI (99.9%): [32.984, 33.454] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 21.0.11, OpenJDK 64-Bit Server VM, 21.0.11 +# VM invoker: /opt/homebrew/Cellar/openjdk@21/21.0.11/libexec/openjdk.jdk/Contents/Home/bin/java +# VM options: <none> +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 3 iterations, 10 s each +# Measurement: 5 iterations, 10 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Average time, time/op +# Benchmark: org.apache.commons.lang3.math.NumberUtilsBenchmark.testShortcircuitCheck + +# Run progress: 50.00% complete, ETA 00:04:00 +# Fork: 1 of 3 +# Warmup Iteration 1: 0.587 ns/op +# Warmup Iteration 2: 0.586 ns/op +# Warmup Iteration 3: 0.586 ns/op +Iteration 1: 0.586 ns/op +Iteration 2: 0.586 ns/op +Iteration 3: 0.586 ns/op +Iteration 4: 0.586 ns/op +Iteration 5: 0.586 ns/op + +# Run progress: 66.67% complete, ETA 00:02:40 +# Fork: 2 of 3 +# Warmup Iteration 1: 0.627 ns/op +# Warmup Iteration 2: 0.586 ns/op +# Warmup Iteration 3: 0.587 ns/op +Iteration 1: 0.586 ns/op +Iteration 2: 0.629 ns/op +Iteration 3: 0.586 ns/op +Iteration 4: 0.585 ns/op +Iteration 5: 0.587 ns/op + +# Run progress: 83.33% complete, ETA 00:01:20 +# Fork: 3 of 3 +# Warmup Iteration 1: 0.628 ns/op +# Warmup Iteration 2: 0.586 ns/op +# Warmup Iteration 3: 0.587 ns/op +Iteration 1: 0.587 ns/op +Iteration 2: 0.587 ns/op +Iteration 3: 0.621 ns/op +Iteration 4: 0.624 ns/op +Iteration 5: 0.587 ns/op + + +Result "org.apache.commons.lang3.math.NumberUtilsBenchmark.testShortcircuitCheck": + 0.594 ±(99.9%) 0.017 ns/op [Average] + (min, avg, max) = (0.585, 0.594, 0.629), stdev = 0.016 + CI (99.9%): [0.577, 0.611] (assumes normal distribution) + + +# Run complete. Total time: 00:08:01 + +REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on +why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial +experiments, perform baseline and negative tests that provide experimental control, make sure +the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. +Do not assume the numbers tell you what you want them to tell. + +NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise +extra caution when trusting the results, look into the generated code to check the benchmark still +works, and factor in a small probability of new VM bugs. Additionally, while comparisons between +different JVMs are already problematic, the performance difference caused by different Blackhole +modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons. + +Benchmark Mode Cnt Score Error Units +NumberUtilsBenchmark.testDefaultCheck avgt 15 33.219 ± 0.235 ns/op +NumberUtilsBenchmark.testShortcircuitCheck avgt 15 0.594 ± 0.017 ns/op + +Benchmark result is saved to target/jmh-result.NumberUtilsBenchmark.json + * </pre> + // @formatter:on + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Thread) +@Fork(3) +@Warmup(iterations = 3) +@Measurement(iterations = 5) +public class NumberUtilsBenchmark { + + private static boolean isAllZeros(final String str) { + if (str == null) { + return true; + } + for (int i = str.length() - 1; i >= 0; i--) { + if (str.charAt(i) != '0') { + return false; + } + } + return true; + } + + private static boolean isZero(final String mant, final String dec) { + return isAllZeros(mant) && isAllZeros(dec); + } + + private final String str = "0.25"; + + private final String mant = "0"; + + private final String dec = "25"; + + Float f = Float.valueOf(str); + + Double d = Double.valueOf(str); + + @Benchmark + public boolean testDefaultCheck() { + return !f.isInfinite() && !(f.floatValue() == 0.0F && !isZero(mant, dec)) && f.toString().equals(d.toString()); + } + + @Benchmark + public boolean testShortcircuitCheck() { + return !f.isInfinite() && !(f.floatValue() == 0.0F && !isZero(mant, dec)) && (d.floatValue() == d.doubleValue() || f.toString().equals(d.toString())); + } +}
