tuning español code java performance coding-style

java - español - .toArray(new MyClass[0]) or.toArray(new MyClass[myList.size()])?



java profiler (9)

A partir de ArrayList en Java 5 , la matriz ya estará llena si tiene el tamaño correcto (o es más grande). Por consiguiente

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

creará un objeto de matriz, llénelo y devuélvalo a "arr". Por otra parte

MyClass[] arr = myList.toArray(new MyClass[0]);

creará dos matrices. El segundo es una matriz de MyClass con longitud 0. Entonces, hay una creación de objetos para un objeto que se descartará inmediatamente. En cuanto al código fuente sugiere que el compilador / JIT no puede optimizar este para que no se cree. Además, usar el objeto de longitud cero resulta en fundición (es) dentro del método toArray ().

Ver la fuente de ArrayList.toArray ():

public <T> T[] toArray(T[] a) { if (a.length < size) // Make a new array of a''s runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; }

Utilice el primer método para que solo se cree un objeto y evite fundiciones (implícitas pero costosas).

Suponiendo que tengo una ArrayList

ArrayList<MyClass> myList;

Y quiero llamar a Array, ¿hay alguna razón de rendimiento para usar

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

encima

MyClass[] arr = myList.toArray(new MyClass[0]);

?

Prefiero el segundo estilo, ya que es menos detallado, y asumí que el compilador se asegurará de que la matriz vacía realmente no se cree, pero me he estado preguntando si eso es cierto.

Por supuesto, en el 99% de los casos no hace una diferencia en un sentido u otro, pero me gustaría mantener un estilo uniforme entre mi código normal y mis bucles internos optimizados ...


Contraintuitivamente, la versión más rápida, en Hotspot 8, es:

MyClass[] arr = myList.toArray(new MyClass[0]);

He ejecutado un micro benchmark usando jmh, los resultados y el código están debajo, mostrando que la versión con una matriz vacía supera consistentemente a la versión con una matriz preestablecida. Tenga en cuenta que si puede reutilizar una matriz existente del tamaño correcto, el resultado puede ser diferente.

Resultados de referencia (puntaje en microsegundos, más pequeño = mejor):

Benchmark (n) Mode Samples Score Error Units c.a.p.SO29378922.preSize 1 avgt 30 0.025 ▒ 0.001 us/op c.a.p.SO29378922.preSize 100 avgt 30 0.155 ▒ 0.004 us/op c.a.p.SO29378922.preSize 1000 avgt 30 1.512 ▒ 0.031 us/op c.a.p.SO29378922.preSize 5000 avgt 30 6.884 ▒ 0.130 us/op c.a.p.SO29378922.preSize 10000 avgt 30 13.147 ▒ 0.199 us/op c.a.p.SO29378922.preSize 100000 avgt 30 159.977 ▒ 5.292 us/op c.a.p.SO29378922.resize 1 avgt 30 0.019 ▒ 0.000 us/op c.a.p.SO29378922.resize 100 avgt 30 0.133 ▒ 0.003 us/op c.a.p.SO29378922.resize 1000 avgt 30 1.075 ▒ 0.022 us/op c.a.p.SO29378922.resize 5000 avgt 30 5.318 ▒ 0.121 us/op c.a.p.SO29378922.resize 10000 avgt 30 10.652 ▒ 0.227 us/op c.a.p.SO29378922.resize 100000 avgt 30 139.692 ▒ 8.957 us/op

Como referencia, el código:

@State(Scope.Thread) @BenchmarkMode(Mode.AverageTime) public class SO29378922 { @Param({"1", "100", "1000", "5000", "10000", "100000"}) int n; private final List<Integer> list = new ArrayList<>(); @Setup public void populateList() { for (int i = 0; i < n; i++) list.add(0); } @Benchmark public Integer[] preSize() { return list.toArray(new Integer[n]); } @Benchmark public Integer[] resize() { return list.toArray(new Integer[0]); } }


El primer caso es más eficiente.

Eso es porque en el segundo caso:

MyClass[] arr = myList.toArray(new MyClass[0]);

el tiempo de ejecución realmente crea una matriz vacía (con tamaño cero) y luego dentro del método toArray crea otra matriz para ajustarse a los datos reales. Esta creación se realiza usando reflexión usando el siguiente código (tomado de jdk1.5.0_10):

public <T> T[] toArray(T[] a) { if (a.length < size) a = (T[])java.lang.reflect.Array. newInstance(a.getClass().getComponentType(), size); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; }

Al usar la primera forma, evita la creación de una segunda matriz y también evita el código de reflexión.


El segundo es marginalmente más legible, pero hay tan poca mejora que no vale la pena. El primer método es más rápido, sin inconvenientes en el tiempo de ejecución, así que eso es lo que uso. Pero lo escribo de la segunda manera, porque es más rápido escribir. Entonces mi IDE lo señala como una advertencia y ofrece solucionarlo. Con una sola pulsación de tecla, convierte el código del segundo tipo en el primero.


El uso de ''toArray'' con la matriz del tamaño correcto tendrá un mejor rendimiento ya que la alternativa creará primero la matriz de tamaño cero y luego la matriz del tamaño correcto. Sin embargo, como dices, es probable que la diferencia sea insignificante.

Además, tenga en cuenta que el compilador javac no realiza ninguna optimización. En la actualidad, todas las optimizaciones son realizadas por los compiladores JIT / HotSpot en tiempo de ejecución. No estoy al tanto de ninguna optimización sobre ''toArray'' en ninguna JVM.

La respuesta a su pregunta, entonces, es en gran medida una cuestión de estilo, pero por coherencia debe formar parte de los estándares de codificación que adhiere (ya sea documentado o no).


Las JVM modernas optimizan la construcción de matriz reflexiva en este caso, por lo que la diferencia de rendimiento es pequeña. Nombrar la colección dos veces en un código repetitivo no es una buena idea, así que evitaría el primer método. Otra ventaja de la segunda es que funciona con colecciones sincronizadas y simultáneas. Si desea optimizar, reutilice la matriz vacía (las matrices vacías son inmutables y pueden compartirse) o utilice un generador de perfiles (!).


Use el primer caso porque es más fácil y proporciona un código más limpio. La razón de esto es que el método subyacente de funcionamiento del método ToArray es realizar una operación de copia que es O (n). La memoria inmutable no es gran cosa. La gestión de tales objetos se realiza de manera muy eficiente y se ampliará al tamaño deseado.

No optimices demasiado hasta que hayas establecido que algo es un cuello de botella en tu código. Si pasas demasiado tiempo optimizando esto, estás perdiendo el tiempo. Estoy seguro de que hay muchas otras cosas que puedes optimizar, así que diría que utilizas una. Si desea legibilidad y menos verbosidad, tome la primera. Si no te importa el código adicional y la disminución de la claridad, utiliza este último.


código de muestra para entero

Integer[] arr = myList.toArray(new integer[0]);


toArray comprueba que el conjunto pasado sea del tamaño correcto (es decir, lo suficientemente grande como para ajustarse a los elementos de su lista) y, de ser así, lo usa. En consecuencia, si el tamaño de la matriz proporciona menos de lo requerido, se creará una nueva matriz de forma refleja.

En su caso, una matriz de tamaño cero es inmutable, por lo que podría elevarse con seguridad a una variable final estática, lo que podría hacer que su código sea un poco más limpio, lo que evita crear la matriz en cada invocación. Se creará una nueva matriz dentro del método de todos modos, por lo que es una optimización de legibilidad.

Podría decirse que la versión más rápida es pasar la matriz de un tamaño correcto, pero a menos que pueda probar que este código es un cuello de botella de rendimiento, prefiera la legibilidad al rendimiento en tiempo de ejecución hasta que se demuestre lo contrario.