2012-06-26 15 views
8

A seguito di una domanda relativa al modo in cui la JVM implementa creazione di stringhe in base a char [], ho detto che non l'iterazione avviene quando il char [] viene copiato all'interno della nuova stringa , dal momento che System.arraycopy viene chiamato alla fine, che copia la memoria desiderata utilizzando una funzione come memcpy a livello nativo, dipendente dall'implementazione (the original question).OpenJDK attuazione System.arraycopy

volevo controllare che per me, così ho scaricato il codice sorgente OpenJDK 7 e ha iniziato la navigazione esso. Ho trovato l'attuazione System.arraycopy nella OpenJDK codice sorgente C++, in 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 

Se gli elementi non hanno bisogno di tipo controlli (questo è il caso con, per esempio, primitivi tipo di dati array), Copy: : conjoin_oops_atomic viene chiamato.

La funzione Copy::conjoint_oops_atomic risiede nel '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); 
} 

Ora siamo dipendenti dalla piattaforma, come l'operazione di copia dispone di un'implementazione diversa, basata su OS/architettura. Vado con Windows come esempio. 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--; 
    } 
} 
} 

E ... con mia grande sorpresa, che scorre gli elementi (i valori OOP), la copia di una per una (apparentemente). Qualcuno può spiegare perché la copia è fatta, anche a livello nativo, iterando attraverso gli elementi dell'array?

risposta

4

Poiché il jint mappe più strettamente per int che associa più strettamente alla vecchia architettura hardware WORD, che è sostanzialmente la stessa dimensione della larghezza del bus dati.

Le architetture di memoria e di elaborazione della CPU di oggi sono progettati per tentare di lavorazione anche in caso di cache miss, e locazioni di memoria tendono a pre-fetch blocchi. Il codice che stai guardando non è così "cattivo" in termini di prestazioni come potresti pensare. L'hardware è più intelligente e, se non si esegue il profilo, le routine di recupero "intelligenti" potrebbero effettivamente non aggiungere nulla (o rallentare l'elaborazione).

quando si è presentato per architetture hardware, è necessario introdurre a quelli semplici. Quelli moderni fanno molto di più, quindi non si può presumere che il codice che sembra inefficiente sia effettivamente inefficiente. Ad esempio, quando viene eseguita una ricerca della memoria per valutare la condizione su un'istruzione if, spesso entrambi i rami dell'istruzione if vengono eseguiti mentre si sta verificando la ricerca e il ramo "false" dell'elaborazione viene scartato dopo che i dati diventano disponibili per valutare la condizione. Se vuoi essere efficiente, devi profilare e poi agire sui dati profilati.

Guardare il ramo sulla sezione opcode JVM. Vedrai che è (o forse era solo) una stranezza macro ifdef a supportare (in una volta) tre diversi modi di saltare al codice che gestiva l'opcode. Questo perché i tre diversi modi in realtà hanno apportato una significativa differenza di prestazioni sulle diverse architetture Windows, Linux e Solaris.

Forse avrebbero potuto includere le routine MMX, ma non mi hanno detto che SUN non pensava che fosse sufficiente un guadagno di prestazioni sull'hardware moderno per preoccuparsene.

+0

Wow, grazie! È stato un po 'difficile guardare per la prima volta attraverso l'implementazione di OpenJDK, quindi mi aspettavo di avere qualcosa di sbagliato. : P Quindi, come pensi che questo ottimizzazione avvenga? Ho fatto alcuni test e System.arraycopy è due volte più veloce nella copia di 10000 pollici rispetto a un normale modo Java. In C++ un'attività simile è ancora notevolmente più veloce, anche se i risultati potrebbero essere influenzati da varie ottimizzazioni del compilatore. –

+0

Una copia C++ non ha un garbage collector in esecuzione su un thread separato. Anche se non generi rifiuti, il raccoglitore deve rubare alcuni cicli per verificare che non ha lavoro da fare. Non sono sicuro che il compilatore stia srotolando il ciclo di arraycopy o se l'hardware sta eseguendo il precaricamento dell'intero blocco dell'array nella cache. In effetti, con l'ottimizzazione del microcodice, è al di là della mia profondità di conoscenza. Ecco perché la profilatura è così importante, è il test che dimostra che l'ottimizzazione è stata utile. –

Problemi correlati