I have an interface PackedObject
:
public interface PackedObject { int get(); int sum(); void setIndex(int index); default int defaultSum() { return get(); } }
An abstract class AbstractPackedObject
:
public abstract class AbstractPackedObject implements PackedObject { protected int index = 0; protected int[] buffer; public void setIndex(int index) { this.index = index; } public void setBuffer(int[] buffer) { this.buffer = buffer; } @Override public int sum(){ return get(); } }
And a concrete implemention WrappedPackedObject
:
public class WrappedPackedObject extends AbstractPackedObject implements PackedObject { public WrappedPackedObject(int[] buffer) { this.buffer = buffer; } @Override public int get() { return buffer[index]; } }
I benchmarked defaultSum
and sum
methods (snippet of the JMH benchmark):
for (int i = 0; i < NB; i++) { packedObject.setIndex(i); value += packedObject.defaultSum(); } for (int i = 0; i < NB; i++) { packedObject.setIndex(i); value += packedObject.sum(); }
I try to figure why the sum
benchmarker is faster than the defaultSum
benchmark by a factor of 1.7.
I have start to dig into the JIT arcane. Call site targets only one method, so I'm expecting inlining to be done. The output of print inlining is the following:
@ 25 com.github.nithril.PackedObject::defaultSum (7 bytes) inline (hot) \-> TypeProfile (479222/479222 counts) = com/github/nithril/WrappedPackedObject @ 1 com.github.nithril.WrappedPackedObject::get (14 bytes) inline (hot) @ 10 java.nio.DirectByteBuffer::getInt (15 bytes) inline (hot) @ 25 com.github.nithril.AbstractPackedObject::sum (5 bytes) inline (hot) @ 1 com.github.nithril.WrappedPackedObject::get (14 bytes) inline (hot) @ 10 java.nio.DirectByteBuffer::getInt (15 bytes) inline (hot)
I don't yet understand why this line appears TypeProfile (479222/479222 counts) = com/github/nithril/WrappedPackedObject
I create a dedicated project with the above code. The benchmark is done using JMH.
Thanks for your help.
EDIT 2015/05/20:
I simplify the java code.
The inner loop of the benchSum
is quite straightforward:
0x00007f1bb11afb84: add 0x10(%r10,%r8,4),%eax ;*iadd ; - com.github.nithril.PackedObjectBench::benchSum@29 (line 50) 0x00007f1bb11afb89: mov %r8d,0xc(%r12,%r11,8) ;*putfield index ; - com.github.nithril.AbstractPackedObject::setIndex@2 (line 13) ; - com.github.nithril.PackedObjectBench::benchSum@17 (line 49) 0x00007f1bb11afb8e: inc %r8d ;*iinc ; - com.github.nithril.PackedObjectBench::benchSum@31 (line 48) 0x00007f1bb11afb91: cmp $0x2710,%r8d 0x00007f1bb11afb98: jl 0x00007f1bb11afb84
The inner loop of the benchDefaultSum
is more complicated with read/write of the index and inside the inner loop a comparison of the array bound. I do not yet completely understand the purpose of this comparison...
0x00007fcfdcf82cb8: mov %edx,0xc(%r12,%r11,8) ;*putfield index ; - com.github.nithril.AbstractPackedObject::setIndex@2 (line 13) ; - com.github.nithril.PackedObjectBench::benchDefaultSum@17 (line 32) 0x00007fcfdcf82cbd: mov 0xc(%r10),%r8d ;*getfield index ; - com.github.nithril.WrappedPackedObject::get@5 (line 17) ; - com.github.nithril.PackedObject::defaultSum@1 (line 15) ; - com.github.nithril.PackedObjectBench::benchDefaultSum@24 (line 33) 0x00007fcfdcf82cc1: cmp %r9d,%r8d 0x00007fcfdcf82cc4: jae 0x00007fcfdcf82d1f ;*iaload ; - com.github.nithril.WrappedPackedObject::get@8 (line 17) ; - com.github.nithril.PackedObject::defaultSum@1 (line 15) ; - com.github.nithril.PackedObjectBench::benchDefaultSum@24 (line 33) 0x00007fcfdcf82cc6: add 0x10(%rcx,%r8,4),%eax ;*iadd ; - com.github.nithril.PackedObjectBench::benchDefaultSum@29 (line 33) 0x00007fcfdcf82ccb: inc %edx ;*iinc ; - com.github.nithril.PackedObjectBench::benchDefaultSum@31 (line 31) 0x00007fcfdcf82ccd: cmp $0x2710,%edx 0x00007fcfdcf82cd3: jl 0x00007fcfdcf82cb8 ;*aload_2 [...] 0x00007fcfdcf82ce6: mov $0xffffffe4,%esi 0x00007fcfdcf82ceb: mov %r10,0x8(%rsp) 0x00007fcfdcf82cf0: mov %ebx,0x4(%rsp) 0x00007fcfdcf82cf4: mov %r8d,0x10(%rsp) 0x00007fcfdcf82cf9: xchg %ax,%ax 0x00007fcfdcf82cfb: callq 0x00007fcfdcdea1a0 ; OopMap{rbp=NarrowOop [8]=Oop off=416} ;*iaload ; - com.github.nithril.WrappedPackedObject::get@8 (line 17) ; - com.github.nithril.PackedObject::defaultSum@1 (line 15) ; - com.github.nithril.PackedObjectBench::benchDefaultSum@24 (line 33) ; {runtime_call} 0x00007fcfdcf82d00: callq 0x00007fcff1c94320 ;*iaload ; - com.github.nithril.WrappedPackedObject::get@8 (line 17) ; - com.github.nithril.PackedObject::defaultSum@1 (line 15) ; - com.github.nithril.PackedObjectBench::benchDefaultSum@24 (line 33) ; {runtime_call} [...] 0x00007fcfdcf82d1f: mov %eax,(%rsp) 0x00007fcfdcf82d22: mov %edx,%ebx 0x00007fcfdcf82d24: jmp 0x00007fcfdcf82ce6
The abstract class can have a state, and its methods can access the implementation's state. Although default methods are allowed in an interface, they can't access the implementation's state.
Abstract class in Java is similar to interface except that it can contain default method implementation. An abstract class can have an abstract method without body and it can have methods with implementation also.
The performance of an abstract class is fast. The performance of interface is slow because it requires time to search actual method in the corresponding class. It is used to implement the core identity of class.
4) The fourth difference between abstract class and interface in Java is that abstract class are slightly faster than interface because interface involves a search before calling any overridden method in Java.
The main difference is that abstract classes can have constructors, state, and behavior. Furthermore, static methods in interfaces make it possible to group related utility methods, without having to create artificial utility classes that are simply placeholders for static methods. 6. Conclusion
When it comes to maintaining backward compatibility with existing code, however, static and default methods are a good trade-off. As usual, all the code samples shown in this article are available over on GitHub.
Multiple Interface Inheritance Rules Default interface methods are a pretty nice feature, but there are some caveats worth mentioning. Since Java allows classes to implement multiple interfaces, it's important to know what happens when a class implements several interfaces that define the same default methods.
Default methods are also known as defender methods or virtual extension methods. Static Methods: The interfaces can have static methods as well which is similar to static method of classes.
Just regurgitating information that i've picked up by cursory reading of the hotspot-compiler-dev mailing list, but this may be the lack of class hierarchy analysis for default methods in interfaces, which prevents devirtualization of interface methods.
See JDK Bug 8065760 and 6986483
My guess is that even though the method is inlined it still is by preceded by a type guard that gets eliminated by CHA in the abstract case but not for the interface method.
Printing optimized assembly (i think JMH has some flag for that) could confirm that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With