blob: 2b78c0aa63c18113790970d574bb42380cc17647 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed 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
*
* http://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 benchmarks;
import java.math.BigInteger;
/**
* Tries to measure important BigInteger operations across a variety of BigInteger sizes.
* Note that BigInteger implementations commonly need to use wildly different algorithms
* for different sizes, so relative performance may change substantially depending on the
* size of the integer.
* This is not structured as a proper benchmark; just run main(), e.g. with
* vogar libcore/benchmarks/src/benchmarks/BigIntegerBenchmark.java.
*/
public class BigIntegerBenchmark {
private static final boolean PRINT_TIMES = true;
private static long getStartTime() {
if (PRINT_TIMES) {
return System.nanoTime();
} else {
return 0;
}
}
private static void printTime(String s, long startTime, int reps) {
if (PRINT_TIMES) {
System.out.println(s
+ (double)(System.nanoTime() - startTime) / 1000.0 / reps + " usecs / iter");
}
}
// A simple sum of products computation, mostly so we can check timing in the
// absence of any division. Computes the sum from 1 to n of ((10^prec) << 30) + 1)^2,
// repeating the multiplication, but not addition of 1, each time through the loop.
// Check the last few bits of the result as we go. Assumes n < 2^30.
// Note that we're actually squaring values in computing the product.
// That affects the algorithm used by some implementations.
private static void inner(int n, int prec) {
BigInteger big = BigInteger.TEN.pow(prec).shiftLeft(30).add(BigInteger.ONE);
BigInteger sum = BigInteger.ZERO;
for (int i = 0; i < n; ++i) {
sum = sum.add(big.multiply(big));
}
if (sum.and(BigInteger.valueOf(0x3fffffff)).intValue() != n) {
System.out.println("inner() got " + sum.and(BigInteger.valueOf(0x3fffffff))
+ " instead of " + n);
}
}
// Execute the above rep times, optionally timing it.
private static void repeatInner(int n, int prec, int rep) {
long startTime = getStartTime();
for (int i = 0; i < rep; ++i) {
inner(n, prec);
}
printTime("inner(" + n + "," + prec + ") took ", startTime, rep);
}
// Approximate the sum of the first 1000 terms of the harmonic series (sum of 1/m as m
// goes from 1 to n) to about prec digits. The result has an implicit decimal point
// prec digits from the right.
private static BigInteger harmonic1000(int prec) {
BigInteger scaledOne = BigInteger.TEN.pow(prec);
BigInteger sum = BigInteger.ZERO;
for (int i = 1; i <= 1000; ++i) {
sum = sum.add(scaledOne.divide(BigInteger.valueOf(i)));
}
return sum;
}
// Execute the above rep times, optionally timing it.
// Check results for equality, and print one, to compaare against reference.
private static void repeatHarmonic1000(int prec, int rep) {
long startTime = getStartTime();
BigInteger refRes = harmonic1000(prec);
for (int i = 1; i < rep; ++i) {
BigInteger newRes = harmonic1000(prec);
if (!newRes.equals(refRes)) {
throw new AssertionError(newRes + " != " + refRes);
}
}
printTime("harmonic(1000) to " + prec + " digits took ", startTime, rep);
if (prec >= 50 && !refRes.toString()
.startsWith("748547086055034491265651820433390017652167916970")) {
throw new AssertionError("harmanic(" + prec + ") incorrectly produced " + refRes);
}
}
// Repeatedly execute just the base conversion from the last test, allowing
// us to time and check it for consistency as well.
private static void repeatToString(int prec, int rep) {
BigInteger refRes = harmonic1000(prec);
long startTime = getStartTime();
String refString = refRes.toString();
for (int i = 1; i < rep; ++i) {
// Disguise refRes to avoid compiler optimization issues.
BigInteger newRes = refRes.shiftLeft(30).add(BigInteger.valueOf(i)).shiftRight(30);
// The time-consuming part:
String newString = newRes.toString();
if (!newString.equals(refString)) {
System.out.println(newString + " != " + refString);
}
}
printTime("toString(" + prec + ") took ", startTime, rep);
}
// Compute base^exp, where base and result are scaled/multiplied by scaleBy to make them
// integers. exp >= 0 .
private static BigInteger myPow(BigInteger base, int exp, BigInteger scaleBy) {
if (exp == 0) {
return scaleBy; // Return one.
} else if ((exp & 1) != 0) {
BigInteger tmp = myPow(base, exp - 1, scaleBy);
return tmp.multiply(base).divide(scaleBy);
} else {
BigInteger tmp = myPow(base, exp / 2, scaleBy);
return tmp.multiply(tmp).divide(scaleBy);
}
}
// Approximate e by computing (1 + 1/n)^n to prec decimal digits.
// This isn't necessarily a very good approximation to e.
// Return the result, scaled by 10^prec.
private static BigInteger eApprox(int n, int prec) {
BigInteger scaledOne = BigInteger.TEN.pow(prec);
BigInteger base = scaledOne.add(scaledOne.divide(BigInteger.valueOf(n)));
return myPow(base, n, scaledOne);
}
// Repeatedly execute and check the above, printing one of the results
// to compare to reference.
private static void repeatEApprox(int n, int prec, int rep) {
long startTime = getStartTime();
BigInteger refRes = eApprox(n, prec);
for (int i = 1; i < rep; ++i) {
BigInteger newRes = eApprox(n, prec);
if (!newRes.equals(refRes)) {
throw new AssertionError(newRes + " != " + refRes);
}
}
printTime("eApprox(" + n + "," + prec + ") took ", startTime, rep);
if (n >= 100000 && prec >= 10 && !refRes.toString().startsWith("271826")) {
throw new AssertionError("eApprox(" + n + "," + prec + ") incorrectly produced "
+ refRes);
}
}
// Test / time modPow()
private static void repeatModPow(int len, int rep) {
BigInteger odd1 = BigInteger.TEN.pow(len / 2).add(BigInteger.ONE);
BigInteger odd2 = BigInteger.TEN.pow(len / 2).add(BigInteger.valueOf(17));
BigInteger product = odd1.multiply(odd2);
BigInteger exponent = BigInteger.TEN.pow(len / 2 - 1);
BigInteger base = BigInteger.TEN.pow(len / 4);
long startTime = getStartTime();
BigInteger lastRes = null;
for (int i = 0; i < rep; ++i) {
BigInteger newRes = base.modPow(exponent, product);
if (i != 0 && !newRes.equals(lastRes)) {
System.out.println(newRes + " != " + lastRes);
}
lastRes = newRes;
}
printTime("ModPow() at decimal length " + len + " took ", startTime, rep);
if (!lastRes.mod(odd1).equals(base.modPow(exponent, odd1))) {
throw new AssertionError("ModPow() result incorrect mod odd1:" + odd1
+ "; lastRes.mod(odd1)=" + lastRes.mod(odd1) + " vs. "
+ "base.modPow(exponent, odd1)=" + base.modPow(exponent, odd1) + " base="
+ base + " exponent=" + exponent);
}
if (!lastRes.mod(odd2).equals(base.modPow(exponent, odd2))) {
throw new AssertionError("ModPow() result incorrect mod odd2");
}
}
// Test / time modInverse()
private static void repeatModInverse(int len, int rep) {
BigInteger odd1 = BigInteger.TEN.pow(len / 2).add(BigInteger.ONE);
BigInteger odd2 = BigInteger.TEN.pow(len / 2).add(BigInteger.valueOf(17));
BigInteger product = odd1.multiply(odd2);
BigInteger arg = BigInteger.ONE.shiftLeft(len / 4);
long startTime = getStartTime();
BigInteger lastRes = null;
for (int i = 0; i < rep; ++i) {
BigInteger newRes = arg.modInverse(product);
if (i != 0 && !newRes.equals(lastRes)) {
System.out.println(newRes + " != " + lastRes);
}
lastRes = newRes;
}
printTime("ModInverse() at decimal length " + len + " took ", startTime, rep);
if (!lastRes.mod(odd1).equals(arg.modInverse(odd1))) {
throw new AssertionError("ModInverse() result incorrect mod odd1");
}
if (!lastRes.mod(odd2).equals(arg.modInverse(odd2))) {
throw new AssertionError("ModInverse() result incorrect mod odd2");
}
}
public static void main(String[] args) throws Exception {
for (int i = 10; i <= 10_000; i *= 10) {
repeatInner(1000, i, PRINT_TIMES ? Math.min(20_000 / i, 3_000) : 2);
}
for (int i = 5; i <= 5_000; i *= 10) {
repeatHarmonic1000(i, PRINT_TIMES ? Math.min(20_000 / i, 3_000) : 2);
}
for (int i = 5; i <= 5_000; i *= 10) {
repeatToString(i, PRINT_TIMES ? Math.min(20_000 / i, 3_000) : 2);
}
for (int i = 10; i <= 10_000; i *= 10) {
repeatEApprox(100_000, i, PRINT_TIMES ? 50_000 / i : 2);
}
for (int i = 5; i <= 5_000; i *= 10) {
repeatModPow(i, PRINT_TIMES ? 10_000 / i : 2);
}
for (int i = 10; i <= 10_000; i *= 10) {
repeatModInverse(i, PRINT_TIMES ? 20_000 / i : 2);
}
}
}