Created
April 6, 2016 12:10
-
-
Save adamgit/0f6c3fbb742b24960bc37e4546602795 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import tmachine.batchprocessors.MBExecutableAlgorithm; | |
import java.text.DecimalFormat; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.function.Consumer; | |
import java.util.function.Function; | |
/** | |
* Created by adam on 29/03/16. | |
*/ | |
public class MicroBenchmarker | |
{ | |
static DecimalFormat format = new DecimalFormat( "#.###" ); | |
public enum LOOP_CHANGER | |
{ | |
ADD, SUBTRACT, MULTIPLY | |
} | |
/** | |
* Puts everything together; this is the method you should use for actual metrics/analysis runs. | |
* | |
* 1. Choose a numRunsToWarmup to get the JIT going. Sometimes 1 is enough; ideally you want minutes or tens of minutes to be sure HotSpot has done its magic. | |
* 2. Setup your 3 final args - the Creator, the Randomizer, and the all-important Executor | |
* 3. Choose params for how big the data size is to be, and how much it should change during the run | |
* | |
* @param numRunsToWarmup | |
* @param initialDataSize | |
* @param maxDataSize | |
* @param loopType if ADD, then each loop, dataSize = dataSize + loopIncrementScalar. If MULTIPLY, then dataSize = dataSize * loopIncrementScalar | |
* @param loopIncrementScalar | |
* @param dataCreator | |
* @param randomizer | |
* @param executor | |
* @param <T> | |
* @return | |
*/ | |
public static<T> List<RunResult[]> measureTrialsWithWarmupsAndRanges( int numRunsToWarmup, int initialDataSize, int maxDataSize, LOOP_CHANGER loopType, float loopIncrementScalar, Function<Integer,T> dataCreator, Consumer<T> randomizer, MBExecutableAlgorithm<T> executor, int repeatsPerRun, int runsPerTrial ) | |
{ | |
List< MicroBenchmarker.RunResult[]> randomizedTrials = null; | |
for( int reRunWarmUpCounter = 0; reRunWarmUpCounter < 1 + numRunsToWarmup; reRunWarmUpCounter ++ ) | |
{ | |
randomizedTrials = new LinkedList< MicroBenchmarker.RunResult[] >(); // discard the old one if it was a warmup | |
switch( loopType ) | |
{ | |
case ADD: | |
for( int dataSize = initialDataSize; dataSize < maxDataSize; dataSize += loopIncrementScalar ) | |
randomizedTrials.add( measureSingleTrial( dataSize, dataCreator, randomizer, executor, repeatsPerRun, runsPerTrial ) ); | |
break; | |
case SUBTRACT: | |
for( int dataSize = initialDataSize; dataSize > maxDataSize; dataSize -= loopIncrementScalar ) | |
randomizedTrials.add( measureSingleTrial( dataSize, dataCreator, randomizer, executor, repeatsPerRun, runsPerTrial ) ); | |
break; | |
case MULTIPLY: | |
for( int dataSize = initialDataSize; dataSize < maxDataSize; dataSize *= loopIncrementScalar ) | |
randomizedTrials.add( measureSingleTrial( dataSize, dataCreator, randomizer, executor, repeatsPerRun, runsPerTrial ) ); | |
break; | |
} | |
if( reRunWarmUpCounter != numRunsToWarmup ) | |
System.out.println( "...warmup run "+(1+reRunWarmUpCounter)+" of "+numRunsToWarmup+" completed;" ); | |
//System.out.println("*****> Total time spent configuring and running benchmark = "+MicroBenchmarker.formatNanos( System.nanoTime() - sTime )); | |
} | |
return randomizedTrials; | |
} | |
/** | |
* NB: You MUST NOT use java.util.function.* for the actual execution of a benchmark - Oracle's implementation is | |
* extremely bad, very very very slow - up to 20x slower than doing it without java.util.function. | |
* | |
* Hence you will usually create an anonymous class as the executor - because Oracle's code is useless. | |
* | |
* @param algorithmDataSize | |
* @param dataCreator | |
* @param randomizer | |
* @param executor pass-in something that = new MBExecutableAlgorithm() { ... } (i.e. an anonymous class) | |
* @param <T> | |
* @return | |
*/ | |
public static <T> RunResult[] measureSingleTrial( int algorithmDataSize, Function< Integer, T > dataCreator, Consumer< T > randomizer, MBExecutableAlgorithm< T > executor, int repeatsPerRun, int runsPerTrial ) | |
{ | |
int totalRunsToReport = runsPerTrial; | |
long sTime = System.nanoTime(); | |
T dataSet = dataCreator.apply( algorithmDataSize ); | |
long sharedCreationTime = System.nanoTime() - sTime; | |
RunResult[] results = new RunResult[totalRunsToReport]; | |
//System.out.println( "Starting "+totalRunsToReport+" runs (each repeating/averaging over: "+repeatsPerRun+" repeats) ... with "+algorithmDataSize+" datasize..." ); | |
for( int k=0; k<totalRunsToReport; k++ ) | |
{ | |
results[k] = new RunResult(); | |
RunResult result = results[k]; | |
sTime = System.nanoTime(); | |
randomizer.accept( dataSet ); | |
result.dataSetInitializationTime = System.nanoTime() - sTime; | |
result.inputDataSize = algorithmDataSize; | |
result.executionStartTime = System.nanoTime(); | |
for( int i = 0; i < repeatsPerRun; i++ ) | |
{ | |
executor.execute( dataSet ); | |
} | |
result.executionEndTime = System.nanoTime(); | |
result.deltaTime = result.executionEndTime -result.executionStartTime; | |
result.timePerRun = result.deltaTime/repeatsPerRun; | |
result.timeRunPerDataSize100 = algorithmDataSize >= 100 ? result.timePerRun / (algorithmDataSize/100) : 0; | |
//System.out.println( "Time taken for "+repeatsPerRun+" repeats = " + formatNanos( result.deltaTime ) + " ("+formatNanos(result.timePerRun)+" per run)" ); | |
//System.out.println( " "+formatNanos( result.timeRunPerDataSize100 )+" per 100 data items ("+algorithmDataSize+" items)"); | |
} | |
return results; | |
} | |
public static String formatNanos( long nanos ) | |
{ | |
if( nanos > 1e8 ) | |
return format.format( nanos/1e9 ) + " secs"; | |
else if( nanos > 1e5 ) | |
return format.format( nanos/1e6 )+" millis"; | |
else | |
return format.format( nanos )+" ns"; | |
} | |
public static void outputConsoleCSV( RunResult[] singleTrial ) | |
{ | |
List<RunResult[]> input = new LinkedList< RunResult[] >(); | |
input.add( singleTrial ); | |
outputConsoleCSV( input ); | |
} | |
public static void outputConsoleCSV( List< RunResult[] > randomizedTrials ) | |
{ | |
System.out.println( "inputDataSize,average (x100),min (x100),max (x100),Total Exec Time, Total Setup Time" ); | |
for( RunResult[] trialOutcome : randomizedTrials ) | |
{ | |
long minTime100 = Long.MAX_VALUE; | |
long maxTime100 = Long.MIN_VALUE; | |
long avgTime100 = 0; | |
long totalTime = 0; | |
long totalTime100 = 0; | |
long setupTimeTotal = 0; | |
for( RunResult run : trialOutcome ) | |
{ | |
totalTime += run.deltaTime; | |
setupTimeTotal += run.dataSetInitializationTime; | |
minTime100 = Math.min( minTime100, run.timeRunPerDataSize100 ); | |
maxTime100 = Math.max( maxTime100, run.timeRunPerDataSize100 ); | |
totalTime100 += run.timeRunPerDataSize100; | |
} | |
avgTime100 = totalTime100 / trialOutcome.length; | |
//Human readable: System.out.println( "["+trialOutcome[0].inputDataSize+"] avg time 100 runs = "+avgTime100+" "+minTime100+"..."+maxTime100 ); | |
//CSV copy/pasteable to XL: | |
System.out.format( "%d,%d,%d,%d,%s,%s\n", trialOutcome[ 0 ].inputDataSize, avgTime100, minTime100, maxTime100, formatNanos( totalTime ), formatNanos( setupTimeTotal ) ); | |
} | |
} | |
public static class RunResult | |
{ | |
public int inputDataSize; | |
public long sharedAllocationTime, dataSetInitializationTime, executionStartTime, executionEndTime, deltaTime, timePerRun, timeRunPerDataSize100; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment