From e5566f8673927026d8a55c4246f45272d2814c84 Mon Sep 17 00:00:00 2001 From: Wink Saville Date: Fri, 28 May 2010 11:49:52 -0700 Subject: [PATCH] Add support for Java micro protobuf's to protobuf-2.2.0a. See README.android for additional information. Change-Id: I9c5ef2eec484cc87e32841f39060f8f27b8e8472 --- Android.mk | 10 +- java/README.txt | 175 +++- .../java/com/google/protobuf/PerfTimer.java | 832 ++++++++++++++++++ 3 files changed, 1014 insertions(+), 3 deletions(-) create mode 100644 java/src/test/java/com/google/protobuf/PerfTimer.java diff --git a/Android.mk b/Android.mk index 928574460a..fb2d964b52 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 3ed06a1e21..1656e594ac 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 /java +directory. The generated jar file is +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 /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 -> | +java_outer_classname -> | + +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 xml element for the + maven-compiler-plugin. Below is for 1.5 sources: + + + maven-compiler-plugin + + 1.5 + 1.5 + + + + 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: + + + maven-compiler-plugin + + 1.3 + 1.5 + + + +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 0000000000..d6df4ffbcc --- /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:

+ * + 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); + * + * + * @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()); + } +}