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 

与我有关的部分是将实例字段的值读入寄存器 v1 的 iget 操作码。相同的字段是从前面操作码中相同的 v1 寄存器写入的,因此操作码看起来完全是多余的。

我唯一能想到的就是这样做是为了使这更加线程安全。但肯定这应该是程序员的责任(通过使用同步块)而不是编译器的责任。尽管我不能 100% 确定,但我认为上述行为与大多数 C/C++ 编译器的行为完全不同。

我应该说,使用 ProGuard 时产生的 dex 基本相同。我还应该提到我正在使用最新的 Android 工具和最新型号的 JDK。

4

2 回答 2

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 足够聪明,可以在运行时执行此操作。

于 2015-03-17T14:05:41.537 回答
0

对字段的每次访问都是独立的。要获得您描述的行为,您需要添加一个额外的局部变量:

int local = m_testField; // iget
local = local + i;
m_testField = local; // iput
doSomething(local);

也就是说,解释器、即时编译器和提前编译器的某种组合可能最终会在运行时为您进行这些优化。

于 2015-03-17T02:39:01.710 回答