安卓dex中的冗余操作码

3

我目前正在研究一些Android性能问题,发现dex代码中存在一些次优模式。我想知道这是否是可预期的,并且背后的理由是什么。

例如,考虑以下Java代码:

m_testField += i;

doSomething(m_testField);

当这个被构建并且通过baksmali运行时,它看起来像下面这样:

iget v1, p0, Lcom/example/MainActivity$FieldTest;->m_testField:I

add-int/2addr v1, v0

iput v1, p0, Lcom/example/MainActivity$FieldTest;->m_testField:I

iget v1, p0, Lcom/example/MainActivity$FieldTest;->m_testField:I

invoke-direct {p0, v1}, Lcom/example/MainActivity$FieldTest;->doSomething(I)V 

我担心的部分是iget操作码,它将实例字段的值读入寄存器v1。在前一个操作码中,相同的字段是从完全相同的v1寄存器中写入的,所以这个操作码似乎是完全多余的。
唯一能想到的是这样做是为了使它更加线程安全。但是这应该是程序员的责任(通过使用同步块),而不是编译器的责任。虽然我不能百分之百确定,但我认为上述行为与大多数C / C ++编译器所做的事情非常不同。
我应该说,当使用ProGuard时会生成基本相同的dex。我也应该提到,我正在使用最新的Android工具和晚期型号的JDK。

如果第二个线程更改了m_testField,则需要第二个iget命令。 - Robert
我相信优化器可以消除冗余的加载,除非该字段被标记为volatile -- 在缺乏同步操作的情况下,编译器不需要预测其他线程的干扰。 - fadden
@fadden - 那很有道理。 - Dave Mc in Cork
@Robert - 另一个线程也可以在第一个 iget 和 iput 之间修改字段。因此,我认为线程安全不是问题所在。正如我在自己的答案中所说,我认为这是因为标准 JVM 是基于堆栈的。 - Dave Mc in Cork
2个回答

0

凭直觉,我进行了进一步的研究,现在我认为我有能力回答自己的问题...

次优的dex似乎是由于它是从标准Java字节码生成的,而标准Java字节码是基于堆栈而不是寄存器的。我反汇编了与我的问题中示例代码对应的.class文件。相关部分如下:

5: aload_0       
6: dup           
7: getfield      #22                 // Field m_testField:I
10: iload_1       
11: iadd          
12: putfield      #22                 // Field m_testField:I
15: aload_0       
16: aload_0       
17: getfield      #22                 // Field m_testField:I
20: invokespecial #33                 // Method doSomething:(I)V

在执行第11行的iadd操作码后,m_testField的值位于堆栈顶部,而“this”引用则从堆栈中排在第二位。问题在于,第12行的putfield操作码会将它们从堆栈中移除。这意味着字段值必须在第17行重新推送到堆栈上。

我必须说我对这种低效率感到非常惊讶。我本以为将字节码转换为dex的dx工具足够聪明,可以消除这种冗余。我只希望ART能够在运行时自动处理这个问题。


你所感受到的惊讶,也许掺杂着困惑和挫败感,正是你听到“呼唤”的声音。在内心深处,你知道编译器和优化器永远无法与手工编写的汇编语言相比的美丽和完美。来吧,加入我们。额外福利:玩得愉快,dx --no-optimize - Caleb Fenton

0
每次访问字段都是独立的。要获得您描述的行为,需要添加一个额外的本地变量:
int local = m_testField; // iget
local = local + i;
m_testField = local; // iput
doSomething(local);

话虽如此,解释器、即时编译器和预编译器的某种组合可能会在运行时为您进行这些优化。


感谢回复。是的,我知道使用本地变量可以避免发送 iget。这使得 Java 代码有点丑陋。此外,在现有大量代码的情况下,这不是可行的解决方案,其中一些代码以在组织的其他部分创建的库的形式提供。可以开发一个工具来后处理 dex 以删除冗余的操作码。我想我的真正问题是,Java 语言规范是否要求所有字段访问都是独立的。 - Dave Mc in Cork
在这种情况下,字节码是源代码的直译。但是这应该在运行时消失,所以您不需要为此付费。 - Jesse Wilson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接