访问本地字段与对象字段。文档错了吗?

时间:2012-09-10 20:06:04

标签: java android performance jit dalvik

文档似乎有误。有人能告诉我哪个是真的吗?

Performance Myths部分中:

  

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

Avoid Internal Getters/Setters部分中:

  

没有JIT,直接字段访问比调用一个简单的getter快 3x 。使用JIT(直接字段访问与访问本地一样便宜),直接字段访问比调用一个简单的getter快 7x

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

但是为什么在第一种情况下性能 20%更好而在第二种情况下性能 133%更好,出于同样的原因,这就是调用对象的JIT优化场?

3 个答案:

答案 0 :(得分:5)

我认为你在比较苹果和橘子。 Performance Myths参考讨论了JI​​T用于字段访问的优势,而第二个参考讨论了JI​​T用于方法访问的优势。

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

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;
    }
}

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

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

如果您仅使用getBar()手动替换上述功能中的bar,则可以提升性能。在没有JIT的机器上,性能提升大约是3倍,因为现场访问仍然有点慢,所以用稍慢的字段访问替换非常慢的方法只会产生适度的提升。但是,使用JIT,字段访问速度很快,因此使用快速字段访问替换速度非常慢的方法会产生更大的(7x)增强。

我希望这是有道理的!

答案 1 :(得分: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.

我认为文档不对,或者一个声明破坏了另一个声明。

答案 2 :(得分:1)

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

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

对于方法调用,假设Dalvik JIT没有内联方法(暗示),你在实际调用之上有相当多的开销,这使得调用成本很高,即使在JIT中:必须将寄存器保存到堆栈,必须恢复它们之后,由于并非所有代码都可见,因此无法进行优化。一个呼叫相对比无呼叫代码更昂贵,因为无呼叫的替代方案非常快,不是因为解释器在做呼叫方面做得更好(它没有,它只是 慢慢做其他事情)。例如,调用不会阻止优化,因为没有优化。