Android:运行空方法会产生多少开销?

时间:2010-12-03 13:12:38

标签: java android performance overhead

我已经创建了一个类来处理我的调试输出,这样我就不需要在发布之前去掉所有的日志输出。

public class Debug {
    public static void debug( String module, String message) {
        if( Release.DEBUG )
            Log.d(module, message);
    }
}

在阅读了另一个问题后,我了解到如果常量Release.DEBUG为false,则不会编译if语句的内容。

我想知道的是运行这个空方法会产生多少开销? (删除if子句后,方法中没有代码)是否会对我的应用程序产生任何影响?显然,在为移动手机编写时,性能是一个大问题= P

由于

加里

5 个答案:

答案 0 :(得分:14)

使用Android 2.3.2在Nexus S上进行的测量:

10^6 iterations of 1000 calls to an empty static void function: 21s  <==> 21ns/call
10^6 iterations of 1000 calls to an empty non-static void function: 65s  <==> 65ns/call

10^6 iterations of 500 calls to an empty static void function: 3.5s  <==> 7ns/call
10^6 iterations of 500 calls to an empty non-static void function: 28s  <==> 56ns/call

10^6 iterations of 100 calls to an empty static void function: 2.4s  <==> 24ns/call
10^6 iterations of 100 calls to an empty non-static void function: 2.9s  <==> 29ns/call

控制:

10^6 iterations of an empty loop: 41ms <==> 41ns/iteration
10^7 iterations of an empty loop: 560ms <==> 56ns/iteration
10^9 iterations of an empty loop: 9300ms <==> 9.3ns/iteration

我已多次重复测量。未发现重大偏差。 您可以看到每次呼叫成本可能因工作负载而有很大差异(可能是由于JIT编译), 但可以得出3个结论:

  1. dalvik / java糟透了优化死码

  2. 静态函数调用可以比非静态调优得更好 (非静态函数是虚拟的,需要在虚拟表中查找)

  3. nexus s的成本不超过70ns / call(即~70 cpu周期) 并且与一个空循环迭代的成本相当(即一个增量和一个局部变量的一个条件检查)

  4. 请注意,在您的情况下,将始终评估字符串参数。如果进行字符串连接,则需要创建中间字符串。这将是非常昂贵的并涉及很多gc。例如执行函数:

    void empty(String string){
    }
    

    使用诸如

    之类的参数调用
    empty("Hello " + 42 + " this is a string " + count );
    

    100次此类调用的10 ^ 4次迭代需要10次。那是10us / call,即比空呼叫慢〜1000倍。它还会产生大量的GC活动。避免这种情况的唯一方法是手动内联函数,即使用&gt;&gt; if&lt;&lt;语句而不是调试函数调用。这很丑陋,但却是让它发挥作用的唯一方法。

答案 1 :(得分:2)

除非你在一个深层嵌套的循环中调用它,否则我不会担心它。

答案 2 :(得分:2)

一个好的编译器会删除整个空方法,导致根本没有开销。我不确定Dalvik编译器是否已经这样做了,但我怀疑它很可能,至少是因为与Froyo的Just-in-time编译器的到来。

另请参阅:Inline expansion

答案 3 :(得分:2)

就性能而言,生成传递给调试函数的消息的开销将更加严重,因为它可能会进行内存分配,例如

Debug.debug(mymodule, "My error message" + myerrorcode);

即使邮件被分箱,仍然会发生这种情况。 不幸的是,你真的需要围绕调用这个函数的“if(Release.DEBUG)”而不是函数本身,如果你的目标是性能,你会在很多android代码中看到这个。

答案 4 :(得分:1)

这是一个有趣的问题,我喜欢@misiu_mp分析,所以我想我会在运行Android 6.0.1的Nexus 7上进行2016测试更新。这是测试代码:

public void runSpeedTest() {
    long startTime;
    long[] times = new long[100000];
    long[] staticTimes = new long[100000];
    for (int i = 0; i < times.length; i++) {
        startTime = System.nanoTime();
        for (int j = 0; j < 1000; j++) {
            emptyMethod();
        }
        times[i] = (System.nanoTime() - startTime) / 1000;
        startTime = System.nanoTime();
        for (int j = 0; j < 1000; j++) {
            emptyStaticMethod();
        }
        staticTimes[i] = (System.nanoTime() - startTime) / 1000;
    }
    int timesSum = 0;
    for (int i = 0; i < times.length; i++) { timesSum += times[i]; Log.d("status", "time," + times[i]); sleep(); }
    int timesStaticSum = 0;
    for (int i = 0; i < times.length; i++) { timesStaticSum += staticTimes[i]; Log.d("status", "statictime," + staticTimes[i]); sleep(); }
    sleep();
    Log.d("status", "final speed = " + (timesSum / times.length));
    Log.d("status", "final static speed = " + (timesStaticSum / times.length));
}

private void sleep() {
    try {
        Thread.sleep(10);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

private void emptyMethod() { }
private static void emptyStaticMethod() { }

添加了sleep()以防止溢出Log.d缓冲区。

我玩了很多次,结果与@misiu_mp非常一致:

10^5 iterations of 1000 calls to an empty static void function: 29ns/call
10^5 iterations of 1000 calls to an empty non-static void function: 34ns/call

静态方法调用总是比非静态方法调用略快,但似乎a)差距已经大大关闭,因为Android 2.3.2和b)仍然需要拨打电话费用一个空方法,静态与否。

然而,观察时间直方图显示出一些有趣的东西。大多数呼叫,无论是静态还是非静态,需要30-40ns,仔细观察数据,它们几乎都是30ns。

enter image description here

使用空循环运行相同的代码(注释掉方法调用)会产生8ns的平均速度,但是,大约3/4的测量时间是0ns,而其余的正好是30ns。

我不确定如何解释这些数据,但我不确定@ misiu_mp的结论是否仍然有效。空静态和非静态方法之间的差异可以忽略不计,测量的优势正好是30ns。话虽如此,似乎运行空方法仍然有一些非零成本。