length data array java performance for-loop benchmarking bytecode

data - static array java



¿El uso de una variable intermedia en lugar de array.length hace que su for-loop sea más rápido? (2)

La sección "Sugerencias de rendimiento" en la documentación de Android tiene un reclamo bastante audaz:

one() es más rápido. Extrae todo en variables locales, evitando las búsquedas. Solo la longitud de la matriz ofrece un beneficio de rendimiento.

donde se refiere a este fragmento de código:

int len = localArray.length; for (int i = 0; i < len; ++i) { sum += localArray[i].mSplat; }

Esto me sorprendió mucho porque localArray.length solo está accediendo a un número entero y si usa una variable intermedia, tendría que hacer ese mismo paso exactamente otra vez. ¿Estamos realmente diciendo que una variable intermedia que solo tiene que ir a x lugar de yx es más rápida?

Eché un vistazo a esta pregunta que trata sobre la misma idea, pero utiliza una lista de arrays y su .size() posterior .size() lugar. Aquí el consenso parecía ser que no habría diferencia, ya que esa llamada al método probablemente se incorporará de todos modos a un acceso entero (que es exactamente el escenario que tenemos aquí).

Así que tomé el bytecode para ver si eso podía decirme algo.

Dado el siguiente código fuente:

public void MethodOne() { int[] arr = new int[5]; for (int i = 0; i < arr.length; i++) { } } public void MethodTwo() { int[] arr = new int[5]; int len = arr.length; for (int i = 0; i < len; i++) { } }

Obtengo el siguiente bytecode:

public void MethodOne(); Code: 0: iconst_5 1: newarray int 3: astore_1 4: iconst_0 5: istore_2 6: iload_2 7: aload_1 8: arraylength 9: if_icmpge 18 12: iinc 2, 1 15: goto 6 18: return public void MethodTwo(); Code: 0: iconst_5 1: newarray int 3: astore_1 4: aload_1 5: arraylength 6: istore_2 7: iconst_0 8: istore_3 9: iload_3 10: iload_2 11: if_icmpge 20 14: iinc 3, 1 17: goto 9 20: return

Difieren en las siguientes instrucciones:

Método uno

6: iload_2 7: aload_1 8: arraylength 9: if_icmpge 18 12: iinc 2, 1 15: goto 6 18: return

Método dos

9: iload_3 10: iload_2 11: if_icmpge 20 14: iinc 3, 1 17: goto 9 20: return

Ahora, no estoy 100% seguro de cómo tengo que interpretar 8: arraylength pero creo que eso solo indica el campo al que estás accediendo. El primer método carga el contador de índice y la matriz y accede al campo de arraylength la arraylength , mientras que el segundo método carga el contador de índice y la variable intermedia.

También comparé los dos métodos con JMH (10 calentamientos, 10 iteraciones, 5 tenedores) lo que me da el siguiente resultado de evaluación comparativa:

c.m.m.Start.MethodOne thrpt 50 3447184.351 19973.900 ops/ms c.m.m.Start.MethodTwo thrpt 50 3435112.281 32639.755 ops/ms

que me dice que la diferencia es insignificante a inexistente.

¿Cuál es la afirmación de la documentación de Android de que se utiliza una variable intermedia en una condición de bucle?


En realidad, no, no hizo que el ciclo sea más rápido, tu idea es correcta cuando se usa String.length ()
la diferencia es que array.length es solo un campo que tiene un valor que usted simplemente usa directamente.
String.length () es un método que necesita un tiempo para ejecutarse.


Usted malentendió la documentación. No se están refiriendo a lo que has descrito (aunque no te culpo, deberían haber puesto más esfuerzo en esos documentos :)).

Extrae todo en variables locales, evitando las búsquedas.

Al evitar las búsquedas, se refieren al costo de acceso variable local o de campo . Acceder al campo ( mArray en el ejemplo en los documentos) requiere cargar this primero, luego cargar el campo basado en el desplazamiento fijo de this .

Después de un tiempo, JIT probablemente sepa qué está pasando y optimice también el acceso de campo (si el campo no es volatile o si ocurre alguna otra forma de sincronización en el ciclo) y reescriba el código para que todas las variables que participan en el se accede / cambia en los registros y cachés de la CPU hasta el final del ciclo.

En general, podría ser mucho más trabajo para JIT determinar si es seguro optimizar el acceso a la longitud de la matriz a la que se hace referencia desde un campo en comparación con la referencia almacenada en una variable local. Digamos que tenemos el siguiente ciclo:

for (int i = 0; i < array.length; ++i) { process(array[i]); }

Si array es un campo y el process invoca miles de líneas de código complejo, entonces JIT puede tener dificultades para comprobar si el campo de la array se cambia en algún lugar del ciclo para hacer referencia a alguna otra matriz que tenga una longitud diferente.

Obviamente, es mucho más fácil verificar si la variable local se cambia en este caso (tres líneas de código).