Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java default methods is slower than the same code but in an abstract class

Tags:

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 
like image 578
Nicolas Labrot Avatar asked May 18 '15 20:05

Nicolas Labrot


People also ask

Why default methods are not allowed in abstract class?

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.

Can abstract class have default methods in Java?

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.

Are abstract classes slow?

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.

Why abstract class is faster than interface?

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.

What is the difference between static and abstract classes 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

Is it better to use static or default methods in Java?

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.

Can a class have multiple default interface methods in Java?

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.

What are default methods in Java?

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.


1 Answers

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.

like image 65
the8472 Avatar answered Oct 14 '22 01:10

the8472