2010-12-03 5 views
7

He creado una clase para manejar mis salidas de depuración para que no tenga que eliminar todas mis salidas de registro antes de la publicación.Android: ¿Cuánta sobrecarga se genera ejecutando un método vacío?

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

Después de leer otra pregunta, he aprendido que el contenido de la sentencia if no se compilan si el Release.DEBUG constante es falsa.

Lo que yo quiero saber es la cantidad de sobrecarga se genera mediante la ejecución de este método vacío? (Una vez que se elimina la cláusula if, no hay código en el método) ¿Va a tener algún impacto en mi aplicación? Obviamente, el rendimiento es un gran problema cuando se escribe para teléfonos móviles = P

Gracias

Gary

Respuesta

14

Las mediciones realizadas en el Nexus S con Android 2.3.2:

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 

de control:

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 

He repetido las mediciones varias veces. No se encontraron desviaciones significativas. Se puede ver que el coste por llamada puede variar en gran medida dependiendo de la carga de trabajo (posiblemente debido a la compilación JIT), pero 3 se pueden sacar conclusiones:

  1. Dalvik/java aspira a optimizar el código muerto

  2. llamadas a funciones estáticas se pueden optimizar mucho mejor que no estático (funciones no estáticos son virtuales y necesitan ser atendidos en una tabla virtual)

  3. el costo de nexo s no es mayor que 70 ns/llamada (Eso es ~ 70 ciclos de CPU) y es comparable con el costo de un vacío para iteración de bucle (es decir un incremento y una verificación de condición en una variable local)

Observe que en su caso siempre se evaluará el argumento de cadena.Si realiza la concatenación de cadenas, esto implicará la creación de cadenas intermedias. Esto será muy costoso e involucrará una gran cantidad de gc. Por ejemplo la ejecución de una función:

void empty(String string){ 
} 

llamado con argumentos tales como

empty("Hello " + 42 + " this is a string " + count); 

10^4 iteraciones de 100 llamadas de ese tipo lleva 10s. Eso es 10us/call, es decir ~ 1000 veces más lento que solo una llamada vacía. También produce una gran cantidad de actividad GC. La única forma de evitar esto es alineando manualmente la función, es decir, utilice la instrucción >> if < < en lugar de llamar a la función de depuración. Es feo, pero la única forma de hacerlo funcionar.

+0

Sí, realmente es el generador de cuerdas el que te muerde si no tienes comentarios. la razón es que el compilador jit or aot en tiempo de ejecución no puede determinar de antemano si la creación de cadena fallará, lo que afecta el flujo del programa. y si está desordenado. un precompilador sería la manera correcta, pero en este momento no hay una manera fácil de hacerlo con las herramientas predeterminadas de Android, creo. –

2

a menos que llamar esto desde dentro de un bucle anidado, yo no me preocuparía por eso.

2

Un buen compilador extrae todo el método vacío, dando como resultado ninguna sobrecarga en absoluto. No estoy seguro si el compilador de Dalvik ya lo hace, pero sospecho que es probable, al menos desde la llegada del compilador Just-in-time con Froyo.

Consulte también: Inline expansion

+0

Consígase una copia de apktool, descompile su aplicación y vea si ha sido optimizada fuera del bytecode dalvik. Hay etapas posteriores en las que podría optimizarse durante la instalación o la carga en tiempo de ejecución, pero parece ser la más obvia. –

+0

si descompila la aplicación en realidad no ve las optimizaciones art o dalvik, solo verá las optimizaciones hechas por el compilador java y el convertidor dex (y posiblemente proguard). necesitarías extraer las cosas almacenadas en la memoria caché desde adentro. –

2

En términos de rendimiento de la sobrecarga de la generación de los mensajes que van pasando a la función de depuración van a ser mucho más grave, ya que su probable que no las asignaciones de memoria, por ejemplo,

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

Lo que seguirá ocurriendo incluso a través del mensaje es binned. Desafortunadamente que realmente necesita el "si (Release.DEBUG)" en torno a las llamadas a esta función en lugar de dentro de la propia función de si su objetivo es el rendimiento, y verá esto en una gran cantidad de código androide.

1

Esta es una pregunta interesante y me gusta el análisis de @misiu_mp, así que pensé que lo actualizaría con una prueba de 2016 en un Nexus 7 con Android 6.0.1. Aquí está el código de prueba:

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() { } 

se añadió el sleep() para evitar desbordamiento del búfer Log.d.

He jugado un poco con él muchas veces y los resultados fueron bastante consistentes con @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 

La llamada al método estático siempre fue ligeramente más rápido que la llamada al método no estático, pero parecería que una) la brecha se ha cerrado significativamente desde Android 2.3.2 yb) todavía hay un costo para hacer llamadas a un método vacío, estático o no.

Mirar un histograma de veces revela algo interesante, sin embargo. La mayoría de las llamadas, ya sean estáticas o no, toman entre 30 y 40 segundos, y mirando de cerca los datos, son prácticamente todos exactamente 30.

enter image description here

Ejecutar el mismo código con bucles vacías (comentando las llamadas a métodos) produce una velocidad media de 8 ns, sin embargo, aproximadamente 3/4 de los tiempos medidos son 0ns mientras que el resto son exactamente 30 ns.

No estoy seguro de cómo explicar estos datos, pero no estoy seguro de que las conclusiones de @ misiu_mp aún se mantengan. La diferencia entre los métodos estáticos vacíos y no estáticos es insignificante, y la preponderancia de las mediciones es exactamente 30ns. Dicho esto, parece que todavía hay un costo no nulo para ejecutar métodos vacíos.

+0

si agrega una creación de cadena al método de llamada, como un mensaje de depuración típico sería "estado" + int, creo que no verá ninguna diferencia (aunque no se haga nada con la cadena dentro del método estático). –

Cuestiones relacionadas