osservare la seguente definizione di una sottoclasse filo (l'intero file sorgente Java eseguibile è incluso alla fine della domanda per la vostra convenienza):Array allocazione e accesso sulla macchina virtuale e la memoria contesa Java
final class Worker extends Thread {
Foo[] array = new Foo[1024];
int sz;
public Worker(int _sz) {
sz = _sz;
}
public void run() {
//Foo[] arr = new Foo[1024];
Foo[] arr = array;
loop(arr);
}
public void loop(Foo[] arr) {
int i = 0;
int pos = 512;
Foo v = new Foo();
while (i < sz) {
if (i % 2 == 0) {
arr[pos] = v;
pos += 1;
} else {
pos -= 1;
v = arr[pos];
}
i++;
}
}
}
Spiegazione: Il programma avvia -Dpar
tali thread e imposta sz
di ogni thread su -Dsize/-Dpar
, dove -Dsize
e -Dpar
vengono impostati tramite la riga di comando durante l'esecuzione del programma. Ogni oggetto thread ha un campo array
che viene inizializzato con un nuovo array -element 1024
. Il ragionamento è che vogliamo dividere una quantità uguale di lavoro tra un numero diverso di thread - ci aspettiamo che il programma si riduca.
Ogni thread viene quindi avviato e viene misurato il tempo necessario per completare tutti i thread. Facciamo più misurazioni per contrastare qualsiasi effetto correlato alla JIT, come mostrato di seguito. Ogni thread fa un ciclo. All'interno del ciclo, il thread legge un elemento nella posizione 512
nell'array in iterazioni uniformi e scrive lo stesso elemento su 512
in iterazioni dispari. Solo le variabili locali sono modificate diversamente.
Il programma completo è in basso.
Analisi:
Testato con -verbose:gc
- non v'è alcun garbage collection che si verificano durante l'esecuzione di questo programma.
comando Esegui:
java -Xmx512m -Xms512m -server -Dsize=500000000 -Dpar=1 org.scalapool.bench.MultiStackJavaExperiment 7
CASO 1: tempi di esecuzione per 1,2,4,8
discussioni, in questo ordine (7 ripetizioni):
>>> All running times: [2149, 2227, 1974, 1948, 1803, 2283, 1878]
>>> All running times: [1140, 1124, 2022, 1141, 2028, 2004, 2136]
>>> All running times: [867, 1022, 1457, 1342, 1436, 966, 1531]
>>> All running times: [915, 864, 1245, 1243, 948, 790, 1007]
Il mio pensiero era che la scala non lineare è dovuto a quanto sostiene la memoria . Per inciso, le iterazioni iniziali in realtà fanno meglio - questo potrebbe essere dovuto al fatto che in diverse iterazioni gli array sono allocati in diverse aree di memoria.
CASO 2: Successivamente, commento la linea Foo[] arr = array
nel metodo del filo run
e allocare una nuova matrice nel metodo run
stessa: Foo[] arr = new Foo[1024]
. Misure:
>>> All running times: [2053, 1966, 2089, 1937, 2046, 1909, 2011]
>>> All running times: [1048, 1178, 1100, 1194, 1367, 1271, 1207]
>>> All running times: [578, 508, 589, 571, 617, 643, 645]
>>> All running times: [330, 299, 300, 322, 331, 324, 575]
Questa volta, tutto scala in modo uniforme come previsto. Non avrei immaginato che il luogo in cui era stato assegnato l'array avesse un ruolo qualsiasi, ma ovviamente lo fa in qualche modo. Il mio pensiero era che gli array fossero stati precedentemente assegnati così vicini l'uno all'altra da provocare un conflitto di memoria.
CASO 3: Per verificare questa ipotesi, ho senza commenti la linea Foo[] arr = array
di nuovo, ma questa volta inizializzato il campo array
-new Foo[32000]
per garantire che la locazione di memoria in fase di scrittura sono sufficientemente distanti tra loro. Quindi, qui stiamo usando l'array assegnato durante la creazione dell'oggetto thread, la differenza con CASE1 è solo che l'array è più grande.
>>> All running times: [2113, 1983, 2430, 2485, 2333, 2359, 2463]
>>> All running times: [1172, 1106, 1163, 1181, 1142, 1169, 1188]
>>> All running times: [578, 677, 614, 604, 583, 637, 597]
>>> All running times: [343, 327, 320, 330, 353, 320, 320]
Quindi, contesa di memoria sembra essere la causa di questo.
Le informazioni sulla piattaforma:
Ubuntu Server 10.04.3 LTS
8 core Intel(R) Xeon(R) CPU X5355 @2.66GHz
~20GB ram
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)
Domanda: Questo è ovviamente un problema di memoria-contesa. Ma perché sta succedendo questo?
È in corso l'analisi di evasione? In tal caso, significa che l'intero array viene allocato nello stack quando viene creato nel metodo
run
in CASE2? Quali sono le condizioni esatte per questa ottimizzazione del runtime? Sicuramente l'array non è allocato nello stack per 1 milione di elementi?Anche se la matrice viene allocato sullo stack invece di essere allocati in mucchio, due matrice accessi da diversi fili deve essere diviso per almeno 512 * 4bytes = 2kb anche in CASE1, laddove gli array sono ! Questo è decisamente più grande di qualsiasi cache-line L1. Se questi effetti sono dovuti a una condivisione errata, come possono le scritture su più linee di cache totalmente indipendenti influenzare così tanto le prestazioni? (Si suppone qui che ogni array occupi un blocco di memoria contiguo sulla JVM, che viene assegnato quando viene creato l'array. Non sono sicuro che sia valido.Un'altra ipotesi è che le scritture di array non arrivano fino a memoria, ma Cache L1 invece, poiché Intel Xeon ha un'architettura ccNUMA - correggimi se ho torto)
E 'possibile che ogni thread abbia la propria parte di heap locale in cui assegna autonomamente nuovi oggetti, e questo è la causa della contesa inferiore quando l'array è allocato nel thread? In tal caso, in che modo viene raccolta l'area della garbage heap se i riferimenti vengono condivisi?
Perché l'aumento della dimensione della matrice su ~ 32000 elementi ha migliorato la scalabilità (diminuzione della contesa della memoria)? Che cosa esattamente nella gerarchia della memoria è la causa di questo?
Si prega di essere precisi e sostenere le vostre affermazioni con riferimenti.
Grazie!
L'intero programma Java eseguibile:
import java.util.ArrayList;
class MultiStackJavaExperiment {
final class Foo {
int x = 0;
}
final class Worker extends Thread {
Foo[] array = new Foo[1024];
int sz;
public Worker(int _sz) {
sz = _sz;
}
public void run() {
Foo[] arr = new Foo[1024];
//Foo[] arr = array;
loop(arr);
}
public void loop(Foo[] arr) {
int i = 0;
int pos = 512;
Foo v = new Foo();
while (i < sz) {
if (i % 2 == 0) {
arr[pos] = v;
pos += 1;
} else {
pos -= 1;
v = arr[pos];
}
i++;
}
}
}
public static void main(String[] args) {
(new MultiStackJavaExperiment()).mainMethod(args);
}
int size = Integer.parseInt(System.getProperty("size"));
int par = Integer.parseInt(System.getProperty("par"));
public void mainMethod(String[] args) {
int times = 0;
if (args.length == 0) times = 1;
else times = Integer.parseInt(args[0]);
ArrayList <Long> measurements = new ArrayList <Long>();
for (int i = 0; i < times; i++) {
long start = System.currentTimeMillis();
run();
long end = System.currentTimeMillis();
long time = (end - start);
System.out.println(i + ") Running time: " + time + " ms");
measurements.add(time);
}
System.out.println(">>>");
System.out.println(">>> All running times: " + measurements);
System.out.println(">>>");
}
public void run() {
int sz = size/par;
ArrayList <Thread> threads = new ArrayList <Thread>();
for (int i = 0; i < par; i++) {
threads.add(new Worker(sz));
threads.get(i).start();
}
for (int i = 0; i < par; i++) {
try {
threads.get(i).join();
} catch (Exception e) {}
}
}
}
È facile scherzare con i numeri e ottenere risultati che stai cercando, grazie per dare un'occhiata alla mia risposta. –
Grazie per la risposta, ma perché l'hai cancellata? – axel22
Non ho letto le tue analisi e domande come avrei dovuto e non ho sentito rispondere correttamente alla tua domanda –