1 /* 2 * Copyright (C) 2018 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.util.Log; 23 24 import java.util.ArrayList; 25 import java.util.concurrent.TimeUnit; 26 27 /** 28 * Provides a benchmark framework. 29 * 30 * This differs from BenchmarkState in that rather than the class measuring the the elapsed time, 31 * the test passes in the elapsed time. 32 * 33 * Example usage: 34 * 35 * public void sampleMethod() { 36 * ManualBenchmarkState state = new ManualBenchmarkState(); 37 * 38 * int[] src = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 39 * long elapsedTime = 0; 40 * while (state.keepRunning(elapsedTime)) { 41 * long startTime = System.nanoTime(); 42 * int[] dest = new int[src.length]; 43 * System.arraycopy(src, 0, dest, 0, src.length); 44 * elapsedTime = System.nanoTime() - startTime; 45 * } 46 * System.out.println(state.summaryLine()); 47 * } 48 * 49 * Or use the PerfManualStatusReporter TestRule. 50 * 51 * Make sure that the overhead of checking the clock does not noticeably affect the results. 52 */ 53 public final class ManualBenchmarkState { 54 private static final String TAG = ManualBenchmarkState.class.getSimpleName(); 55 56 // TODO: Tune these values. 57 // warm-up for duration 58 private static final long WARMUP_DURATION_NS = TimeUnit.SECONDS.toNanos(5); 59 // minimum iterations to warm-up for 60 private static final int WARMUP_MIN_ITERATIONS = 8; 61 62 // target testing for duration 63 private static final long TARGET_TEST_DURATION_NS = TimeUnit.SECONDS.toNanos(16); 64 private static final int MAX_TEST_ITERATIONS = 1000000; 65 private static final int MIN_TEST_ITERATIONS = 10; 66 67 private static final int NOT_STARTED = 0; // The benchmark has not started yet. 68 private static final int WARMUP = 1; // The benchmark is warming up. 69 private static final int RUNNING = 2; // The benchmark is running. 70 private static final int FINISHED = 3; // The benchmark has stopped. 71 72 private int mState = NOT_STARTED; // Current benchmark state. 73 74 private long mWarmupStartTime = 0; 75 private int mWarmupIterations = 0; 76 77 private int mMaxIterations = 0; 78 79 // Individual duration in nano seconds. 80 private ArrayList<Long> mResults = new ArrayList<>(); 81 82 // Statistics. These values will be filled when the benchmark has finished. 83 // The computation needs double precision, but long int is fine for final reporting. 84 private Stats mStats; 85 beginBenchmark(long warmupDuration, int iterations)86 private void beginBenchmark(long warmupDuration, int iterations) { 87 mMaxIterations = (int) (TARGET_TEST_DURATION_NS / (warmupDuration / iterations)); 88 mMaxIterations = Math.min(MAX_TEST_ITERATIONS, 89 Math.max(mMaxIterations, MIN_TEST_ITERATIONS)); 90 mState = RUNNING; 91 } 92 93 /** 94 * Judges whether the benchmark needs more samples. 95 * 96 * For the usage, see class comment. 97 */ keepRunning(long duration)98 public boolean keepRunning(long duration) { 99 if (duration < 0) { 100 throw new RuntimeException("duration is negative: " + duration); 101 } 102 switch (mState) { 103 case NOT_STARTED: 104 mState = WARMUP; 105 mWarmupStartTime = System.nanoTime(); 106 return true; 107 case WARMUP: { 108 final long timeSinceStartingWarmup = System.nanoTime() - mWarmupStartTime; 109 ++mWarmupIterations; 110 if (mWarmupIterations >= WARMUP_MIN_ITERATIONS 111 && timeSinceStartingWarmup >= WARMUP_DURATION_NS) { 112 beginBenchmark(timeSinceStartingWarmup, mWarmupIterations); 113 } 114 return true; 115 } 116 case RUNNING: { 117 mResults.add(duration); 118 final boolean keepRunning = mResults.size() < mMaxIterations; 119 if (!keepRunning) { 120 mStats = new Stats(mResults); 121 mState = FINISHED; 122 } 123 return keepRunning; 124 } 125 case FINISHED: 126 throw new IllegalStateException("The benchmark has finished."); 127 default: 128 throw new IllegalStateException("The benchmark is in an unknown state."); 129 } 130 } 131 132 private String summaryLine() { 133 final StringBuilder sb = new StringBuilder(); 134 sb.append("Summary: "); 135 sb.append("median=").append(mStats.getMedian()).append("ns, "); 136 sb.append("mean=").append(mStats.getMean()).append("ns, "); 137 sb.append("min=").append(mStats.getMin()).append("ns, "); 138 sb.append("max=").append(mStats.getMax()).append("ns, "); 139 sb.append("sigma=").append(mStats.getStandardDeviation()).append(", "); 140 sb.append("iteration=").append(mResults.size()).append(", "); 141 sb.append("values=").append(mResults.toString()); 142 return sb.toString(); 143 } 144 145 public void sendFullStatusReport(Instrumentation instrumentation, String key) { 146 if (mState != FINISHED) { 147 throw new IllegalStateException("The benchmark hasn't finished"); 148 } 149 Log.i(TAG, key + summaryLine()); 150 final Bundle status = new Bundle(); 151 status.putLong(key + "_median", mStats.getMedian()); 152 status.putLong(key + "_mean", (long) mStats.getMean()); 153 status.putLong(key + "_percentile90", mStats.getPercentile90()); 154 status.putLong(key + "_percentile95", mStats.getPercentile95()); 155 status.putLong(key + "_stddev", (long) mStats.getStandardDeviation()); 156 instrumentation.sendStatus(Activity.RESULT_OK, status); 157 } 158 } 159 160