• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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