2010-09-29 8 views
6

Sto sperimentando alcune costruzioni multithreading, ma in qualche modo sembra che il multithreading non sia più veloce di un singolo thread. L'ho ristretto a un test molto semplice con un loop annidato (1000x1000) in cui il sistema conta solo.
Di seguito ho pubblicato il codice sia per il threading singolo che per il multithreading e come vengono eseguiti.
Il risultato è che il thread singolo completa il ciclo in circa 110 ms, mentre i due thread richiedono circa 112 ms.
Non penso che il problema sia il sovraccarico del multithreading. Se invio uno solo dei due Runnables al ThreadPoolExecutor, viene eseguito in metà del tempo del thread singolo, il che ha senso. Ma aggiungere che il secondo Runnable lo rende 10 volte più lento. Entrambi i core da 3.00 Ghz funzionano al 100%.
Penso che potrebbe essere specifico per il PC, in quanto il PC di qualcun altro mostrava risultati a doppia velocità sul multithreading. Ma allora, cosa posso fare a riguardo? Ho un Intel Pentium 4 3.00 GHz (2 CPU) e Java jre6. Codice

prova:Multithreading non più veloce del singolo thread (test ad anello semplice)

// Single thread: 
long start = System.nanoTime(); // Start timer 
final int[] i = new int[1];  // This is to keep the test fair (see below) 
int i = 0; 
for(int x=0; x<10000; x++) 
{ 
    for(int y=0; y<10000; y++) 
    { 
     i++; // Just counting... 
    } 
} 
int i0[0] = i; 
long end = System.nanoTime(); // Stop timer 

Questo codice viene eseguito in circa 110 ms .

// Two threads: 

start = System.nanoTime(); // Start timer 

// Two of the same kind of variables to count with as in the single thread. 
final int[] i1 = new int [1]; 
final int[] i2 = new int [1]; 

// First partial task (0-5000) 
Thread t1 = new Thread() { 
    @Override 
    public void run() 
    { 
     int i = 0; 
     for(int x=0; x<5000; x++) 
      for(int y=0; y<10000; y++) 
       i++; 
     i1[0] = i; 
    } 
}; 

// Second partial task (5000-10000) 
Thread t2 = new Thread() { 
    @Override 
    public void run() 
    { 
     int i = 0; 
     for(int x=5000; x<10000; x++) 
      for(int y=0; y<10000; y++) 
       i++; 
     int i2[0] = i; 
    } 
}; 

// Start threads 
t1.start(); 
t2.start(); 

// Wait for completion 
try{ 
    t1.join(); 
    t2.join(); 
}catch(Exception e){ 
    e.printStackTrace(); 
} 

end = System.nanoTime(); // Stop timer 

Questo codice viene eseguito in circa 112 ms .

Edit: ho cambiato il Runnables per Fili e sono liberato del ExecutorService (per semplicità del problema).

Edit: ha provato alcuni suggerimenti

+0

Quindi, hai provato i suggerimenti? –

+0

Ah, Pentium4 - guarda la mia risposta aggiornata :) – snemarch

risposta

11

Non si desidera mantenere il polling Thread.isAlive() - questo brucia un sacco di cicli della CPU senza una buona ragione. Utilizzare invece Thread.join().

Inoltre, probabilmente non è una buona idea avere i thread incrementare direttamente gli array di risultati, le linee della cache e tutto. Aggiorna le variabili locali ed esegui un singolo negozio al termine dei calcoli.

EDIT:

totalmente trascurato il fatto che si sta utilizzando un Pentium 4. Per quanto ne so, non c'è nessuna versione multicore della P4 - per dare l'illusione di multicore, ha Hyper-Threading: due nuclei logici condividono le unità di esecuzione di uno nucleo fisico. Se i tuoi thread dipendono dalle stesse unità di esecuzione, le tue prestazioni saranno uguali a (o peggio di!) Prestazioni single-threaded. Ad esempio, sono necessari calcoli in virgola mobile in un thread e calcoli in interi in un altro per ottenere miglioramenti delle prestazioni.

L'implementazione di P4 HT è stata criticata molto, le implementazioni più recenti (nucleo2 recente) dovrebbero essere migliori.

+0

+1 - Il primo paragrafo è probabilmente la maggior parte della differenza. –

+0

