The JMH tool was used with settings for each benchmark:
Mode = AverageTime- benchmark measurement mode - function execution time in seconds per operation;Fork = 10- number of forks of the process with the program;Measurement = 1- number of iterations to count the measurement.
There is a single operation in one fork, so that JIT optimization does not work
The measurement results are presented in the format:
| Benchmark | Mode | Iterations | Score | Error | Units |
|---|---|---|---|---|---|
B | M | I | S | E | U |
Total time:T
Where: Benchmark B (className.methodName) was measured in the M mode the number of times I. We got the result S with the error E in units of measurement U. The measurement lasted time T
For example, in the first measurement, the execution time of the Instantiation.measureWith function is 3,714 ± 0,246 seconds per operation.
The declared accuracy of the tool is 99.9%, so with a probability of 0.999, the actual execution time is in the interval (3,714 - 0,246; 3,714 + 0,246) s/ops. Of course, it depends on a lot of parameters, and other conditions will lead to different results.
Task: Call a method many times that does not modify the object.
Question: How much overhead will it take each time to create a new object for this?
Instantiating a new object for each call:
publicvoidmeasureWith(Blackholebh) {
for (longi = 0; i < 100000000L; i++) {
bh.consume(newFoo().bar());
}
}Instantiating a single object for all calls (since this.hashCode() is called in bar(), the call is optimized):
publicvoidmeasureWithout(Blackholebh) {
Foofoo = newFoo();
for (longi = 0; i < 100000000L; i++) {
bh.consume(foo.bar());
}
}| Benchmark | Mode | Iterations | Score | Error | Units |
|---|---|---|---|---|---|
Instantiation.measureWith | AverageTime | 10 | 3,714 | ± 0,246 | s/op |
Instantiation.measureWithout | AverageTime | 10 | 0,091 | ± 0,004 | s/op |
Total time: 3m 49s
Task: Create a collection of objects and calculate the sum of the method values for each object.
Question: How does the style of writing code affect performance?
Procedural style using standard Java syntax:
publicvoidmeasureProcedural(Blackholebh) {
finalFoo[] foos = newFoo[100000000];
for (inti = 0; i < 100000000; i++) {
foos[i] = newFoo();
}
intsum = 0;
for (Foofoo : foos) {
sum += foo.bar();
}
bh.consume(sum);
}Relatively functional style using Java Stream API:
publicvoidmeasureFunctional(Blackholebh) {
intsum = Stream.generate(Foo::new)
.limit(100000000)
.map(Foo::bar)
.mapToInt(Integer::intValue)
.sum();
bh.consume(sum);
}Object-Oriented Declarative style using Cactoos:
publicvoidmeasureDeclarative(Blackholebh) {
intsum = newSumOf(
newMapped<Integer>(
foo -> foo.bar(),
newRepeated<Foo>(
100000000,
newFoo()
)
)
).intValue();
bh.consume(sum);
}| Benchmark | Mode | Iterations | Score | Error | Units |
|---|---|---|---|---|---|
Collections.measureProcedural | AverageTime | 10 | 7,631 | ± 0,524 | s/op |
Collections.measureFunctional | AverageTime | 10 | 4,560 | ± 0,595 | s/op |
Collections.measureDeclarative | AverageTime | 10 | 9,070 | ± 5,783 | s/op |
Total time: 7m 54s
Task: Call a polymorphic method many times.
Question: How does Java Dynamic Dispatch affect performance?
Late binding polymorphism using Dynamic Dispatch:
publicvoidmeasureWith(Blackholebh) {
Cartc = newCart(newBook("1984"));
c.p = newMovie("Godfather");
for (longi = 0; i < 10000000000L; i++) {
bh.consume(c.total());
}
}Reducing polymorphism before compilation using object specialization:
publicvoidmeasureWithout(Blackholebh) {
Cart1c1 = newCart1(newBook("1984"));
Cart2c2 = c1.with(newMovie("Godfather"));
for (longi = 0; i < 10000000000L; i++) {
bh.consume(c2.total());
}
}| Benchmark | Mode | Iterations | Score | Error | Units |
|---|---|---|---|---|---|
Polymorphism.measureWith | AverageTime | 10 | 8,648 | ± 0,834 | s/op |
Polymorphism.measureWithout | AverageTime | 10 | 6,360 | ± 0,346 | s/op |
Total time: 5m 18s