1 /* 2 * Copyright (C) 2020 The Android Open Source Project 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 android.libcore; 18 19 import android.perftests.utils.BenchmarkState; 20 import android.perftests.utils.PerfStatusReporter; 21 22 import androidx.test.filters.LargeTest; 23 import androidx.test.runner.AndroidJUnit4; 24 25 import org.junit.Rule; 26 import org.junit.Test; 27 import org.junit.runner.RunWith; 28 29 import java.math.BigInteger; 30 31 /** 32 * Tries to measure important BigInteger operations across a variety of BigInteger sizes. Note that 33 * BigInteger implementations commonly need to use wildly different algorithms for different sizes, 34 * so relative performance may change substantially depending on the size of the integer. This is 35 * not structured as a proper benchmark; just run main(), e.g. with vogar 36 * libcore/benchmarks/src/benchmarks/BigIntegerBenchmark.java. 37 */ 38 @RunWith(AndroidJUnit4.class) 39 @LargeTest 40 public class BigIntegerPerfTest { 41 @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); 42 43 // A simple sum of products computation, mostly so we can check timing in the 44 // absence of any division. Computes the sum from 1 to n of ((10^prec) << 30) + 1)^2, 45 // repeating the multiplication, but not addition of 1, each time through the loop. 46 // Check the last few bits of the result as we go. Assumes n < 2^30. 47 // Note that we're actually squaring values in computing the product. 48 // That affects the algorithm used by some implementations. inner(int n, int prec)49 private static void inner(int n, int prec) { 50 BigInteger big = BigInteger.TEN.pow(prec).shiftLeft(30).add(BigInteger.ONE); 51 BigInteger sum = BigInteger.ZERO; 52 for (int i = 0; i < n; ++i) { 53 sum = sum.add(big.multiply(big)); 54 } 55 if (sum.and(BigInteger.valueOf(0x3fffffff)).intValue() != n) { 56 throw new AssertionError( 57 "inner() got " + sum.and(BigInteger.valueOf(0x3fffffff)) + " instead of " + n); 58 } 59 } 60 61 // Execute the above rep times, optionally timing it. 62 @Test repeatInner()63 public void repeatInner() { 64 BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); 65 while (state.keepRunning()) { 66 for (int i = 10; i <= 10_000; i *= 10) { 67 inner(100, i); 68 } 69 } 70 } 71 72 // Approximate the sum of the first 1000 terms of the harmonic series (sum of 1/m as m 73 // goes from 1 to n) to about prec digits. The result has an implicit decimal point 74 // prec digits from the right. harmonic1000(int prec)75 private static BigInteger harmonic1000(int prec) { 76 BigInteger scaledOne = BigInteger.TEN.pow(prec); 77 BigInteger sum = BigInteger.ZERO; 78 for (int i = 1; i <= 1000; ++i) { 79 sum = sum.add(scaledOne.divide(BigInteger.valueOf(i))); 80 } 81 return sum; 82 } 83 84 // Execute the above rep times, optionally timing it. 85 // Check results for equality, and print one, to compaare against reference. 86 @Test repeatHarmonic1000()87 public void repeatHarmonic1000() { 88 BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); 89 while (state.keepRunning()) { 90 for (int i = 5; i <= 5_000; i *= 10) { 91 BigInteger refRes = harmonic1000(i); 92 BigInteger newRes = harmonic1000(i); 93 if (!newRes.equals(refRes)) { 94 throw new AssertionError(newRes + " != " + refRes); 95 } 96 if (i >= 50 97 && !refRes.toString() 98 .startsWith("748547086055034491265651820433390017652167916970")) { 99 throw new AssertionError("harmanic(" + i + ") incorrectly produced " + refRes); 100 } 101 } 102 } 103 } 104 105 // Repeatedly execute just the base conversion from the last test, allowing 106 // us to time and check it for consistency as well. 107 @Test repeatToString()108 public void repeatToString() { 109 BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); 110 while (state.keepRunning()) { 111 for (int i = 5; i <= 5_000; i *= 10) { 112 BigInteger refRes = harmonic1000(i); 113 String refString = refRes.toString(); 114 // Disguise refRes to avoid compiler optimization issues. 115 BigInteger newRes = refRes.shiftLeft(30).add(BigInteger.valueOf(i)).shiftRight(30); 116 // The time-consuming part: 117 String newString = newRes.toString(); 118 } 119 } 120 } 121 122 // Compute base^exp, where base and result are scaled/multiplied by scaleBy to make them 123 // integers. exp >= 0 . myPow(BigInteger base, int exp, BigInteger scaleBy)124 private static BigInteger myPow(BigInteger base, int exp, BigInteger scaleBy) { 125 if (exp == 0) { 126 return scaleBy; // Return one. 127 } else if ((exp & 1) != 0) { 128 BigInteger tmp = myPow(base, exp - 1, scaleBy); 129 return tmp.multiply(base).divide(scaleBy); 130 } else { 131 BigInteger tmp = myPow(base, exp / 2, scaleBy); 132 return tmp.multiply(tmp).divide(scaleBy); 133 } 134 } 135 136 // Approximate e by computing (1 + 1/n)^n to prec decimal digits. 137 // This isn't necessarily a very good approximation to e. 138 // Return the result, scaled by 10^prec. eApprox(int n, int prec)139 private static BigInteger eApprox(int n, int prec) { 140 BigInteger scaledOne = BigInteger.TEN.pow(prec); 141 BigInteger base = scaledOne.add(scaledOne.divide(BigInteger.valueOf(n))); 142 return myPow(base, n, scaledOne); 143 } 144 145 // Repeatedly execute and check the above, printing one of the results 146 // to compare to reference. 147 @Test repeatEApprox()148 public void repeatEApprox() { 149 BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); 150 while (state.keepRunning()) { 151 for (int i = 10; i <= 10_000; i *= 10) { 152 BigInteger refRes = eApprox(100_000, i); 153 BigInteger newRes = eApprox(100_000, i); 154 if (!newRes.equals(refRes)) { 155 throw new AssertionError(newRes + " != " + refRes); 156 } 157 if (i >= 10 && !refRes.toString().startsWith("271826")) { 158 throw new AssertionError( 159 "eApprox(" + 100_000 + "," + i + ") incorrectly produced " + refRes); 160 } 161 } 162 } 163 } 164 165 // Test / time modPow() 166 @Test repeatModPow()167 public void repeatModPow() { 168 BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); 169 while (state.keepRunning()) { 170 for (int i = 5; i <= 500; i *= 10) { 171 BigInteger odd1 = BigInteger.TEN.pow(i / 2).add(BigInteger.ONE); 172 BigInteger odd2 = BigInteger.TEN.pow(i / 2).add(BigInteger.valueOf(17)); 173 BigInteger product = odd1.multiply(odd2); 174 BigInteger exponent = BigInteger.TEN.pow(i / 2 - 1); 175 BigInteger base = BigInteger.TEN.pow(i / 4); 176 BigInteger newRes = base.modPow(exponent, product); 177 if (!newRes.mod(odd1).equals(base.modPow(exponent, odd1))) { 178 throw new AssertionError( 179 "ModPow() result incorrect mod odd1:" 180 + odd1 181 + "; lastRes.mod(odd1)=" 182 + newRes.mod(odd1) 183 + " vs. " 184 + "base.modPow(exponent, odd1)=" 185 + base.modPow(exponent, odd1) 186 + " base=" 187 + base 188 + " exponent=" 189 + exponent); 190 } 191 if (!newRes.mod(odd2).equals(base.modPow(exponent, odd2))) { 192 throw new AssertionError("ModPow() result incorrect mod odd2"); 193 } 194 } 195 } 196 } 197 198 // Test / time modInverse() 199 @Test repeatModInverse()200 public void repeatModInverse() { 201 BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); 202 while (state.keepRunning()) { 203 for (int i = 10; i <= 10_000; i *= 10) { 204 BigInteger odd1 = BigInteger.TEN.pow(i / 2).add(BigInteger.ONE); 205 BigInteger odd2 = BigInteger.TEN.pow(i / 2).add(BigInteger.valueOf(17)); 206 BigInteger product = odd1.multiply(odd2); 207 BigInteger arg = BigInteger.ONE.shiftLeft(i / 4); 208 BigInteger lastRes = null; 209 BigInteger newRes = arg.modInverse(product); 210 lastRes = newRes; 211 if (!lastRes.mod(odd1).equals(arg.modInverse(odd1))) { 212 throw new AssertionError("ModInverse() result incorrect mod odd1"); 213 } 214 if (!lastRes.mod(odd2).equals(arg.modInverse(odd2))) { 215 throw new AssertionError("ModInverse() result incorrect mod odd2"); 216 } 217 } 218 } 219 } 220 } 221