• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.perftests.utils;
18 
19 import android.app.Activity;
20 import android.app.Instrumentation;
21 import android.os.Bundle;
22 import android.os.Debug;
23 import android.os.Trace;
24 import android.util.Log;
25 
26 import androidx.test.InstrumentationRegistry;
27 
28 import java.io.File;
29 import java.util.ArrayList;
30 import java.util.concurrent.TimeUnit;
31 
32 /**
33  * Provides a benchmark framework.
34  *
35  * Example usage:
36  * // Executes the code while keepRunning returning true.
37  *
38  * public void sampleMethod() {
39  *     BenchmarkState state = new BenchmarkState();
40  *
41  *     int[] src = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
42  *     while (state.keepRunning()) {
43  *         int[] dest = new int[src.length];
44  *         System.arraycopy(src, 0, dest, 0, src.length);
45  *     }
46  *     System.out.println(state.summaryLine());
47  * }
48  */
49 public final class BenchmarkState {
50 
51     private static final String TAG = "BenchmarkState";
52     private static final boolean ENABLE_PROFILING = false;
53 
54     private static final int NOT_STARTED = 0;  // The benchmark has not started yet.
55     private static final int WARMUP = 1; // The benchmark is warming up.
56     private static final int RUNNING = 2;  // The benchmark is running.
57     private static final int RUNNING_CUSTOMIZED = 3;  // Running for customized measurement.
58     private static final int FINISHED = 4;  // The benchmark has stopped.
59 
60     private int mState = NOT_STARTED;  // Current benchmark state.
61 
62     private static final long WARMUP_DURATION_NS = ms2ns(250); // warm-up for at least 250ms
63     private static final int WARMUP_MIN_ITERATIONS = 16; // minimum iterations to warm-up for
64 
65     // TODO: Tune these values.
66     private static final long TARGET_TEST_DURATION_NS = ms2ns(500); // target testing for 500 ms
67     private static final int MAX_TEST_ITERATIONS = 1000000;
68     private static final int MIN_TEST_ITERATIONS = 10;
69     private static final int REPEAT_COUNT = 5;
70 
71     private long mStartTimeNs = 0;  // Previously captured System.nanoTime().
72     private boolean mPaused;
73     private long mPausedTimeNs = 0; // The System.nanoTime() when the pauseTiming() is called.
74     private long mPausedDurationNs = 0;  // The duration of paused state in nano sec.
75 
76     private int mIteration = 0;
77     private int mMaxIterations = 0;
78 
79     private int mRepeatCount = 0;
80 
81     /**
82      * Additional iteration that used to apply customized measurement. The result during these
83      * iterations won't be counted into {@link #mStats}.
84      */
85     private int mMaxCustomizedIterations;
86     private int mCustomizedIterations;
87     private CustomizedIterationListener mCustomizedIterationListener;
88 
89     // Statistics. These values will be filled when the benchmark has finished.
90     // The computation needs double precision, but long int is fine for final reporting.
91     private Stats mStats;
92 
93     // Individual duration in nano seconds.
94     private ArrayList<Long> mResults = new ArrayList<>();
95 
ms2ns(long ms)96     private static final long ms2ns(long ms) {
97         return TimeUnit.MILLISECONDS.toNanos(ms);
98     }
99 
100     // Stops the benchmark timer.
101     // This method can be called only when the timer is running.
pauseTiming()102     public void pauseTiming() {
103         if (mPaused) {
104             throw new IllegalStateException(
105                     "Unable to pause the benchmark. The benchmark has already paused.");
106         }
107         mPausedTimeNs = System.nanoTime();
108         mPaused = true;
109     }
110 
111     // Starts the benchmark timer.
112     // This method can be called only when the timer is stopped.
resumeTiming()113     public void resumeTiming() {
114         if (!mPaused) {
115             throw new IllegalStateException(
116                     "Unable to resume the benchmark. The benchmark is already running.");
117         }
118         mPausedDurationNs += System.nanoTime() - mPausedTimeNs;
119         mPausedTimeNs = 0;
120         mPaused = false;
121     }
122 
123     /**
124      * This is used to run the benchmark with more information by enabling some debug mechanism but
125      * we don't want to account the special runs (slower) in the stats report.
126      */
setCustomizedIterations(int iterations, CustomizedIterationListener listener)127     public void setCustomizedIterations(int iterations, CustomizedIterationListener listener) {
128         mMaxCustomizedIterations = iterations;
129         mCustomizedIterationListener = listener;
130     }
131 
beginWarmup()132     private void beginWarmup() {
133         Trace.beginSection("Warmup");
134         mStartTimeNs = System.nanoTime();
135         mIteration = 0;
136         mState = WARMUP;
137     }
138 
endWarmup()139     private void endWarmup() {
140         Trace.endSection();
141     }
142 
beginBenchmark(long warmupDuration, int iterations)143     private void beginBenchmark(long warmupDuration, int iterations) {
144         if (ENABLE_PROFILING) {
145             File f = new File(InstrumentationRegistry.getContext().getDataDir(), "benchprof");
146             Log.d(TAG, "Tracing to: " + f.getAbsolutePath());
147             Debug.startMethodTracingSampling(f.getAbsolutePath(), 16 * 1024 * 1024, 100);
148         }
149         Trace.beginSection("Benchmark");
150         mMaxIterations = (int) (TARGET_TEST_DURATION_NS / (warmupDuration / iterations));
151         mMaxIterations = Math.min(MAX_TEST_ITERATIONS,
152                 Math.max(mMaxIterations, MIN_TEST_ITERATIONS));
153         mPausedDurationNs = 0;
154         mIteration = 0;
155         mRepeatCount = 0;
156         mState = RUNNING;
157         mStartTimeNs = System.nanoTime();
158     }
159 
endBenchmark()160     private void endBenchmark() {
161         Trace.endSection();
162     }
163 
startNextTestRun()164     private boolean startNextTestRun() {
165         final long currentTime = System.nanoTime();
166         mResults.add((currentTime - mStartTimeNs - mPausedDurationNs) / mMaxIterations);
167         mRepeatCount++;
168         if (mRepeatCount >= REPEAT_COUNT) {
169             if (ENABLE_PROFILING) {
170                 Debug.stopMethodTracing();
171             }
172             mStats = new Stats(mResults);
173             if (mMaxCustomizedIterations > 0 && mCustomizedIterationListener != null) {
174                 mState = RUNNING_CUSTOMIZED;
175                 mCustomizedIterationListener.onStart(mCustomizedIterations);
176                 return true;
177             }
178             mState = FINISHED;
179             endBenchmark();
180             return false;
181         }
182         mPausedDurationNs = 0;
183         mIteration = 0;
184         mStartTimeNs = System.nanoTime();
185         return true;
186     }
187 
188     /**
189      * Judges whether the benchmark needs more samples.
190      *
191      * For the usage, see class comment.
192      */
keepRunning()193     public boolean keepRunning() {
194         switch (mState) {
195             case NOT_STARTED:
196                 beginWarmup();
197                 return true;
198             case WARMUP:
199                 mIteration++;
200                 // Only check nanoTime on every iteration in WARMUP since we
201                 // don't yet have a target iteration count.
202                 final long duration = System.nanoTime() - mStartTimeNs;
203                 if (mIteration >= WARMUP_MIN_ITERATIONS && duration >= WARMUP_DURATION_NS) {
204                     endWarmup();
205                     beginBenchmark(duration, mIteration);
206                 }
207                 return true;
208             case RUNNING:
209                 mIteration++;
210                 if (mIteration >= mMaxIterations) {
211                     return startNextTestRun();
212                 }
213                 if (mPaused) {
214                     throw new IllegalStateException(
215                             "Benchmark step finished with paused state. " +
216                             "Resume the benchmark before finishing each step.");
217                 }
218                 return true;
219             case RUNNING_CUSTOMIZED:
220                 mCustomizedIterationListener.onFinished(mCustomizedIterations);
221                 mCustomizedIterations++;
222                 if (mCustomizedIterations >= mMaxCustomizedIterations) {
223                     mState = FINISHED;
224                     endBenchmark();
225                     return false;
226                 }
227                 mCustomizedIterationListener.onStart(mCustomizedIterations);
228                 return true;
229             case FINISHED:
230                 throw new IllegalStateException("The benchmark has finished.");
231             default:
232                 throw new IllegalStateException("The benchmark is in unknown state.");
233         }
234     }
235 
mean()236     private long mean() {
237         if (mState != FINISHED) {
238             throw new IllegalStateException("The benchmark hasn't finished");
239         }
240         return (long) mStats.getMean();
241     }
242 
median()243     private long median() {
244         if (mState != FINISHED) {
245             throw new IllegalStateException("The benchmark hasn't finished");
246         }
247         return mStats.getMedian();
248     }
249 
min()250     private long min() {
251         if (mState != FINISHED) {
252             throw new IllegalStateException("The benchmark hasn't finished");
253         }
254         return mStats.getMin();
255     }
256 
standardDeviation()257     private long standardDeviation() {
258         if (mState != FINISHED) {
259             throw new IllegalStateException("The benchmark hasn't finished");
260         }
261         return (long) mStats.getStandardDeviation();
262     }
263 
summaryLine()264     private String summaryLine() {
265         StringBuilder sb = new StringBuilder();
266         sb.append("Summary: ");
267         sb.append("median=").append(median()).append("ns, ");
268         sb.append("mean=").append(mean()).append("ns, ");
269         sb.append("min=").append(min()).append("ns, ");
270         sb.append("sigma=").append(standardDeviation()).append(", ");
271         sb.append("iteration=").append(mResults.size()).append(", ");
272         // print out the first few iterations' number for double checking.
273         int sampleNumber = Math.min(mResults.size(), 16);
274         for (int i = 0; i < sampleNumber; i++) {
275             sb.append("No ").append(i).append(" result is ").append(mResults.get(i)).append(", ");
276         }
277         return sb.toString();
278     }
279 
sendFullStatusReport(Instrumentation instrumentation, String key)280     public void sendFullStatusReport(Instrumentation instrumentation, String key) {
281         Log.i(TAG, key + summaryLine());
282         Bundle status = new Bundle();
283         status.putLong(key + "_median (ns)", median());
284         status.putLong(key + "_mean (ns)", mean());
285         status.putLong(key + "_min (ns)", min());
286         status.putLong(key + "_standardDeviation", standardDeviation());
287         instrumentation.sendStatus(Activity.RESULT_OK, status);
288     }
289 
290     /** The interface to receive the events of customized iteration. */
291     public interface CustomizedIterationListener {
292         /** The customized iteration starts. */
onStart(int iteration)293         void onStart(int iteration);
294 
295         /** The customized iteration finished. */
onFinished(int iteration)296         void onFinished(int iteration);
297     }
298 }
299