I wrote a basic app for measuring time it takes to execute any given block of code. What do you think?
@NoArgsConstructor
public class Measurer {
private LoggingType loggingType = LoggingType.DISABLED;
public Measurer(LoggingType loggingType) {
this.loggingType = loggingType;
}
public long measure(MeasuredProcedure measuredProcedure) {
long start = System.nanoTime();
measuredProcedure.run();
long finish = System.nanoTime();
long nanosTaken = finish - start;
if (loggingType.equals(LoggingType.ENABLED)) {
logMeasurement(measuredProcedure, nanosTaken);
}
return nanosTaken;
}
public long compare(MeasuredProcedure firstProcedure, MeasuredProcedure secondProcedure) {
long nanosTakenByFirstProcedure = measure(firstProcedure);
long nanosTakenBySecondProcedure = measure(secondProcedure);
long signedDifference = nanosTakenByFirstProcedure - nanosTakenBySecondProcedure;
if (loggingType.equals(LoggingType.ENABLED)) {
logDifferenceBetween(firstProcedure, nanosTakenByFirstProcedure)
.and(secondProcedure, nanosTakenBySecondProcedure);
}
return signedDifference;
}
private void logMeasurement(MeasuredProcedure measuredProcedure, long nanosTaken) {
String timeTaken = renderReadable(nanosTaken);
System.out.println(measuredProcedure + " took: " + timeTaken);
}
private ComparisonLoggingIntermediary logDifferenceBetween(MeasuredProcedure firstProcedure, long firstProcedureNanos) {
return new ComparisonLoggingIntermediary(firstProcedure, firstProcedureNanos);
}
private static String renderReadable(long nanos) {
var r = new NanoProcessor().process(nanos);
TimeUnit[] timeUnits = {r.minutes(), r.seconds(), r.millis(), r.nanos()};
var joiner = new StringJoiner(", ");
for (int i = 0; i < timeUnits.length; i++) {
TimeUnit unit = timeUnits[i];
if (unit.value() == 0) {
continue;
}
String suffix = (unit.value() % 10 == 1) ? "" : "s";
joiner.add(unit.value() + " " + unit.name() + suffix);
}
return joiner.toString();
}
public enum LoggingType {
ENABLED, DISABLED
}
static class NanoProcessor {
NanoProcessingResult process(long nanos) {
long minutes = extractLooseMinutes(nanos);
long seconds = extractLooseSeconds(nanos);
long milliseconds = extractLooseMilliseconds(nanos);
long nanoseconds = extractLooseNanoseconds(nanos);
return new NanoProcessingResult(
new TimeUnit(TimeUnitName.MINUTE, minutes),
new TimeUnit(TimeUnitName.SECOND, seconds),
new TimeUnit(TimeUnitName.MILLISECOND, milliseconds),
new TimeUnit(TimeUnitName.NANOSECOND, nanoseconds)
);
}
private long extractLooseMinutes(long nanos) {
return nanos / 60_000_000_000L;
}
private long extractLooseSeconds(long nanos) {
return nanos % 60_000_000_000L / 1_000_000_000L;
}
private long extractLooseMilliseconds(long nanos) {
return nanos % 1_000_000_000L / 1_000_000L;
}
private long extractLooseNanoseconds(long nanos) {
return nanos % 1_000_000L;
}
record NanoProcessingResult(TimeUnit minutes, TimeUnit seconds,
TimeUnit millis, TimeUnit nanos) {
}
record TimeUnit(TimeUnitName name, long value) {
enum TimeUnitName {
MINUTE, SECOND, MILLISECOND, NANOSECOND;
@Override
public String toString() {
return name().toLowerCase();
}
}
}
}
private record ComparisonLoggingIntermediary(MeasuredProcedure firstProcedure,
long firstProcedureNanos) {
void and(MeasuredProcedure secondProcedure, long secondProcedureNanos) {
MeasuredProcedure fastestProcedure = (firstProcedureNanos < secondProcedureNanos) ?
firstProcedure : secondProcedure;
MeasuredProcedure slowestProcedure = fastestProcedure.equals(firstProcedure) ?
secondProcedure : firstProcedure;
long difference = Math.abs(firstProcedureNanos - secondProcedureNanos);
long percentage = fastestProcedure.equals(firstProcedure) ?
difference / (secondProcedureNanos / 100) :
difference / (firstProcedureNanos / 100);
String renderedDifference = renderReadable(difference);
System.out.println(fastestProcedure + " has shown better performance: it is faster than " +
slowestProcedure + " by " + percentage + "% (" + renderedDifference + ")");
}
}
}
abstract class MeasuredProcedure {
protected String name;
protected MeasuredProcedure(String name) {
this.name = name;
}
abstract void run();
@Override
public String toString() {
return name;
}
}
public final class MeasuredProcedureFactory {
public static final MeasuredProcedure MILLION_INTS_TO_ARRLIST_NO_PREALLOCATION =
new MeasuredProcedure("MILLION_INTS_TO_ARRLIST_NO_PREALLOCATION") {
@Override
public void run() {
int numOfElements = 1_000_000;
List<Integer> intList = new ArrayList<>();
addRandomInts(intList, numOfElements);
}
};
public static final MeasuredProcedure MILLION_INTS_TO_ARRLIST_WITH_PREALLOCATION =
new MeasuredProcedure("MILLION_INTS_TO_ARRLIST_WITH_PREALLOCATION") {
@Override
public void run() {
int numOfElements = 1_000_000;
List<Integer> intList = new ArrayList<>(numOfElements);
addRandomInts(intList, numOfElements);
}
};
public static final MeasuredProcedure PLUS_CONCATENATION_OF_THOUSAND_LETTERS =
new MeasuredProcedure("PLUS_CONCATENATION_OF_THOUSAND_LETTER") {
@Override
void run() {
String res = "";
String[] alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
for(int i = 0; i < 1_000; i++) {
int randomIndex = Util.randomInt(alphabet.length);
res += alphabet[randomIndex];
}
}
};
public static final MeasuredProcedure STRING_BUILDING_OF_THOUSAND_LETTERS =
new MeasuredProcedure("STRING_BUILDING_OF_THOUSAND_LETTERS") {
@Override
void run() {
StringBuilder sb = new StringBuilder();
String[] alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
for(int i = 0; i < 1_000; i++) {
int randomIndex = Util.randomInt(alphabet.length);
sb.append(alphabet[randomIndex]);
}
sb.toString();
}
};
public static final MeasuredProcedure STRING_BUFFERING_OF_THOUSAND_LETTERS =
new MeasuredProcedure("STRING_BUFFERING_OF_THOUSAND_LETTERS") {
@Override
void run() {
StringBuffer sb = new StringBuffer();
String[] alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
for(int i = 0; i < 1_000; i++) {
int randomIndex = Util.randomInt(alphabet.length);
sb.append(alphabet[randomIndex]);
}
sb.toString();
}
};
private MeasuredProcedureFactory() {
}
private static void addRandomInts(List<Integer> intList, int numOfElements) {
for (int i = 0; i < numOfElements; i++) {
int randomInt = Util.randomInt(numOfElements);
intList.add(randomInt);
}
}
}
Example of usage:
public class App {
public static void main(String[] args) {
Measurer measurer = new Measurer(LoggingType.ENABLED);
measurer.compare(
MeasuredProcedureFactory.STRING_BUFFERING_OF_THOUSAND_LETTERS,
MeasuredProcedureFactory.STRING_BUILDING_OF_THOUSAND_LETTERS
);
}
}
STRING_BUFFERING_OF_THOUSAND_LETTERS took: 2 milliseconds, 384700 nanoseconds
STRING_BUILDING_OF_THOUSAND_LETTERS took: 416800 nanoseconds
STRING_BUILDING_OF_THOUSAND_LETTERS has shown better performance: it is faster than STRING_BUFFERING_OF_THOUSAND_LETTERS by 82% (1 millisecond, 967900 nanoseconds)