Chciałem przetestować o ile tak na prawdę bardziej wydajne jest unikanie tworzenia dodatkowych obiektów. Temat ten poruszyłem w poście na swoim blogu:
devcave.pl/unikaj-tworzenia-niepotrzebnych-obiektow
Mam tam dwa warianty prostej klasy do sprawdzania czy podana liczba jest poprawną liczbą rzymską:
public class InefficientRomanNumerals {
static boolean isRomanNumeral(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
}
i
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.compile(
"^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
}
Różnica polega na tym, że w drugim przypadku Pattern jest tworzony i kompilowany tylko raz.
Na początku po prostu sprawdzałem to porównując System.nanoTime()
, jednak później zacząłem bawić się z toolem do benchmarków - JMH. O dziwo wyniki bardzo się nie różnią. Tak wygląda benchmark:
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = TIME, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = TIME, timeUnit = TimeUnit.MILLISECONDS)
@Fork(5)
public class PatternBenchmark {
public static final int LOOPS = 200001;
@Benchmark
public void efficientPattern() {
for (int i = 1; i < LOOPS; i++) {
RomanNumerals.isRomanNumeral(i + "s");
}
}
@Benchmark
public void ineficentPattern() {
for (int i = 1; i < LOOPS; i++) {
InefficientRomanNumerals.isRomanNumeral(i + "s");
}
}
}
i jego wynik:
Benchmark Mode Cnt Score Error Units
Avoid_creating_unnecessary_objects.PatternBenchmark.efficientPattern avgt 25 0.036 ± 0.003 s/op
Avoid_creating_unnecessary_objects.PatternBenchmark.ineficentPattern avgt 25 0.234 ± 0.011 s/op
~200ms różnicy przy 200 000 obrotów w pętli. Szału nie ma, myślałem, że różnica będzie większa. W przypadku przykładu z autoboxingiem, który też jest w poście, różnica była ogromna - ~1s vs ~7s.
Tylko pytanie - czy ten benchmark jest dobrze napisany? I na ile ten wynik jest miarodajny?