Why are interface method invocations slower than concrete invocations?

匿名 (未验证) 提交于 2019-12-03 02:05:01

问题:

This is question comes in mind when I finding difference between abstract class and interface. In this post I came to know that interfaces are slow as they required extra indirection. But I am not getting what type of indirection required by the interface and not by the abstract class or concrete class.Please clarify on it. Thanks in advance

回答1:

There are many performance myths, and some were probably true several years ago, and some might still be true on VMs that don't have a JIT.

The Android documentation (remember that Android don't have a JVM, they have Dalvik VM) used to say that invoking a method on an interfaces was slower than invoking it on a class, so they were contributing to spreading the myth (it's also possible that it was slower on the Dalvik VM before they turned on the JIT). The documentation does now say:

Performance Myths

Previous versions of this document made various misleading claims. We address some of them here.

On devices without a JIT, it is true that invoking methods via a variable with an exact type rather than an interface is slightly more efficient. (So, for example, it was cheaper to invoke methods on a HashMap map than a Map map, even though in both cases the map was a HashMap.) It was not the case that this was 2x slower; the actual difference was more like 6% slower. Furthermore, the JIT makes the two effectively indistinguishable.

Source: Designing for performance on Android

The same thing is probably true for the JIT in the JVM, it would be very odd otherwise.



回答2:

If in doubt, measure it. My results showed no significant difference. When run, the following program produced:

7421714 (abstract) 5840702 (interface)  7621523 (abstract) 5929049 (interface) 

But when I switched the places of the two loops:

7887080 (interface) 5573605 (abstract)  7986213 (interface) 5609046 (abstract) 

It appears that abstract classes are slightly (~6%) faster, but that should not be noticeable; These are nanoseconds. 7887080 nanoseconds are ~7 milliseconds. That makes it a difference of 0.1 millis per 40k invocations (Java version: 1.6.20)

Here's the code:

public class ClassTest {      public static void main(String[] args) {         Random random = new Random();         List foos = new ArrayList(40000);         List bars = new ArrayList(40000);         for (int i = 0; i 


回答3:

An object has a "vtable pointer" of some kind which points to a "vtable" (method pointer table) for its class ("vtable" might be the wrong terminology, but that's not important). The vtable has pointers to all the method implementations; each method has an index which corresponds to a table entry. So, to call a class method, you just look up the corresponding method (using its index) in the vtable. If one class extends another, it just has a longer vtable with more entries; calling a method from the base class still uses the same procedure: that is, look up the method by its index.

However, in calling a method from an interface via an interface reference, there must be some alternative mechanism to find the method implementation pointer. Because a class can implement multiple interfaces, it's not possible for the method to always have the same index in the vtable (for instance). There are various possible ways to resolve this, but no way that is quite as efficient as simple vtable dispatch.

However, as mentioned in the comments, it probably won't make much difference with a modern Java VM implementation.



回答4:

This is variation on Bozho example. It runs longer and re-uses the same objects so the cache size doesn't matter so much. I also use an array so there is no overhead from the iterator.

public static void main(String[] args) {     Random random = new Random();     int testLength = 200 * 1000 * 1000;     Foo[] foos = new Foo[testLength];     Bar[] bars = new Bar[testLength];     Foo1Impl foo1 = new Foo1Impl();     Foo2Impl foo2 = new Foo2Impl();     Bar1Impl bar1 = new Bar1Impl();     Bar2Impl bar2 = new Bar2Impl();     for (int i = 0; i 

prints

The average abstract method call was 4.2 ns The average interface method call was 4.1 ns 

if you swap the order the tests are run you get

The average interface method call was 4.2 ns The average abstract method call was 4.1 ns 

There is more difference in how you run the test than which one you chose.

I got the same result with Java 6 update 26 and OpenJDK 7.


BTW: If you add a loop which only call the same object each time, you get

The direct method call was 2.2 ns 


回答5:

I tried to write a test that would quantify all of the various ways methods might be invoked. My findings show that it is not whether a method is an interface method or not that matters, but rather the type of the reference through which you are calling it. Calling an interface method through a class reference is much faster (relative to the number of calls) than calling the same method on the same class via an interface reference.

The results for 1,000,000 calls are...

interface method via interface reference: (nanos, millis) 5172161.0, 5.0

interface method via abstract reference: (nanos, millis) 1893732.0, 1.8

interface method via toplevel derived reference: (nanos, millis) 1841659.0, 1.8

Concrete method via concrete class reference: (nanos, millis) 1822885.0, 1.8

Note that the first two lines of the results are calls to the exact same method, but via different references.

And here is the code...

package interfacetest;  /**  *  * @author rpbarbat  */ public class InterfaceTest {     static public interface ITest     {         public int getFirstValue();         public int getSecondValue();     }      static abstract public class ATest implements ITest     {         int first = 0;          @Override         public int getFirstValue()         {             return first++;         }     }      static public class TestImpl extends ATest     {         int second = 0;          @Override         public int getSecondValue()         {             return second++;         }     }      static public class Test     {         int value = 0;          public int getConcreteValue()         {             return value++;         }     }      static int loops = 1000000;      /**      * @param args the command line arguments      */     public static void main(String[] args)     {         // Get some various pointers to the test classes         // To Interface         ITest iTest = new TestImpl();          // To abstract base         ATest aTest = new TestImpl();          // To impl         TestImpl testImpl = new TestImpl();          // To concrete         Test test = new Test();          System.out.println("Method call timings - " + loops + " loops");           StopWatch stopWatch = new StopWatch();          // Call interface method via interface reference         stopWatch.start();          for (int i = 0; i 

This was using the Oracles JDK 1.6_24. Hope this helps put this question to bed...

Regards,

Rodney Barbati



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!