我现在正在研究一些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寄存器写入相同的字段,因此操作码似乎是完全冗余的。
我唯一能想到的就是这样做是为了让这更加线程安全。但肯定应该是程序员的责任(通过使用同步块)而不是编译器的责任。虽然我不是100%肯定,但我认为上述行为与大多数C / C ++编译器所做的完全不同。
我应该说使用ProGuard时产生的dex基本相同。我还应该提一下,我使用的是最新的Android工具和最新的JDK模型。
答案 0 :(得分:0)
对字段的每次访问都是独立的。要获得您描述的行为,您需要添加一个额外的局部变量:
int local = m_testField; // iget
local = local + i;
m_testField = local; // iput
doSomething(local);
也就是说,解释器,即时编译器和提前编译器的某些组合最终可能会在运行时为您进行优化。
答案 1 :(得分:0)
在预感中,我做了一些进一步的研究,我认为我可以回答我自己的问题......
次优dex似乎是由标准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能够在运行时做到这一点。