Android(Dalvik)成员变量访问性能

时间:2013-02-13 19:48:54

标签: android dalvik

我只是做了一个基准来比较局部变量,成员变量,其他对象的成员变量和getter setter的访问性能。基准测试增加了循环中的变量,迭代次数为10 mio。这是输出:

基准:当地101,成员1697,外国成员151,吸气者二传手268

这是在摩托罗拉XOOM平板电脑和Android 3.2上完成的。这些数字是毫秒执行时间。任何人都可以向我解释成员变量的偏差吗?特别是与其他对象的成员变量相比时。基于这些数字,在计算中使用它们的值之前,将成员变量复制到局部变量似乎是值得的。顺便说一句,我在HTC One X和Android 4.1上做了同样的基准测试,它显示了相同的偏差。

这些数字是否合理或是否存在我错过的系统错误?

以下是基准功能:

private int mID;

public void testMemberAccess() {
    // compare access times for local variables, members, members of other classes
    // and getter/setter functions
    final int numIterations = 10000000;
    final Item item = new Item();
    int i = 0;

    long start = SystemClock.elapsedRealtime(); 
    for (int k = 0; k < numIterations; k++) {
        mID++;
    }
    long member = SystemClock.elapsedRealtime() - start;

    start = SystemClock.elapsedRealtime(); 
    for (int k = 0; k < numIterations; k++) {
        item.mID++;
    }
    long foreignMember = SystemClock.elapsedRealtime() - start;

    start = SystemClock.elapsedRealtime(); 
    for (int k = 0; k < numIterations; k++) {
        item.setID(item.getID() + 1);

    }
    long getterSetter = SystemClock.elapsedRealtime() - start;

    start = SystemClock.elapsedRealtime(); 
    for (int k = 0; k < numIterations; k++) {
        i++;
    }
    long local = SystemClock.elapsedRealtime() - start;

    // just make sure nothing loops aren't optimized away?
    final int dummy = item.mID + i + mID;  
    Log.d(Game.ENGINE_NAME, String.format("BENCHMARK: local %d, member %d, foreign member %d, getter setter %d, dummy %d",
            local, member, foreignMember, getterSetter, dummy));
}

编辑:
我将每个循环放在一个函数中并随机调用它们100次。结果: BENCHMARK:当地100名,成员168名,外国成员190名,得主二传手271名 看起来不错,谢谢。 异物创建为最终类成员,而不是函数内部。

2 个答案:

答案 0 :(得分:1)

好吧,我会说Dalvik VM的优化器很聪明;-)我知道Dalvik VM是基于寄存器的。我不知道Dalvik VM的胆量,但我认为以下情况正在发生(或多或少):

local 的情况下,您正在循环中递增方法局部变量。优化器识别出在循环完成之前不访问此变量,因此可以使用寄存器并在那里应用增量直到循环完成,然后将值存储回局部变量。这会产生:1次提取,10000000次寄存器增量和1次存储。

成员的情况下,您正在循环内增加成员变量。优化器无法确定在循环运行时是否访问成员变量(通过另一个方法,对象或线程),因此在每次循环迭代时,它都被强制获取,递增并将值存储回成员变量中。这会产生:10000000次提取,10000000次增量和10000000次存储操作。

外部成员的情况下,您将在循环内递增对象的成员变量。您已在方法中创建该对象。优化器识别在循环完成之前无法访问此对象(由另一个对象,方法或线程),因此可以使用寄存器并在那里应用增量直到循环完成,然后将值存储回外部成员变量。这会产生:1次提取,10000000次寄存器增量和1次存储。

getter / setter 的情况下,我将假设编译器和/或优化器足够聪明以“内联”getter / setter(即:它实际上并不是一个方法call - 它用item.setID(item.getID() + 1)替换item.mID = item.mID + 1。优化器识别出您正在循环内增加对象的成员变量。您已在方法中创建该对象。优化器识别在循环完成之前无法访问此对象(由另一个对象,方法或线程),因此它可以使用寄存器并在那里应用增量直到循环完成,然后将值存储回外部成员变量。这会产生:1次提取,10000000次寄存器增量和1次存储。

我无法解释为什么 getter / setter 时间是外部成员时间的两倍,但这可能是由于优化器需要花费的时间它,或其他东西。

一个有趣的测试是将外部对象的创建移出方法,看看是否有任何改变。尝试移动这一行:

final Item item = new Item();

在方法之外(即:将其声明为某个对象的私有成员变量)。我猜想性能会更差。

免责声明:我不是Dalvik的工程师。

答案 1 :(得分:0)

除了改变他们的顺序之外,还有其他一些事情可以做,以试图消除任何干扰:

1-通过第二次计算第一项来消除边界效应;最好是使用另一个长变量。

2-将迭代次数增加10. 1000000似乎是一个很大的数字,但正如你从第一个建议中看到的那样;在现代CPU上增加100万次变量是如此之快,以至于填充各种缓存等许多其他因素都会影响其重要性。

3-添加虚假指令,例如插入虚拟long l = SystemClock.elapsedRealtime()-start计算。这将有助于显示此1000000次迭代实际上是一个小数字。

4-将volatile关键字添加到mID字段。这可能是分解任何编译器或CPU相关优化的最佳方法。