Skip to content

Instantly share code, notes, and snippets.

@adamgit
Created April 6, 2016 12:10
Show Gist options
  • Save adamgit/0f6c3fbb742b24960bc37e4546602795 to your computer and use it in GitHub Desktop.
Save adamgit/0f6c3fbb742b24960bc37e4546602795 to your computer and use it in GitHub Desktop.
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