2012-06-26 15 views
8

Después de una pregunta relacionada con la forma en que la JVM implementa la creación de cadenas basadas en char [], mencioné que no tiene lugar ninguna iteración cuando el char [] se copia al interior de la nueva cadena , ya que eventualmente se llama a System.arraycopy, que copia la memoria deseada utilizando una función como memcpy en un nivel nativo dependiente de la implementación (the original question).Implementación OpenJDK de System.arraycopy

Quería comprobarlo por mí mismo, así que descargué el código fuente de Openjdk 7 y comencé a explorarlo. me encontré con la implementación de System.arraycopy en el código fuente del OpenJDK C++, en openjdx/hotspot/src/share/vm/oops/objArrayKlass.cpp:

if (stype == bound || Klass::cast(stype)->is_subtype_of(bound)) { 
    // elements are guaranteed to be subtypes, so no check necessary 
    bs->write_ref_array_pre(dst, length); 
    Copy::conjoint_oops_atomic(src, dst, length); 
} else { 
    // slow case: need individual subtype checks 

Si los elementos necesitan ninguna comprobación de tipo (que es el caso de, por ejemplo, primitivas matrices de tipos de datos), copiar: : se llama a conjoin_oops_atomic.

La función Copy::conjoint_oops_atomic reside en 'copy.hpp':

// overloaded for UseCompressedOops 
static void conjoint_oops_atomic(narrowOop* from, narrowOop* to, size_t count) { 
    assert(sizeof(narrowOop) == sizeof(jint), "this cast is wrong"); 
    assert_params_ok(from, to, LogBytesPerInt); 
    pd_conjoint_jints_atomic((jint*)from, (jint*)to, count); 
} 

Estamos dependientes de la plataforma, ya que la operación de copia tiene una aplicación diferente, basado en OS/arquitectura. Iré con Windows como ejemplo. openjdk\hotspot\src\os_cpu\windows_x86\vm\copy_windows_x86.inline.hpp:

static void pd_conjoint_oops_atomic(oop* from, oop* to, size_t count) { 
// Do better than this: inline memmove body NEEDS CLEANUP 
if (from > to) { 
    while (count-- > 0) { 
    // Copy forwards 
    *to++ = *from++; 
    } 
} else { 
    from += count - 1; 
    to += count - 1; 
    while (count-- > 0) { 
    // Copy backwards 
    *to-- = *from--; 
    } 
} 
} 

Y ... para mi sorpresa, se itera a través de los elementos (los valores POO), copiarlos uno por uno (aparentemente). ¿Alguien puede explicar por qué se hace la copia, incluso a nivel nativo, al recorrer los elementos de la matriz?

Respuesta

4

Porque el jint se asocia más estrechamente al int que más se acerca a la antigua arquitectura de hardware WORD, que es básicamente del mismo tamaño que el ancho del bus de datos.

Las arquitecturas de memoria y el procesamiento de la CPU de hoy están diseñados para intentar el procesamiento incluso en el caso de una falta de caché, y las ubicaciones de la memoria tienden a preseleccionar los bloques. El código que estás viendo no es tan "malo" en rendimiento como podrías pensar. El hardware es más inteligente, y si no lo hace, sus rutinas de búsqueda "inteligentes" en realidad podrían no agregar nada (o incluso ralentizar el procesamiento).

Cuando conozca las arquitecturas de hardware, debe conocer las sencillas. Los modernos hacen mucho más, por lo que no se puede suponer que el código que parece ineficiente en realidad es ineficiente. Por ejemplo, cuando se realiza una búsqueda de memoria para evaluar la condición en una instrucción if, a menudo ambas ramas de la instrucción if se ejecutan mientras se realiza la búsqueda, y la rama "falsa" del proceso se descarta después de que los datos estén disponibles para evaluar la condición. Si quiere ser eficiente, debe perfilar y luego actuar sobre los datos perfilados.

Mire la rama en la sección de código de operación JVM. Verás que es (o quizás, simplemente) una macro rareza ifdef para admitir (a la vez) tres formas diferentes de saltar al código que maneja el opcode. Eso se debió a que las tres formas diferentes realmente hicieron una diferencia de rendimiento significativa en las diferentes arquitecturas de Windows, Linux y Solaris.

Quizás podrían haber incluido rutinas MMX, pero que no me dijeron que SUN no pensó que era suficiente una ganancia de rendimiento en el hardware moderno para preocuparse por ello.

+0

¡Guau, gracias! Fue un poco confuso mirar a través de la implementación OpenJDK por primera vez, así que esperaba haber obtenido algo mal. : P Entonces, ¿cómo crees que se lleva a cabo esta optimización? Hice algunas pruebas, y System.arraycopy es dos veces más rápido copiando 10000 ints que una forma Java normal. En C++, una tarea similar es notablemente más rápida, aunque los resultados pueden verse afectados por varias optimizaciones del compilador. –

+0

Una copia en C++ no tiene un recolector de basura ejecutándose en un hilo separado. Incluso si no genera basura, el recolector debe robar unos ciclos para verificar que no tiene trabajo que hacer. No estoy seguro de si el compilador está desenrollando el loop de arraycopy o si el hardware está captando previamente todo el bloque de la matriz en caché. De hecho, con la optimización de microcódigo, está más allá de mi conocimiento profundo. Es por eso que la creación de perfiles es tan importante, es la prueba que demuestra que la optimización valió la pena. –

Cuestiones relacionadas