¿Es mejor usar System.arraycopy (…) que un bucle for para copiar matrices?

89

Quiero crear una nueva matriz de objetos juntando dos matrices más pequeñas.

No pueden ser nulos, pero el tamaño puede ser 0.

No puedo elegir entre estas dos formas: ¿son equivalentes o una más eficiente (por ejemplo, system.arraycopy () copia fragmentos completos)?

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
System.arraycopy(publicThings, 0, things, 0, publicThings.length);
System.arraycopy(privateThings, 0, things,  publicThings.length, privateThings.length);

o

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
for (int i = 0; i < things.length; i++) {
    if (i<publicThings.length){
        things[i] = publicThings[i]
    } else {
        things[i] = privateThings[i-publicThings.length]        
    }
}

¿La única diferencia es el aspecto del código?

EDITAR: gracias por la pregunta vinculada, pero parecen tener una discusión sin resolver:

¿Es realmente más rápido si it is not for native types: byte [], Object [], char []? en todos los demás casos, se ejecuta una verificación de tipo, que sería mi caso y entonces sería equivalente ... ¿no?

En otra pregunta vinculada, dicen que the size matters a lot, para un tamaño> 24, system.arraycopy () gana, para un tamaño menor que 10, el bucle manual es mejor ...

Ahora estoy realmente confundido.

Daren
fuente
16
arraycopy()es una llamada nativa, que sin duda es más rápida.
Sotirios Delimanolis
4
¿Ha intentado comparar las dos implementaciones diferentes?
Alex
5
Eche un vistazo aquí: stackoverflow.com/questions/2772152/…
Sotirios Delimanolis
15
Debe elegir el que le resulte más legible y fácil de mantener en el futuro. Solo cuando haya determinado que esta es la fuente de un cuello de botella, debe cambiar su enfoque.
arshajii
1
¡No reinventes la rueda!
camickr

Respuestas:

87
public void testHardCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    for(int i = 0; i < out.length; i++)
    {
        out[i] = bytes[i];
    }
}

public void testArrayCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    System.arraycopy(bytes, 0, out, 0, out.length);
}

Sé que las pruebas JUnit no son realmente las mejores para la evaluación comparativa, pero
testHardCopyBytes tardó 0.157s en completarse
y
testArrayCopyBytes tardó 0.086s en completarse.

Creo que depende de la máquina virtual, pero parece que copia bloques de memoria en lugar de copiar elementos de una sola matriz. Esto aumentaría absolutamente el rendimiento.

EDITAR:
Parece que el rendimiento de System.arraycopy está por todos lados. Cuando se usan cadenas en lugar de bytes y las matrices son pequeñas (tamaño 10), obtengo estos resultados:

    String HC:  60306 ns
    String AC:  4812 ns
    byte HC:    4490 ns
    byte AC:    9945 ns

Así es como se ve cuando las matrices tienen un tamaño de 0x1000000. Parece que System.arraycopy definitivamente gana con matrices más grandes.

    Strs HC:  51730575 ns
    Strs AC:  24033154 ns
    Bytes HC: 28521827 ns
    Bytes AC: 5264961 ns

¡Qué peculiar!

Gracias, Daren, por señalar que las referencias se copian de manera diferente. ¡Hizo de esto un problema mucho más interesante!

Trent pequeño
fuente
2
Gracias por su esfuerzo, pero se perdió puntos aparentemente cruciales: tipos no nativos (cree una clase aleatoria con cualquier signo para que la matriz contenga referencias) y el tamaño ... parece para un tamaño de matriz más pequeño, el bucle for manual es más rápido. ¿Te importaría corregir esto?
Daren
2
¡Oh, vaya, tienes razón! Eso es interesante. Colocar cadenas en esas matrices en lugar de bytes hace una gran diferencia: <<< testHardCopyStrs: 0.161s >>> <<< testArrayCopyStrs: 0.170s >>>
Trent Small
¿Qué resultados obtiene entonces? También sería interesante probar con array size = 10 ... ¡gracias! (Ojalá tuviera mi IDE aquí, estoy codificando sin compilador).
Daren
Bueno, ahora los he envuelto en algunas llamadas System.nanoTime () y configuré size = 10 para ver cuántos nanosegundos toma cada uno. Parece que para arreglos primitivos pequeños, los bucles son mejores; para referencias, arrayCopy es mejor .: <<< testHardCopyBytes: 4491 ns >>> <<< testHardCopyStrs: 56778 ns >>> <<< testArrayCopyBytes: 10265 ns >>> <<< testArrayCopyStrs: 4490 ns >>>
Trent Pequeño
¡Resultados muy interesantes! ¡Muchas gracias! ¿Puede editar su respuesta para incluir esto? Con mucho gusto lo aceptaré para que siga siendo el primero para que todos lo vean ... ya obtuvo mi voto. :)
Daren
36

