¿Por qué System.arraycopy es nativo en Java?

84

Me sorprendió ver en la fuente de Java que System.arraycopy es un método nativo.

Por supuesto, la razón es porque es más rápido. Pero, ¿qué trucos nativos puede emplear el código que lo hacen más rápido?

¿Por qué no simplemente recorrer la matriz original y copiar cada puntero a la nueva matriz? Seguramente esto no es tan lento y engorroso.

James B
fuente

Respuestas:

82

En código nativo, se puede hacer con un solo memcpy/ memmove, a diferencia de n operaciones de copia distintas. La diferencia de rendimiento es sustancial.

Péter Török
fuente
@Peter, entonces, ¿dentro del código nativo puedes jugar con el modelo de memoria de Java? (Nunca he tenido motivos para hacer ningún malarkey nativo)
James B
8
En realidad, solo algunas subcasas de arraycopypodrían implementarse usando memcpy/ memmove. Otros requieren una verificación de tipo en tiempo de ejecución para cada elemento copiado.
Stephen C
1
@ Stephen C, interesante, ¿por qué?
Péter Török
3
@ Péter Török: considere la posibilidad de copiar de un objeto Object[]poblado Stringa un String[]. Consulte el último párrafo de java.sun.com/javase/6/docs/api/java/lang/…
Stephen C
3
Peter, Object [] y byte [] + char [] son ​​los que se copian con más frecuencia, ninguno de ellos requiere una verificación de tipo explícita. El compilador es lo suficientemente inteligente como para NO verificar a menos que sea necesario y virtualmente en el 99.9% de los casos no lo es. Lo curioso es que las copias de tamaño pequeño (menos de una línea de caché) son bastante dominantes, por lo que "memcpy" para cosas de tamaño pequeño es realmente importante.
bestsss
16

No se puede escribir en Java. El código nativo puede ignorar o eludir la diferencia entre matrices de Object y matrices de primitivas. Java no puede hacer eso, al menos no de manera eficiente.

Y no se puede escribir con un solo memcpy(), debido a la semántica requerida por las matrices superpuestas.

Marqués de Lorne
fuente
5
Bien, memmoveentonces. Aunque no creo que haya mucha diferencia en el contexto de esta pregunta.
Péter Török
Tampoco memmove (), vea los comentarios de @Stephen C sobre otra respuesta.
Marqués de Lorne
Ya vi eso, ya que resultó ser mi propia respuesta ;-) Pero gracias de todos modos.
Péter Török
1
@Geek Arrays que se superponen. Si las matrices de origen y destino son iguales y solo las compensaciones son diferentes, el comportamiento se especifica cuidadosamente y memcpy () no cumple.
Marqués de Lorne
1
¿No se puede escribir en Java? ¿No se podría escribir un método genérico para manejar subclases de Object y luego uno para cada uno de los tipos primitivos?
Michael Dorst
10

Por supuesto, depende de la implementación.

HotSpot lo tratará como un código "intrínseco" e insertará en el sitio de la llamada. Eso es código máquina, no código C antiguo lento. Esto también significa que los problemas con la firma del método desaparecen en gran medida.

Un bucle de copia simple es lo suficientemente simple como para que se le puedan aplicar optimizaciones obvias. Por ejemplo, desenrollado de bucle. Exactamente lo que sucede depende nuevamente de la implementación.

Tom Hawtin - tackline
fuente
2
esta es una respuesta muy decente :), esp. la mención de los intrínsecos. sin ellos, la iteración simple podría ser más rápida, ya que el JIT generalmente la desenrolla de todos modos
mejor del
4

En mis propias pruebas, System.arraycopy () para copiar matrices de múltiples dimensiones es de 10 a 20 veces más rápido que intercalar para bucles:

