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