Arrays.copyOf(T[], int)es más fácil de leer. Internamente usa System.arraycopy()que es una llamada nativa.

¡No puedes conseguirlo más rápido!

Philipp Sander
fuente
parece que puede depender de algunas cosas, pero gracias por señalar esa función que no conocía y que es más fácil de leer. :)
Daren
¡si! Depende de bastantes cosas, como dijo @Svetoslav Tsolov. Solo quería señalar Arrays.copyOf
Philipp Sander
2
copyOfno siempre se puede reemplazar arraycopy, pero es apropiado para este caso de uso.
Blaisorblade
1
NB Si está buscando rendimiento, esto no será tan rápido como System.arraycopy () ya que requiere asignación de memoria. Si esto está en un bucle, las asignaciones repetidas conducirán a la recolección de basura, lo que será un gran impacto en el rendimiento.
Will Calderwood
1
@PhilippSander Solo para comprobar que no estaba siendo estúpido, agregué código para copiar una matriz de 1 MB en mi bucle de juego, que casi nunca activa GC. Con Array.copyOf (), mi DVM llamaba a GC 5 veces por segundo y el juego se volvió extremadamente lento. Creo que es seguro decir que se está produciendo una asignación de memoria.
Will Calderwood
17

Depende de la máquina virtual, pero System.arraycopy debería brindarle lo más cercano que pueda al rendimiento nativo.

He trabajado durante 2 años como desarrollador de Java para sistemas integrados (donde el rendimiento es una gran prioridad) y en todos los lugares donde se podría usar System.arraycopy, lo he usado principalmente / lo he visto usado en código existente. Siempre se prefiere a los bucles cuando el rendimiento es un problema. Sin embargo, si el rendimiento no es un gran problema, seguiría el ciclo. Mucho más fácil de leer.


fuente
Cosas como el tamaño y el tipo de la matriz (básica frente a heredada) parecen afectar el rendimiento.
Daren
2
Sí, no es un 'rendimiento nativo' per se, por eso dije que lo uso 'principalmente' donde puedo (notarás que principalmente gana sobre la copia en bucle). Supongo que la razón es: cuando se trata de una pequeña matriz de un tipo primitivo, el 'costo de llamada' es mayor que el aumento de rendimiento. El uso de JNI puede degradar el rendimiento por la misma razón: el código nativo en sí es rápido, pero llamarlo desde un proceso de Java, no tanto.
Corrección menor, Arrays.copy no es JNI, es intrínseco. Los intrínsecos son mucho más rápidos que JNI. Cómo y cuándo el compilador JIT lo convierte en intrínseco depende del compilador / JVM utilizado.
Nitsan Wakart
1
Arrays.copyno existe, Arrays.copyOfes una función de biblioteca.
Blaisorblade
12

En lugar de confiar en la especulación y la información posiblemente desactualizada, ejecuté algunos puntos de referencia utilizando . De hecho, Caliper viene con algunos ejemplos, ¡incluido un CopyArrayBenchmarkque mide exactamente esta pregunta! Todo lo que tienes que hacer es correr

mvn exec:java -Dexec.mainClass=com.google.caliper.runner.CaliperMain -Dexec.args=examples.CopyArrayBenchmark

Mis resultados se basan en Java HotSpot (TM) 64-Bit Server VM, 1.8.0_31-b13, que se ejecuta en una MacBook Pro de mediados de 2010 (macOS 10.11.6 con Intel Arrandale i7, 8 GiB de RAM). No creo que sea útil publicar los datos de tiempo sin procesar. Más bien, resumiré las conclusiones con las visualizaciones de apoyo.

