Selaa lähdekoodia

Add support for Java micro protobuf's to protobuf-2.2.0a.

See README.android for additional information.

Change-Id: I9c5ef2eec484cc87e32841f39060f8f27b8e8472
Wink Saville 15 vuotta sitten
vanhempi
commit
e5566f8673
3 muutettua tiedostoa jossa 1014 lisäystä ja 3 poistoa
  1. 8 2
      Android.mk
  2. 174 1
      java/README.txt
  3. 832 0
      java/src/test/java/com/google/protobuf/PerfTimer.java

+ 8 - 2
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)

+ 174 - 1
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
 =====
 

+ 832 - 0
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());
+    }
+}