float[][] foo = mLoadMillionsOfPoints(); // result is a float[1200000][9]
float[][] fooCpy = new float[foo.length][foo[0].length];
long lTime = System.currentTimeMillis();
System.arraycopy(foo, 0, fooCpy, 0, foo.length);
System.out.println("native duration: " + (System.currentTimeMillis() - lTime) + " ms");
lTime = System.currentTimeMillis();

for (int i = 0; i < foo.length; i++)
{
    for (int j = 0; j < foo[0].length; j++)
    {
        fooCpy[i][j] = foo[i][j];
    }
}
System.out.println("System.arraycopy() duration: " + (System.currentTimeMillis() - lTime) + " ms");
for (int i = 0; i < foo.length; i++)
{
    for (int j = 0; j < foo[0].length; j++)
    {
        if (fooCpy[i][j] != foo[i][j])
        {
            System.err.println("ERROR at " + i + ", " + j);
        }
    }
}

Esto imprime:

System.arraycopy() duration: 1 ms
loop duration: 16 ms
jumar
fuente
9
Aunque esta pregunta es antigua, solo para que conste: este NO es un punto de referencia justo (y mucho menos la pregunta de si dicho punto de referencia tendría sentido en primer lugar). System.arraycopyhace una copia superficial (solo se copian las referencias a los float[]s internos ), mientras que sus forbucles anidados realizan una copia profunda ( floatpor float). Un cambio en fooCpy[i][j]se reflejará en el foouso System.arraycopy, pero no usará los forbucles anidados .
misberner
4

Hay unas pocas razones:

  1. Es poco probable que el JIT genere un código de bajo nivel tan eficiente como un código C escrito manualmente. El uso de C de bajo nivel puede permitir muchas optimizaciones que son casi imposibles de realizar para un compilador JIT genérico.

    Consulte este enlace para ver algunos trucos y comparaciones de velocidad de implementaciones de C escritas a mano (memcpy, pero el principio es el mismo): marque esto Optimizing Memcpy mejora la velocidad

  2. La versión C es prácticamente independiente del tipo y tamaño de los miembros de la matriz. No es posible hacer lo mismo en Java, ya que no hay forma de obtener el contenido de la matriz como un bloque de memoria sin formato (por ejemplo, puntero).

Hrvoje Prgeša
fuente
1
El código Java se puede optimizar. De hecho, lo que realmente sucede es que se genera un código de máquina que es más eficiente que el C.
Tom Hawtin - tackline
Estoy de acuerdo en que a veces el código JITed estará mejor optimizado localmente, ya que sabe en qué procesador se está ejecutando. Sin embargo, dado que es "justo a tiempo", nunca podrá utilizar todas esas optimizaciones no locales que tardan más en ejecutarse. Además, nunca podrá coincidir con el código C elaborado a mano (que también podría tener en cuenta el procesador y anular parcialmente las ventajas de JIT, ya sea compilando para un procesador específico o mediante algún tipo de verificación de tiempo de ejecución).
Hrvoje Prgeša
1
Creo que el equipo del compilador Sun JIT disputaría muchos de esos puntos. Por ejemplo, creo que HotSpot realiza una optimización global para eliminar el envío de métodos innecesarios, y no hay ninguna razón por la que un JIT no pueda generar un código específico del procesador. Luego está el punto en el que un compilador JIT puede realizar una optimización de rama según el comportamiento de ejecución de la aplicación actual.
Stephen C
@Stephen C: excelente punto sobre las optimizaciones de rama, aunque también puede realizar perfiles de rendimiento estáticos con compiladores C / C ++ para lograr un efecto similar. También creo que el hotspot tiene 2 modos de operación: las aplicaciones de escritorio no usarán todas las optimizaciones disponibles para lograr un tiempo de inicio razonable, mientras que las aplicaciones de servidor se optimizarán de manera más agresiva. Con todo, obtienes algunas ventajas, pero también pierdes algunas.
Hrvoje Prgeša
1
System.arrayCopy no se implementa usando C, lo que invalida esta respuesta
Nitsan Wakart