En resumen:

  • Escribir un forbucle manual para copiar cada elemento en una matriz recién instanciada nunca es ventajoso, ya sea para matrices cortas o largas.
  • Arrays.copyOf(array, array.length)y array.clone()ambos son consistentemente rápidos. Estas dos técnicas tienen un rendimiento casi idéntico; cuál elijas es cuestión de gustos.
  • System.arraycopy(src, 0, dest, 0, src.length)es casi tan rápido como y , pero no tan consistentemente. (Vea el caso de 50000 s.) Por eso, y la verbosidad de la llamada, recomendaría si necesita un control preciso sobre qué elementos se copian y dónde.Arrays.copyOf(array, array.length)array.clone()intSystem.arraycopy()

Aquí están las gráficas de tiempo:

Tiempos para copiar matrices de longitud 5 Tiempos para copiar matrices de longitud 500 Tiempos para copiar matrices de 50000 de longitud

200_exito
fuente
3
¿Hay algo extraño en la copia int? Parece extraño que su copia de matriz sea lenta en ints a gran escala.
whaleberg
6

La ejecución de métodos nativos como Arrays.copyOf(T[], int)tiene algunos gastos generales, pero no significa que no sea rápido, ya que lo está ejecutando utilizando JNI.

La forma más sencilla es escribir un punto de referencia y realizar una prueba.

Puede comprobar que Arrays.copyOf(T[], int)es más rápido que su forciclo normal .

El código de referencia de aquí : -

public void test(int copySize, int copyCount, int testRep) {
    System.out.println("Copy size = " + copySize);
    System.out.println("Copy count = " + copyCount);
    System.out.println();
    for (int i = testRep; i > 0; --i) {
        copy(copySize, copyCount);
        loop(copySize, copyCount);
    }
    System.out.println();
}

public void copy(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        System.arraycopy(src, 1, dst, 0, copySize);
        dst[copySize] = src[copySize] + 1;
        System.arraycopy(dst, 0, src, 0, copySize);
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Arraycopy: " + (end - begin) / 1e9 + " s");
}

public void loop(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        for (int i = copySize - 1; i >= 0; --i) {
            dst[i] = src[i + 1];
        }
        dst[copySize] = src[copySize] + 1;
        for (int i = copySize - 1; i >= 0; --i) {
            src[i] = dst[i];
        }
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Man. loop: " + (end - begin) / 1e9 + " s");
}

public int[] newSrc(int arraySize) {
    int[] src = new int[arraySize];
    for (int i = arraySize - 1; i >= 0; --i) {
        src[i] = i;
    }
    return src;
}

System.arraycopy()usa JNI (Java Native Interface) para copiar una matriz (o partes de ella), por lo que es increíblemente rápido, como puede confirmar aquí

Rahul Tripathi
fuente
Este código usa int []. ¿Puedes probarlo con String [] (inicializado con diferentes valores: "1", "2", etc. ya que son inmutables
Daren
1
JNI es muy lento . System.arraycopyno lo usa.
Chai T. Rex
No, System.arraycopyno usa JNI, que es solo para llamar a bibliotecas de terceros. En cambio, es una llamada nativa, lo que significa que hay una implementación nativa en la VM para ella.
spheenik
6

No es posible que Arrays.copyOfsea ​​más rápido System.arraycopyya que esta es la implementación de copyOf:

public static int[] copyOf(int[] original, int newLength) {
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}
DimaD
fuente
4

System.arraycopy()es una llamada nativa que copia la operación directamente en la memoria. La copia de memoria única siempre será más rápida que su bucle for

Nandkumar Tekale
fuente
3
He leído que para tipos no nativos (cualquier clase creada, como la mía) puede que no sea tan eficiente ... y para tamaños pequeños (mi caso) manual para bucle puede ser mejor ... ¿te importaría comentar?
Daren
De hecho, System.arraycopy () tiene algo de sobrecarga, por lo que para matrices pequeñas (n = ~ 10) un bucle es en realidad más rápido
RecursiveExceptionException