5

文档似乎是错误的。谁能告诉我哪个是真的?

性能神话部分是:

在没有 JIT 的设备上,缓存字段访问比重复访问字段快约20% 。使用 JIT,现场访问成本与本地访问成本大致相同。

避免内部获取器/设置器部分是:

如果没有 JIT,直接字段访问比调用普通 getter 快大约3 倍。使用 JIT(直接字段访问与访问本地一样便宜),直接字段访问比调用普通 getter 快约7 倍。

很明显,没有 JIT 本地访问会更快。同样清楚的是,直接访问字段比使用 getter 访问更快。

但是为什么在第一种情况下性能提高20%,在第二种情况下性能提高133%,原因相同,即调用对象字段的 JIT 优化?

4

3 回答 3

5

我想你是在比较苹果和橘子。Performance Myths 参考讨论了 JIT 对于字段访问的优势,而第二个参考讨论了 JIT 对于方法访问的优势。

据我了解,直接字段访问与本地访问(不是您在帖子中所写的本地字段访问 - 没有本地字段之类的东西)的类比如下:

class foo {
    int bar = 42;

    int doStuff(){
        int x = 1;
        x += bar;
        x += bar;
        x += bar;
        return x;
    }
}

每个对 的引用bar都有相关的性能成本。一个好的编译器会认识到优化的机会并像这样“重写”代码:

int doStuff(){
    int x = 1f;
    int local_bar = bar;
    x += local_bar;
    x += local_bar;
    x += local_bar;
    return x;
}

如果没有 JIT,这是一个方便的优化,可以让您的性能提高20%

使用 JIT,优化是不必要的,因为 JIT 首先消除了访问对性能的影响bar

第二个参考描述了以下场景:

class foo {
    int bar = 42;

    int getBar() { return bar; }

    int doStuff(){
        int x = 1;
        x += getBar();
        x += getBar();
        x += getBar();
        return x;
    }
}

每个函数调用都有相关的性能损失。编译器不能缓存多个getBar()方法调用(因为它在前面的示例中缓存了多个直接字段访问bar),因为 getBar() 可能在每次调用时返回一个完全不同的数字(即,如果它有一个随机或时间-基于组件的返回值)。因此,它必须执行三个方法调用。

了解上述函数在有或没有 JIT 的情况下将以大致相同的速度执行是至关重要的。

If you were to manually replace getBar() in the above function with simply bar, you would achieve a performance boost. On a machine without a JIT, that performance boost is roughly 3x, because field access is still somewhat slow, so replacing the very slow methods with somewhat slow field accesses only yields a moderate boost. With a JIT, however, field access is fast, so replacing the very slow methods with fast field access yields a much greater (7x) boost.

I hope that makes sense!

于 2012-09-10T20:38:14.497 回答
2

我想你可能在比较苹果和橘子。在第一个报价中:

 caching field accesses is about 20% faster than repeatedly accesssing the field

意味着缓存策略可以在没有 JIT 编译的情况下仅在直接字段访问期间提高性能。换句话说:

int a = this.field;
if (a == 1)
...
if (a == 7) // etc.

产生更好的性能比

if (this.field == 1)
....
if (this.field == 7) //etc.

引用表明,重复引用该字段而不是将其存储在本地会受到惩罚。

第二个引用表明,如果没有 JIT,使用普通的 getter/setter 比直接访问字段要慢,例如:

if (this.getField()) // etc.

慢于:

if (this.field) // etc.

我不认为文档是错误的,或者一种说法会破坏另一种说法。

于 2012-09-10T20:37:08.460 回答
1

这只是一个有根据的猜测,我不知道 Dalvik 的内部结构。但请注意,在第一种情况下,将本地访问的性能与字段访问进行比较,而在第二种情况下,将字段访问与普通方法调用进行比较。另请注意,通过添加 JIT,x% 的加速并没有真正减少 x% 的时间,我们谈论的是相对性能:(a)解释的本地访问比解释的字段访问快 20%,并且( b) JIT 的本地访问与 JIT 的字段访问不暗示的一样快 (c) 解释的本地访问与 JIT 的本地/字段访问一样快。事实上,它很可能更慢。

在大多数 VM 架构中,在解释器中读取本地是一种内存访问,而不是寄存器访问(我们谈论的是机器寄存器,而不是 Dalvik 寄存器)。读取一个字段甚至更慢——我不能确定为什么(我的猜测是第二次查找,读取寄存器和对象字段),但无论如何它更复杂。另一方面,JIT 可以将字段和本地变量都放入寄存器中(这是我必须假设来解释性能平等,实际上有 JIT 可以做到这一点——我只是不知道它是否适用于此)和消除了大部分开销。

对于方法调用,假设 Dalvik JIT 不内联方法(这是隐含的),您在实际调用之上有相当多的开销,这使得调用即使在 JIT 时也很昂贵:必须将寄存器保存到堆栈,之后必须恢复它们,不能尽可能优化,因为并非所有代码都是可见的。调用比无调用代码相对更昂贵,因为无调用替代方案非常快,而不是因为解释器在调用方面做得更好(它没有,它只是在做其他所有事情时也很慢)。例如,调用不会阻止任何优化,因为没有优化。

于 2012-09-10T20:38:00.527 回答