aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk10
-rw-r--r--java/README.txt175
-rw-r--r--java/src/test/java/com/google/protobuf/PerfTimer.java832
3 files changed, 1014 insertions, 3 deletions
diff --git a/Android.mk b/Android.mk
index 92857446..fb2d964b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 The Android Open Source Project
+# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -11,8 +11,14 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+#
+#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-# TODO: Add code to build the javamicro library
+LOCAL_SRC_FILES := $(call all-java-files-under, java/src/main/java/com/google/protobuf/micro)
+
+LOCAL_MODULE := com.google.protobuf.micro
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/java/README.txt b/java/README.txt
index 3ed06a1e..1656e594 100644
--- a/java/README.txt
+++ b/java/README.txt
@@ -82,11 +82,184 @@ running unit tests.
$ protoc --java_out=src/main/java -I../src \
../src/google/protobuf/descriptor.proto
-
3) Compile the code in src/main/java using whatever means you prefer.
4) Install the classes wherever you prefer.
+Micro version
+============================
+
+The runtime and generated code for MICRO_RUNTIME is smaller
+because it does not include support for the descriptor,
+reflection or extensions. Also, not currently supported
+are packed repeated elements nor testing of java_multiple_files.
+
+To create a jar file for the runtime and run tests invoke
+"mvn package -P micro" from the <protobuf-root>/java
+directory. The generated jar file is
+<protobuf-root>java/target/protobuf-java-2.2.0-micro.jar.
+
+If you wish to compile the MICRO_RUTIME your self, place
+the 7 files below, in <root>/com/google/protobuf and
+create a jar file for use with your code and the generated
+code:
+
+ByteStringMicro.java
+CodedInputStreamMicro.java
+CodedOutputStreamMicro.java
+InvalidProtocolBufferException.java
+MessageMicro.java
+StringUtf8Micro.java
+WireFormatMicro.java
+
+If you wish to change on the code generator it is located
+in /src/google/protobuf/compiler/javamicro.
+
+To generate code for the MICRO_RUNTIME invoke protoc with
+--javamicro_out command line parameter. javamciro_out takes
+a series of optional sub-parameters separated by comma's
+and a final parameter, with a colon separator, which defines
+the source directory. Sub-paraemeters begin with a name
+followed by an equal and if that sub-parameter has multiple
+parameters they are seperated by "|". The command line options
+are:
+
+opt -> speed or space
+java_use_vector -> true or false
+java_package -> <file-name>|<package-name>
+java_outer_classname -> <file-name>|<package-name>
+
+opt:
+ This change the code generation to optimize for speed,
+ opt=speed, or space, opt=space. When opt=speed this
+ changes the code generation for strings to use
+ StringUtf8Micro which eliminates multiple conversions
+ of the string to utf8. The default value is opt=space.
+
+java_use_vector:
+ Is a boolean flag either java_use_vector=true or
+ java_use_vector=false. When java_use_vector=true the
+ code generated for repeated elements uses
+ java.util.Vector and when java_use_vector=false the
+ java.util.ArrayList<> is used. When java.util.Vector
+ is used the code must be compiled with Java 1.3 and
+ when ArrayList is used Java 1.5 or above must be used.
+ The using javac the source parameter maybe used to
+ control the version of the srouce: "javac -source 1.3".
+ You can also change the <source> xml element for the
+ maven-compiler-plugin. Below is for 1.5 sources:
+
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+
+ When compiling for 1.5 java_use_vector=false or not
+ present where the default value is false.
+
+ And below would be for 1.3 sources note when changing
+ to 1.3 you must also set java_use_vector=true:
+
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.3</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+
+java_package:
+ The allows setting/overriding the java_package option
+ and associates allows a package name for a file to
+ be specified on the command line. Overriding any
+ "option java_package xxxx" in the file. The default
+ if not present is to use the value from the package
+ statment or "option java_package xxxx" in the file.
+
+java_outer_classname:
+ This allows the setting/overriding of the outer
+ class name option and associates a class name
+ to a file. An outer class name is required and
+ must be specified if there are multiple messages
+ in a single proto file either in the proto source
+ file or on the command line. If not present the
+ no outer class name will be used.
+
+Below are a series of examples for clarification of the
+various javamicro_out parameters using
+src/test/proto/simple-data.proto:
+
+package testprotobuf;
+
+message SimpleData {
+ optional fixed64 id = 1;
+ optional string description = 2;
+ optional bool ok = 3 [default = false];
+};
+
+
+Assuming you've only compiled and not installed protoc and
+your current working directory java/, then a simple
+command line to compile simple-data would be:
+
+../src/protoc --javamicro_out=. src/test/proto/simple-data.proto
+
+This will create testprotobuf/SimpleData.java
+
+The directory testprotobuf is created because on line 1
+of simple-data.proto is "package testprotobuf;". If you
+wanted a different package name you could use the
+java_package option command line sub-parameter:
+
+../src/protoc '--javamicro_out=java_package=src/test/proto/simple-data.proto|my_package:.' src/test/proto/simple-data.proto
+
+Here you see the new java_package sub-parameter which
+itself needs two parameters the file name and the
+package name, these are separated by "|". Now you'll
+find my_package/SimpleData.java.
+
+If you wanted to also change the optimization for
+speed you'd add opt=speed with the comma seperator
+as follows:
+
+../src/protoc '--javamicro_out=opt=speed,java_package=src/test/proto/simple-data.proto|my_package:.' src/test/proto/simple-data.proto
+
+Finally if you also wanted an outer class name you'd
+do the following:
+
+../src/protoc '--javamicro_out=opt=speed,java_package=src/test/proto/simple-data.proto|my_package,java_outer_classname=src/test/proto/simple-data.proto|OuterName:.' src/test/proto/simple-data.proto
+
+Now you'll find my_packate/OuterName.java.
+
+As mentioned java_package and java_outer_classname
+may also be specified in the file. In the example
+below we must define java_outer_classname because
+there are multiple messages in
+src/test/proto/two-messages.proto
+
+package testmicroruntime;
+
+option java_package = "com.example";
+option java_outer_classname = "TestMessages";
+
+message TestMessage1 {
+ required int32 id = 1;
+}
+
+message TestMessage2 {
+ required int32 id = 1;
+}
+
+This could be compiled using:
+
+../src/protoc --javamicro_out=. src/test/proto/two-message.proto
+
+With the result will be com/example/TestMessages.java
+
+
Usage
=====
diff --git a/java/src/test/java/com/google/protobuf/PerfTimer.java b/java/src/test/java/com/google/protobuf/PerfTimer.java
new file mode 100644
index 00000000..d6df4ffb
--- /dev/null
+++ b/java/src/test/java/com/google/protobuf/PerfTimer.java
@@ -0,0 +1,832 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.protobuf;
+
+import java.util.Arrays;
+
+/**
+ * A Performance Timing class that can be used to estimate the amount of time a
+ * sequence of code takes. The typical code sequence would be as follows:</p>
+ * <code>
+ PerfTimer pt = new PerfTimer();
+ pt.calibrate();
+ pt.timeEachAutomatically(new Runnable() = {
+ public void run() {
+ // Add code to time
+ }
+ });
+ System.out.printf("time per loop=" + pt);
+
+ * The calibrate method determines the overhead of timing the run() method and
+ * the number of times to call the run() method to have approximately 1% precision
+ * for timing. The method pt.stats() method will return a string containing some
+ * statistics tpl, il, ol, min, max, mean, median, stddev and total.
+ *
+ * tpl ::= Timer per loop
+ * min ::= minimum time one call to run() took
+ * stddev ::= Standard deviation of the collected times
+ * mean ::= the average time to call run()
+ * median ::= 1/2 the times were > than this time and 1/2 were less.
+ * total ::= Sum of the times collected.
+ * il ::= innerLoops; the number of times run() between each call to start/stop
+ * ol ::= outerLoops, the number of times start/stop was called
+ *
+ * You can also use start/stop/restart to do simple timing:
+ *
+ * pt.start();
+ * a += 1;
+ * pt.stop();
+ * pt.log("time=" + pt);
+ * pt.restart();
+ * doSomething();
+ * pt.stop();
+ * System.out.printf("time=" + pt);
+ * </code>
+ *
+ * @author wink@google.com (Wink Saville)
+ */
+public class PerfTimer {
+ /** No debug */
+ public static final int DEBUG_LEVEL_NONE = 0;
+
+ /** Some debug */
+ public static final int DEBUG_LEVEL_SOME = 1;
+
+ /** All debug */
+ public static final int DEBUG_LEVEL_ALL = 2;
+
+ /** Timer ticks per microsecond */
+ private static final double TICKS_PER_MICROSECOND = 1000.0;
+
+ /** Random number generator */
+ java.util.Random rng = new java.util.Random();
+
+ /** get ticks */
+ private static long getTicks() {
+ return System.nanoTime();
+ }
+
+ /** Debug logging */
+ private static void log(String s) {
+ System.out.printf(String.format("[PerfTimer] %s\n", s));
+ }
+
+ /** Outer loops for timeEachAutomatically */
+ private static final int OUTER_LOOPS = 100;
+
+ /** Thrown if an error occurs while timing */
+ public static class PerfTimerException extends RuntimeException {
+ }
+
+ /**
+ * Calibration record
+ */
+ public static class CalibrationRec {
+ /** Runnable overhead */
+ public double mRunnableOverheadInMicros = 0.0;
+
+ /** Minimum Threshold value for timeEachAutomaticaly */
+ public double mMinThresholdInMicros = 3000.0;
+
+ /** Maximum Threshold value for timeEachAutomaticaly */
+ public double mMaxThresholdInMicros = 6000.0;
+
+ /** Desired precision in decimal digits */
+ public double mPrecisionInDecimalDigits = 2.0;
+
+ /**
+ * Default number of retries if the standard deviation ratio is too
+ * large
+ */
+ public final int mStdDevRetrys = 5;
+
+ /** Default maximum standard deviation radio */
+ public final double mMaxStdDevRatio = 0.15;
+
+ /** Number of votes looking for smallest time per loop */
+ public final int mVotes = 3;
+
+ /** Convert to string */
+ @Override
+ public String toString() {
+ return String
+ .format(
+ "oh=%.6fus minT=%.6fus maxT=%.6fus prc=%,.3f stdDevRetrys=%d maxStdDevRatio=%.2f votes=%d",
+ mRunnableOverheadInMicros, mMinThresholdInMicros,
+ mMaxThresholdInMicros, mPrecisionInDecimalDigits, mStdDevRetrys,
+ mMaxStdDevRatio, mVotes);
+ }
+ }
+
+ /**
+ * Calibration record
+ */
+ private CalibrationRec mCr;
+
+ /**
+ * Statistics calculated on the timing data.
+ */
+ public static class Stats {
+ /** Number of outer loops */
+ private int mOuterLoops;
+
+ /** Number of inner loops */
+ private int mInnerLoops;
+
+ /** Minimum time in times array */
+ private long mMin;
+
+ /** Maximum time in times array */
+ private long mMax;
+
+ /** Median value in times array */
+ private double mMedian;
+
+ /** The mean (average) of the values in times array */
+ private double mMean;
+
+ /** The standard deviation of the values in times array */
+ private double mStdDev;
+
+ private int mStdDevTooLargeCount;
+
+ /** Sum of the times in the times array */
+ private double mTotal;
+
+ /** Initialize */
+ public void init() {
+ mInnerLoops = 1;
+ mOuterLoops = 1;
+ mMin = 0;
+ mMax = 0;
+ mMedian = 0;
+ mMean = 0;
+ mStdDev = 0;
+ mStdDevTooLargeCount = 0;
+ mTotal = 0;
+ }
+
+ /** Constructor */
+ public Stats() {
+ init();
+ }
+
+ /** Set number of inner loops */
+ public void setInnerLoops(int loops) {
+ mInnerLoops = loops;
+ }
+
+ /** Get number of inner loops */
+ public int getInnerLoops() {
+ return mInnerLoops;
+ }
+
+ /** Set number of inner loops */
+ public void setOuterLoops(int loops) {
+ mOuterLoops = loops;
+ }
+
+ /** Get number of inner loops */
+ public int getOuterLoops() {
+ return mOuterLoops;
+ }
+
+ /**
+ * Minimum value of collected data in microseconds, valid after analyze.
+ */
+ public double getMinInMicros() {
+ return mMin / TICKS_PER_MICROSECOND;
+ }
+
+ /**
+ * Maximum value of collected data in microseconds, valid after analyze.
+ */
+ public double getMaxInMicros() {
+ return mMax / TICKS_PER_MICROSECOND;
+ }
+
+ /**
+ * Sum of the values of collected data in microseconds, valid after
+ * analyze.
+ */
+ public double getTotalInMicros() {
+ return mTotal / TICKS_PER_MICROSECOND;
+ }
+
+ /** Sum of the values of collected data in seconds, valid after analyze. */
+ public double getTotalInSecs() {
+ return mTotal / (TICKS_PER_MICROSECOND * 1000000.0);
+ }
+
+ /** Sum of the values of collected data in seconds, valid after analyze. */
+ public double getMeanInMicros() {
+ return mMean / TICKS_PER_MICROSECOND;
+ }
+
+ /** Median value of collected data in microseconds, valid after analyze. */
+ public double getMedianInMicros() {
+ return mMedian / TICKS_PER_MICROSECOND;
+ }
+
+ /**
+ * Standard deviation of collected data in microseconds, valid after
+ * analyze.
+ */
+ public double getStdDevInMicros() {
+ return mStdDev / TICKS_PER_MICROSECOND;
+ }
+
+ public double getStdDevRatio() {
+ return mStdDev / mMin;
+ }
+
+ /** Return true if (mStdDev / mMin) <= maxStdDevRation */
+ public boolean stdDevOk(double maxStdDevRatio) {
+ return getStdDevRatio() <= maxStdDevRatio;
+ }
+
+ /** Increment StdDevTooLargeCount */
+ public void incStdDevTooLargeCount() {
+ mStdDevTooLargeCount += 1;
+ }
+
+ /** Return number of times stdDev was not ok */
+ public int getStdDevTooLargeCount() {
+ return mStdDevTooLargeCount;
+ }
+
+ /** Return time per loop */
+ public double getTimePerLoop() {
+ return mMin / TICKS_PER_MICROSECOND / mInnerLoops;
+ }
+
+ /**
+ * Calculate the stats for the data. Note the data in the range will be
+ * sorted.
+ *
+ * @param data
+ * @param count
+ */
+ public Stats calculate(long data[], int count) {
+ if (count == 1) {
+ mMin = mMax = data[0];
+ mTotal = mMedian = mMean = data[0];
+ mStdDev = 0;
+ } else if (count > 1) {
+ Arrays.sort(data, 0, count);
+ mMin = data[0];
+ mMax = data[count - 1];
+ if ((count & 1) == 1) {
+ mMedian = data[((count + 1) / 2) - 1];
+ } else {
+ mMedian = (data[count / 2] + data[(count / 2) - 1]) / 2;
+ }
+ mTotal = 0;
+ double sumSquares = 0;
+ for (int i = 0; i < count; i++) {
+ long t = data[i];
+ mTotal += t;
+ sumSquares += t * t;
+ }
+ mMean = mTotal / count;
+ double variance = (sumSquares / count) - (mMean * mMean);
+ mStdDev = Math.pow(variance, 0.5);
+ } else {
+ init();
+ }
+ return this;
+ }
+
+ /** Convert to string */
+ @Override
+ public String toString() {
+ double timePerLoop = getTimePerLoop();
+ double stdDevPerLoop = mStdDev / TICKS_PER_MICROSECOND / mInnerLoops;
+ return String.format(
+ "tpl=%,.6fus stdDev=%,.6fus tpl/stdDev=%.2fpercent min=%,.6fus median=%,.6fus mean=%,.6fus max=%,.6fus total=%,.6fs il=%d, ol=%d tlc=%d",
+ timePerLoop, stdDevPerLoop, (stdDevPerLoop / timePerLoop) * 100, mMin
+ / TICKS_PER_MICROSECOND, mMedian / TICKS_PER_MICROSECOND, mMean
+ / TICKS_PER_MICROSECOND, mMax / TICKS_PER_MICROSECOND, mTotal
+ / (TICKS_PER_MICROSECOND * 1000000.0), mInnerLoops, mOuterLoops, mStdDevTooLargeCount);
+ }
+ }
+
+ /** Statistics */
+ private Stats mStats = new Stats();
+
+ /** Statistics of the clock precision */
+ private Stats mClockStats;
+
+ /** Number of items in times array */
+ private int mCount;
+
+ /** Array of stop - start times */
+ private long mTimes[];
+
+ /** Time of last started */
+ private long mStart;
+
+ /** Sleep a little so we don't look like a hog */
+ private void sleep() {
+ try {
+ Thread.sleep(0);
+ } catch (InterruptedException e) {
+ // Ignore exception
+ }
+ }
+
+ /** Empty Runnable used for determining overhead */
+ private Runnable mEmptyRunnable = new Runnable() {
+ public void run() {
+ }
+ };
+
+ /** Initialize */
+ private void init(int maxCount, CalibrationRec cr) {
+ mTimes = new long[maxCount];
+ mCr = cr;
+ reset();
+ }
+
+ /** Construct the stop watch */
+ public PerfTimer() {
+ init(10, new CalibrationRec());
+ }
+
+ /** Construct setting size of times array */
+ public PerfTimer(int maxCount) {
+ init(maxCount, new CalibrationRec());
+ }
+
+ /** Construct the stop watch */
+ public PerfTimer(CalibrationRec cr) {
+ init(10, cr);
+ }
+
+ /** Construct the stop watch */
+ public PerfTimer(int maxCount, CalibrationRec cr) {
+ init(maxCount, cr);
+ }
+
+ /** Reset the contents of the times array */
+ public PerfTimer reset() {
+ mCount = 0;
+ mStats.init();
+ return this;
+ }
+
+ /** Reset and then start the timer */
+ public PerfTimer restart() {
+ reset();
+ mStart = getTicks();
+ return this;
+ }
+
+ /** Start timing */
+ public PerfTimer start() {
+ mStart = getTicks();
+ return this;
+ }
+
+ /**
+ * Record the difference between start and now in the times array
+ * incrementing count. The time will be stored in the times array if the
+ * array is not full.
+ */
+ public PerfTimer stop() {
+ long stop = getTicks();
+ if (mCount < mTimes.length) {
+ mTimes[mCount++] = stop - mStart;
+ }
+ return this;
+ }
+
+ /**
+ * Time how long it takes to execute runnable.run() innerLoop number of
+ * times outerLoops number of times.
+ *
+ * @param outerLoops
+ * @param innerLoops
+ * @param runnable
+ * @return PerfTimer
+ */
+ public PerfTimer timeEach(Stats stats, int outerLoops, int innerLoops, Runnable runnable) {
+ reset();
+ resize(outerLoops);
+ stats.setOuterLoops(outerLoops);
+ stats.setInnerLoops(innerLoops);
+ for (int i = 0; i < outerLoops; i++) {
+ start();
+ for (int j = 0; j < innerLoops; j++) {
+ runnable.run();
+ }
+ stop();
+ sleep();
+ }
+ return this;
+ }
+
+ /**
+ * Time how long it takes to execute runnable.run(). Runs runnable votes
+ * times and returns the Stats of the fastest run. The actual number times
+ * that runnable.run() is executes is enough times so that it runs at least
+ * minThreadholeInMicros but not greater than maxThreadholdInMicro. This
+ * minimizes the chance that long context switches influence the result.
+ *
+ * @param votes is the number of runnable will be executed to determine
+ * fastest run
+ * @param outerLoops is the number of of times the inner loop is run
+ * @param initialInnerLoops is the initial inner loop
+ * @param maxStdDevRetrys if the maxStdDevRatio is exceeded this number of
+ * time the PerfTimerException is thrown.
+ * @param maxStdDevRatio the ratio of the standard deviation of the run and
+ * the time to run.
+ * @param debugLevel DEBUG_LEVEL_NONE, DEBUG_LEVEL_SOME, DEBUG_LEVEL_ALL
+ * @param runnable is the code to test.
+ * @return Stats of the fastest run.
+ */
+ public Stats timeEachAutomatically(int votes, int outerLoops, int initialInnerLoops,
+ double minThresholdInMicros, double maxThresholdInMicros, int maxStdDevRetrys,
+ double maxStdDevRatio, int debugLevel, Runnable runnable) throws PerfTimerException {
+ Stats minStats = null;
+
+ for (int v = 0; v < votes; v++) {
+ boolean successful = false;
+ Stats stats = new Stats();
+ int innerLoops = initialInnerLoops;
+
+ /* Warm up cache */
+ timeEach(stats, outerLoops, initialInnerLoops, runnable);
+
+ for (int stdDevRetrys = 0; stdDevRetrys < maxStdDevRetrys; stdDevRetrys++) {
+ /**
+ * First time may be long enough
+ */
+ timeEach(stats, outerLoops, innerLoops, runnable);
+ analyze(stats, mTimes, outerLoops, debugLevel);
+ double innerLoopTime = stats.getMinInMicros();
+ if ((innerLoopTime >= minThresholdInMicros
+ - ((maxThresholdInMicros - minThresholdInMicros) / 2))) {
+ if (stats.stdDevOk(maxStdDevRatio)) {
+ successful = true;
+ break;
+ } else {
+ stats.incStdDevTooLargeCount();
+ if (debugLevel >= DEBUG_LEVEL_SOME) {
+ log(String.format(
+ "tea: tlc=%d StdDevRatio=%.2f > maxStdDevRatio=%.2f",
+ stats.getStdDevTooLargeCount(), stats.getStdDevRatio(),
+ maxStdDevRatio));
+ }
+ }
+ } else {
+ /**
+ * The initial number of loops is too short find the number
+ * of loops that exceeds maxThresholdInMicros. Then use a
+ * binary search to find the approriate innerLoop value that
+ * is between min/maxThreshold.
+ */
+ innerLoops *= 10;
+ int maxInnerLoops = innerLoops;
+ int minInnerLoops = 1;
+ boolean binarySearch = false;
+ for (int i = 0; i < 10; i++) {
+ timeEach(stats, outerLoops, innerLoops, runnable);
+ analyze(stats, mTimes, outerLoops, debugLevel);
+ innerLoopTime = stats.getMedianInMicros();
+ if ((innerLoopTime >= minThresholdInMicros)
+ && (innerLoopTime <= maxThresholdInMicros)) {
+ if (stats.stdDevOk(maxStdDevRatio)) {
+ successful = true;
+ break;
+ } else {
+ stats.incStdDevTooLargeCount();
+ if (debugLevel >= DEBUG_LEVEL_SOME) {
+ log(String.format(
+ "tea: tlc=%d StdDevRatio=%.2f > maxStdDevRatio=%.2f",
+ stats.getStdDevTooLargeCount(), stats.getStdDevRatio(),
+ maxStdDevRatio));
+ }
+ }
+ } else if (binarySearch) {
+ if ((innerLoopTime < minThresholdInMicros)) {
+ minInnerLoops = innerLoops;
+ } else {
+ maxInnerLoops = innerLoops;
+ }
+ innerLoops = (maxInnerLoops + minInnerLoops) / 2;
+ } else if (innerLoopTime >= maxThresholdInMicros) {
+ /* Found a too large value, change to binary search */
+ binarySearch = true;
+ maxInnerLoops = innerLoops;
+ innerLoops = (maxInnerLoops + minInnerLoops) / 2;
+ } else {
+ innerLoops *= 10;
+ }
+ }
+ if (successful) {
+ break;
+ }
+ }
+ }
+ if (!successful) {
+ /* Couldn't find the number of loops to execute */
+ throw new PerfTimerException();
+ }
+
+ /** Looking for minimum */
+ if ((minStats == null) || (minStats.getTimePerLoop() > stats.getTimePerLoop())) {
+ minStats = stats;
+ }
+ if (debugLevel >= DEBUG_LEVEL_SOME) {
+ log(String.format("minStats.getTimePerLoop=%f minStats: %s", minStats.getTimePerLoop(), minStats));
+ }
+ }
+
+ return minStats;
+ }
+
+ /**
+ * Time how long it takes to execute runnable.run() with a threshold of 1 to
+ * 10ms.
+ *
+ * @param debugLevel DEBUG_LEVEL_NONE, DEBUG_LEVEL_SOME, DEBUG_LEVEL_ALL
+ * @param runnable
+ * @throws PerfTimerException
+ */
+ public Stats timeEachAutomatically(int debugLevel, Runnable runnable)
+ throws PerfTimerException {
+ mStats = timeEachAutomatically(mCr.mVotes, OUTER_LOOPS, 1, mCr.mMinThresholdInMicros,
+ mCr.mMaxThresholdInMicros, mCr.mStdDevRetrys, mCr.mMaxStdDevRatio, debugLevel,
+ runnable);
+ return mStats;
+ }
+
+ /**
+ * Time how long it takes to execute runnable.run() with a threshold of 1 to
+ * 10ms.
+ *
+ * @param runnable
+ * @throws PerfTimerException
+ */
+ public Stats timeEachAutomatically(Runnable runnable) throws PerfTimerException {
+ mStats = timeEachAutomatically(mCr.mVotes, OUTER_LOOPS, 1, mCr.mMinThresholdInMicros,
+ mCr.mMaxThresholdInMicros, mCr.mStdDevRetrys, mCr.mMaxStdDevRatio,
+ DEBUG_LEVEL_NONE, runnable);
+ return mStats;
+ }
+
+ /** Resize the times array */
+ public void resize(int maxCount) {
+ if (maxCount > mTimes.length) {
+ mTimes = new long[maxCount];
+ }
+ }
+
+ /**
+ * Analyze the data calculating the min, max, total, median, mean and
+ * stdDev. The standard deviation is calculated as sqrt(((sum of the squares
+ * of each time) / count) - mean^2)
+ * {@link "http://www.sciencebuddies.org/mentoring/project_data_analysis_variance_std_deviation.shtml"}
+ *
+ * @param debugLevel DEBUG_LEVEL_NONE, DEBUG_LEVEL_SOME, DEBUG_LEVEL_ALL
+ * @return StopWatch
+ */
+ public Stats analyze(Stats stats, long data[], int count, int debugLevel) {
+ if (count > 0) {
+ if (debugLevel >= DEBUG_LEVEL_ALL) {
+ for (int j = 0; j < count; j++) {
+ log(String.format("data[%d]=%,dns", j, data[j]));
+ }
+ }
+ stats.calculate(data, count);
+ } else {
+ stats.init();
+ }
+ if (debugLevel >= DEBUG_LEVEL_SOME) {
+ log("stats: " + stats);
+ }
+ return stats;
+ }
+
+ /**
+ * Calibrate the system and set it for this PerfTimer instance
+ *
+ * @param debugLevel DEBUG_LEVEL_NONE, DEBUG_LEVEL_SOME, DEBUG_LEVEL_ALL
+ * @param precisionInDecimalDigits the precision in number of decimal digits
+ */
+ public CalibrationRec calibrate(int debugLevel, double precisionInDecimalDigits)
+ throws PerfTimerException {
+ int nonZeroCount = 0;
+ Stats stats = new Stats();
+ CalibrationRec cr = new CalibrationRec();
+
+ /* initialize the precision */
+ cr.mPrecisionInDecimalDigits = precisionInDecimalDigits;
+
+ /* Warm up the cache */
+ timeEach(stats, OUTER_LOOPS, 10, mEmptyRunnable);
+
+ /*
+ * Determine the clock stats with at least 20% non-zero unique values.
+ */
+ for (int clockStatsTries = 1; clockStatsTries < 100; clockStatsTries++) {
+ int j;
+ int i;
+ long cur;
+ long prev;
+ long min;
+
+ int innerLoops = clockStatsTries * 10;
+ timeEach(stats, OUTER_LOOPS, innerLoops, mEmptyRunnable);
+ long nonZeroValues[] = new long[mCount];
+ prev = 0;
+ for (nonZeroCount = 0, i = 0; i < mCount; i++) {
+ cur = mTimes[i];
+ if (cur > 0) {
+ nonZeroValues[nonZeroCount++] = cur;
+ }
+ }
+ if (nonZeroCount > (mCount * 0.20)) {
+ // Calculate thresholds
+ analyze(stats, nonZeroValues, nonZeroCount, debugLevel);
+ stats.calculate(nonZeroValues, nonZeroCount);
+ cr.mMinThresholdInMicros = stats.getMeanInMicros()
+ * Math.pow(10, cr.mPrecisionInDecimalDigits);
+ cr.mMaxThresholdInMicros = cr.mMinThresholdInMicros * 2;
+
+ // Set overhead to 0 and time the empty loop then set overhead.
+ cr.mRunnableOverheadInMicros = 0;
+ mClockStats = timeEachAutomatically(mCr.mVotes, OUTER_LOOPS, innerLoops,
+ cr.mMinThresholdInMicros, cr.mMaxThresholdInMicros, mCr.mStdDevRetrys,
+ mCr.mMaxStdDevRatio, debugLevel, mEmptyRunnable);
+ cr.mRunnableOverheadInMicros = mClockStats.getMinInMicros()
+ / mClockStats.getInnerLoops();
+ break;
+ }
+ nonZeroCount = 0;
+ }
+ if (nonZeroCount == 0) {
+ throw new PerfTimerException();
+ }
+ if (debugLevel >= DEBUG_LEVEL_SOME) {
+ log(String.format("calibrate X oh=%.6fus minT=%,.6fus maxT=%,.6fus stats: %s",
+ cr.mRunnableOverheadInMicros, cr.mMinThresholdInMicros,
+ cr.mMaxThresholdInMicros, stats));
+ }
+ mCr = cr;
+ return mCr;
+ }
+
+ /** Calibrate the system and set it for this PerfTimer instance */
+ public CalibrationRec calibrate(double precisionInDecimalDigits) throws PerfTimerException {
+ return calibrate(DEBUG_LEVEL_NONE, precisionInDecimalDigits);
+ }
+
+ /** Calibrate the system and set it for this PerfTimer instance */
+ public CalibrationRec calibrate() throws PerfTimerException {
+ return calibrate(DEBUG_LEVEL_NONE, mCr.mPrecisionInDecimalDigits);
+ }
+
+ /*
+ * Accessors for the private data
+ */
+
+ /** Set calibration record */
+ public void setCalibrationRec(CalibrationRec cr) {
+ mCr = cr;
+ }
+
+ /** Get calibration record */
+ public CalibrationRec getCalibrationRec() {
+ return mCr;
+ }
+
+ /** Number of samples in times array. */
+ public int getCount() {
+ return mCount;
+ }
+
+ /** Minimum value of collected data in microseconds, valid after analyze. */
+ public double getMinInMicros() {
+ return mStats.getMinInMicros();
+ }
+
+ /** Maximum value of collected data in microseconds, valid after analyze. */
+ public double getMaxInMicros() {
+ return mStats.getMaxInMicros();
+ }
+
+ /**
+ * Sum of the values of collected data in microseconds, valid after analyze.
+ */
+ public double getTotalInMicros() {
+ return mStats.getTotalInMicros();
+ }
+
+ /** Sum of the values of collected data in seconds, valid after analyze. */
+ public double getTotalInSecs() {
+ return mStats.getTotalInSecs();
+ }
+
+ /** Sum of the values of collected data in seconds, valid after analyze. */
+ public double getMeanInMicros() {
+ return mStats.getMeanInMicros();
+ }
+
+ /** Median value of collected data in microseconds, valid after analyze. */
+ public double getMedianInMicros() {
+ return mStats.getMedianInMicros();
+ }
+
+ /**
+ * Standard deviation of collected data in microseconds, valid after
+ * analyze.
+ */
+ public double getStdDevInMicros() {
+ return mStats.getStdDevInMicros();
+ }
+
+ /** The mTimes[index] value */
+ public long getTime(int index) {
+ return mTimes[index];
+ }
+
+ /** The mTimes */
+ public long[] getTimes() {
+ return mTimes;
+ }
+
+ /** @return the clock stats as measured in calibrate */
+ public Stats getClockStats() {
+ return mClockStats;
+ }
+
+ /** @return the stats */
+ public Stats getStats() {
+ return mStats;
+ }
+
+ /**
+ * Convert stats to string
+ *
+ * @param debugLevel DEBUG_LEVEL_NONE, DEBUG_LEVEL_SOME, DEBUG_LEVEL_ALL
+ */
+ public String stats(int debugLevel) {
+ int innerLoops = mStats.getInnerLoops();
+ if (mCount == 0) {
+ return String.format("%,.3fus", (getTicks() - mStart) / TICKS_PER_MICROSECOND);
+ } else {
+ if (mCount == 1) {
+ return String.format("%,.3fus", getTime());
+ } else {
+ analyze(mStats, mTimes, mCount, debugLevel);
+ return mStats.toString();
+ }
+ }
+ }
+
+ /**
+ * Convert string
+ */
+ public String stats() {
+ return stats(0);
+ }
+
+ /**
+ * Get time
+ */
+ public double getTime() {
+ int innerLoops = mStats.getInnerLoops();
+ if (mCount == 0) {
+ return (getTicks() - mStart) / TICKS_PER_MICROSECOND;
+ } else {
+ if (mCount == 1) {
+ return mStats.getTotalInMicros();
+ } else {
+ analyze(mStats, mTimes, mCount, DEBUG_LEVEL_NONE);
+ return (mStats.getMinInMicros() / innerLoops) - mCr.mRunnableOverheadInMicros;
+ }
+ }
+ }
+
+ /** Convert to string */
+ @Override
+ public String toString() {
+ return String.format("%,.3fus", getTime());
+ }
+}