当我发现抽象类和接口之间的区别时,这是一个问题.在这篇文章中,我开始知道接口很慢,因为它们需要额外的间接性.但是我没有得到接口所需的什么类型的间接,而不是抽象类或具体类.请澄清它.提前致谢
Kaj*_*Kaj 39
有许多性能神话,有些可能在几年前就已经存在,有些可能仍然适用于没有JIT的虚拟机.
Android文档(请记住Android没有JVM,他们有Dalvik VM)曾经说过在接口上调用方法比在类上调用它要慢,所以它们有助于传播神话(这也是可能的)在他们打开JIT之前它在Dalvik VM上速度较慢).文档现在说:
表现神话
本文档的先前版本提出了各种误导性声明.我们在这里解决其中一些问题.
在没有JIT的设备上,通过具有精确类型而不是接口的变量调用方法确实更有效.(例如,调用HashMap映射上的方法比使用Map映射更便宜,即使在这两种情况下映射都是HashMap.)情况并非如此慢2倍; 实际差异更像是慢了6%.此外,JIT使两者有效地难以区分.
对于JVM中的JIT来说,同样的事情可能也是如此,否则它将是非常奇怪的.
Boz*_*zho 24
如果有疑问,请测量它.我的结果显示没有显着差异.运行时,产生以下程序:
7421714 (abstract)
5840702 (interface)
7621523 (abstract)
5929049 (interface)
Run Code Online (Sandbox Code Playgroud)
但是当我切换两个循环的位置时:
7887080 (interface)
5573605 (abstract)
7986213 (interface)
5609046 (abstract)
Run Code Online (Sandbox Code Playgroud)
看起来抽象类的速度略微提高了(~6%),但这并不明显; 这些是纳秒.7887080纳秒是~7毫秒.这使得每40k调用的差异为0.1毫安(Java版本:1.6.20)
这是代码:
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
一个对象有一个某种类型的"vtable指针",它指向其类的"vtable"(方法指针表)("vtable"可能是错误的术语,但这并不重要).vtable具有指向所有方法实现的指针; 每个方法都有一个对应于表条目的索引.因此,要调用类方法,只需在vtable中查找相应的方法(使用其索引).如果一个类扩展另一个类,它只有一个更长的vtable,有更多的条目; 从基类调用方法仍然使用相同的过程:即,通过索引查找方法.
但是,在通过接口引用从接口调用方法时,必须有一些替代机制来查找方法实现指针.因为类可以实现多个接口,所以该方法不可能在vtable中始终具有相同的索引(例如).有各种可能的方法来解决这个问题,但没有办法像简单的vtable调度一样有效.
但是,正如评论中所提到的,它可能与现代Java VM实现没有太大区别.
这是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);
}
Run Code Online (Sandbox Code Playgroud)
版画
The average abstract method call was 4.2 ns
The average interface method call was 4.1 ns
Run Code Online (Sandbox Code Playgroud)
如果您交换订单,则运行测试
The average interface method call was 4.2 ns
The average abstract method call was 4.1 ns
Run Code Online (Sandbox Code Playgroud)
运行测试的方式与您选择的方式有很大不同.
我在Java 6更新26和OpenJDK 7中获得了相同的结果.
顺便说一句:如果你添加一个每次只调用同一个对象的循环,你就得到了
The direct method call was 2.2 ns
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
10258 次 |
最近记录: |