+1 - In realtà, entrambi i suggerimenti velocizzano significativamente il processo, grazie. Ma c'è qualcosa di strano: usare Thread.isAlive() in combinazione con gli array di incremento direttamente, è più veloce (800 ms) che usare Thread.join() (2200 ms), ma usando isAlive() in combinazione con il tuo secondo suggerimento, è più lento (190 ms) di join() (114 ms). Ad ogni modo, utilizzando entrambi i suggerimenti accelera il sistema da 2200 ms a 114: D. Tuttavia, il tuo secondo suggerimento accelera anche il thread singolo a circa 110 ms, quindi ora non c'è ancora alcuna differenza. – RemiX

+0

Una differenza di meno di 10 ms non ti dice nulla quando si esegue un sistema operativo multitasking: sarà necessario aumentare le iterazioni per misurare la differenza di velocità in modo più affidabile :) – snemarch

1

Non fate nulla con i, in modo che il ciclo è probabilmente solo ottimizzato distanza.

+0

In realtà, ho stampato il valore di i in fondo (ma non è mostrato nel codice). – RemiX

+0

I tempi sono coerenti con l'ottimizzazione, ma non ottimizzati. Mi piacerebbe vedere il test ripetuto (senza riavviare il processo). Un thread di problemi può avere in questo contesto è che HotSpot viene eseguito in un thread diverso e il thread aggiuntivo potrebbe finire per eseguire il codice non ottimizzato per un po 'di tempo. –

+0

Un altro thread che fa esattamente la stessa cosa di t2 (solo 10000x10000) è completato in 107 ms (più veloce di t1 e t2 insieme) o non è quello che intendevi? – RemiX

2

Non sono affatto sorpreso della differenza. Stai utilizzando il framework di concorrenza di Java per creare i tuoi thread (anche se non vedo alcuna garanzia che due thread vengano creati anche dal primo lavoro potrebbe essere completato prima che inizi anche il secondo.

Probabilmente ci sono tutti i tipi di blocco e sincronizzazione in corso dietro le quinte, che in realtà non c'è bisogno per il vostro semplice test. In breve mi faccio che il problema è il sovraccarico di multithreading.

+0

L'ho testato anche con due soli Thread e usando thread1.start(), mostrando lo stesso risultato. Inoltre, un Runnable in ExecutorService funziona molto rapidamente e, infine, un'altra macchina funziona bene con questo codice. – RemiX

4

provare ad aumentare la dimensione della matrice po '. No, davvero.

Gli oggetti piccoli allocati sequenzialmente nella stessa thread tendono ad essere inizialmente allocati in sequenza. abilmente nella stessa linea di cache. Se hai due core ad accedere alla stessa linea di cache (e quindi micro-benhcmark sta essenzialmente facendo una sequenza di scritture nello stesso indirizzo) allora dovranno lottare per l'accesso.

C'è una classe in java.util.concurrent che contiene un mucchio di campi inutilizzati long. Il loro scopo è separare gli oggetti che possono essere usati frequentemente da diversi thread in diverse linee di cache.

+0

Sto usando un array diverso per ogni Thread, quindi non credo che debbano combattere per l'accesso ... o ho frainteso? – RemiX

+4

@RemiX: sono entrambi allocati nello heap, i2 viene assegnato subito dopo i1. C'è un'alta probabilità che finiscano nella stessa cacheline. – snemarch

+0

+1 - da 2200 ms a 280 ms semplicemente aumentando la dimensione degli array a 10. Sfortunatamente, usando gli altri suggerimenti l'effetto non è più così grande. Buono da ricordare, però. – RemiX

1

Avete controllato il numero di core disponibili sul vostro PC con Runtime.getRuntime(). AvailableProcessors()?

+0

Appena fatto, e dice 2 processori. Inoltre, posso vederli lavorare nel Task Manager. – RemiX

0

Il tuo codice incrementa semplicemente una variabile - questa è comunque un'operazione molto veloce. Non stai ottenendo molto dall'uso di più thread qui. I guadagni di prestazioni sono più pronunciati quando thread-1 deve attendere qualche risposta esterna o eseguire calcoli più complessi, mentre il thread principale o qualche altro thread può continuare l'elaborazione e non viene sospeso. Potresti sembrare più guadagni se contassi più alto o usi più thread (probabilmente un numero sicuro è il numero di CPU/core nella tua macchina).

Problemi correlati