java 为什么接口方法调用比具体调用慢?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6839943/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why are interface method invocations slower than concrete invocations?
提问by Sanjay Jain
This is question comes in mind when I finding difference between abstract class and interface. In this postI 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
当我发现抽象类和接口之间的区别时,就会想到这个问题。在这篇文章中,我了解到接口很慢,因为它们需要额外的间接访问。但是我没有得到接口需要的间接类型,而不是抽象类或具体类。请澄清一下。提前致谢
回答by Kaj
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.
有许多关于性能的迷思,有些可能在几年前是正确的,有些在没有 JIT 的 VM 上可能仍然正确。
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:
Android 文档(请记住,Android 没有 JVM,他们有 Dalvik VM)曾经说过在接口上调用方法比在类上调用它慢,所以他们有助于传播神话(这也是可能的)在打开 JIT 之前,它在 Dalvik VM 上速度较慢)。文档现在确实说:
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.
性能神话
本文档的先前版本提出了各种误导性声明。我们在这里讨论其中的一些。
在没有 JIT 的设备上,确实通过具有确切类型的变量而不是接口来调用方法更有效。(因此,例如,在 HashMap 映射上调用方法比在 Map 映射上调用方法更便宜,即使在这两种情况下,映射都是 HashMap。)事实并非如此慢 2 倍;实际差异更像是慢了 6%。此外,JIT 使两者实际上无法区分。
Source: Designing for performance on Android
The same thing is probably true for the JIT in the JVM, it would be very odd otherwise.
JVM 中的 JIT 可能也是如此,否则会很奇怪。
回答by Bozho
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)
看起来抽象类要稍微快一些(~6%),但这应该不明显;这些是纳秒。7887080 纳秒约为 7 毫秒。这使得每 40k 次调用有 0.1 毫秒的差异(Java 版本:1.6.20)
Here's the code:
这是代码:
public class ClassTest {
public static void main(String[] args) {
Random random = new Random();
List<Foo> foos = new ArrayList<Foo>(40000);
List<Bar> bars = new ArrayList<Bar>(40000);
for (int i = 0; i < 40000; i++) {
foos.add(random.nextBoolean() ? new Foo1Impl() : new Foo2Impl());
bars.add(random.nextBoolean() ? new Bar1Impl() : new Bar2Impl());
}
long start = System.nanoTime();
for (Foo foo : foos) {
foo.foo();
}
System.out.println(System.nanoTime() - start);
start = System.nanoTime();
for (Bar bar : bars) {
bar.bar();
}
System.out.println(System.nanoTime() - start);
}
abstract static class Foo {
public abstract int foo();
}
static interface Bar {
int bar();
}
static class Foo1Impl extends Foo {
@Override
public int foo() {
int i = 10;
i++;
return i;
}
}
static class Foo2Impl extends Foo {
@Override
public int foo() {
int i = 10;
i++;
return i;
}
}
static class Bar1Impl implements Bar {
@Override
public int bar() {
int i = 10;
i++;
return i;
}
}
static class Bar2Impl implements Bar {
@Override
public int bar() {
int i = 10;
i++;
return i;
}
}
}
回答by davmac
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.
对象具有某种类型的“vtable 指针”,它指向其类的“vtable”(方法指针表)(“vtable”可能是错误的术语,但这并不重要)。vtable 有指向所有方法实现的指针;每个方法都有一个对应于表条目的索引。因此,要调用类方法,只需在 vtable 中查找相应的方法(使用其索引)。如果一个类扩展另一个类,它只会有一个更长的 vtable 和更多的条目;从基类调用方法仍然使用相同的过程:即通过索引查找方法。
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.
但是,在通过接口引用从接口调用方法时,必须有一些替代机制来查找方法实现指针。因为一个类可以实现多个接口,所以该方法不可能在 vtable 中始终具有相同的索引(例如)。有多种可能的方法来解决这个问题,但没有一种方法像简单的 vtable 调度那样有效。
However, as mentioned in the comments, it probably won't make much difference with a modern Java VM implementation.
但是,正如评论中所提到的,它与现代 Java VM 实现可能没有太大区别。
回答by Peter Lawrey
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.
这是 Bozho 示例的变体。它运行的时间更长并重复使用相同的对象,因此缓存大小并不那么重要。我还使用了一个数组,因此迭代器没有开销。
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 < testLength; i++) {
boolean flip = random.nextBoolean();
foos[i] = flip ? foo1 : foo2;
bars[i] = flip ? bar1 : bar2;
}
long start;
start = System.nanoTime();
for (Foo foo : foos) {
foo.foo();
}
System.out.printf("The average abstract method call was %.1f ns%n", (double) (System.nanoTime() - start) / testLength);
start = System.nanoTime();
for (Bar bar : bars) {
bar.bar();
}
System.out.printf("The average interface method call was %.1f ns%n", (double) (System.nanoTime() - start) / testLength);
}
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.
我在 Java 6 update 26 和 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
回答by Rodney P. Barbati
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...
1,000,000 次调用的结果是...
interface method via interface reference: (nanos, millis) 5172161.0, 5.0
通过接口参考的接口方法:(nanos, millis) 5172161.0, 5.0
interface method via abstract reference: (nanos, millis) 1893732.0, 1.8
通过抽象引用的接口方法:(nanos, millis) 1893732.0, 1.8
interface method via toplevel derived reference: (nanos, millis) 1841659.0, 1.8
通过顶层派生参考的接口方法:(nanos, millis) 1841659.0, 1.8
Concrete method via concrete class reference: (nanos, millis) 1822885.0, 1.8
通过具体类参考的具体方法:(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 < loops; i++)
{
iTest.getFirstValue();
}
stopWatch.stop();
System.out.println("interface method via interface reference: (nanos, millis)" + stopWatch.getElapsedNanos() + ", " + stopWatch.getElapsedMillis());
// Call interface method via abstract reference
stopWatch.start();
for (int i = 0; i < loops; i++)
{
aTest.getFirstValue();
}
stopWatch.stop();
System.out.println("interface method via abstract reference: (nanos, millis)" + stopWatch.getElapsedNanos() + ", " + stopWatch.getElapsedMillis());
// Call derived interface via derived reference
stopWatch.start();
for (int i = 0; i < loops; i++)
{
testImpl.getSecondValue();
}
stopWatch.stop();
System.out.println("interface via toplevel derived reference: (nanos, millis)" + stopWatch.getElapsedNanos() + ", " + stopWatch.getElapsedMillis());
// Call concrete method in concrete class
stopWatch.start();
for (int i = 0; i < loops; i++)
{
test.getConcreteValue();
}
stopWatch.stop();
System.out.println("Concrete method via concrete class reference: (nanos, millis)" + stopWatch.getElapsedNanos() + ", " + stopWatch.getElapsedMillis());
}
}
package interfacetest;
/**
*
* @author rpbarbat
*/
public class StopWatch
{
private long start;
private long stop;
public StopWatch()
{
start = 0;
stop = 0;
}
public void start()
{
stop = 0;
start = System.nanoTime();
}
public void stop()
{
stop = System.nanoTime();
}
public float getElapsedNanos()
{
return (stop - start);
}
public float getElapsedMillis()
{
return (stop - start) / 1000;
}
public float getElapsedSeconds()
{
return (stop - start) / 1000000000;
}
}
This was using the Oracles JDK 1.6_24. Hope this helps put this question to bed...
这是使用 Oracles JDK 1.6_24。希望这有助于解决这个问题......
Regards,
问候,
Rodney Barbati
罗德尼·巴巴蒂
回答by Akash Kumar
Interfaces are slower than abstract class as run time decision of method invocation would add little penalty of time,
接口比抽象类慢,因为方法调用的运行时决定会增加很少的时间损失,
However as JIT comes in picture which will take care of repeated calls of same method hence you may see the performance lag only in first call which is also very minimal,
然而,随着 JIT 的出现,它将处理相同方法的重复调用,因此您可能只会在第一次调用中看到性能滞后,这也非常小,
Now for Java 8, they almost made abstract class useless by adding default & static function,
现在对于 Java 8,他们几乎通过添加默认和静态函数使抽象类无用,