Domanda

Sto scrivendo un'applicazione Java Multi Threading che funziona sul processore Nehalem. Comunque ho un problema che a partire da 4 fili che quasi non vedo la velocità nella mia applicazione.

Ho fatto un semplice test. Ho creato un filo che assegna solo un grande array e aprire l'accesso a voci casuali nell'array. Quindi quando eseguo il numero di thread, il tempo di esecuzione non dovrebbe cambiare (supponendo che non sto superando il numero di nuclei della CPU disponibili). Ma ciò che ho osservato è che in esecuzione 1 o 2 fili richiede quasi allo stesso tempo, ma l'esecuzione di 4 o 8 fili è significativamente più lenta. Quindi, prima di provare, risolvi il problema algoritmico e sincronizzazione nella mia applicazione, voglio scoprire qual è la massima possibile parallelizzazione possibile.

Ho usato l'opzione -XX:+UseNUMA JVM, quindi gli array dovrebbero essere assegnati nella memoria vicino ai fili corrispondenti.

P.S. Se i thread stavano facendo un semplice calcolo matematico non c'era alcuna caduta di tempo per 4 e anche 8 fili, quindi ho concluso che quando i fili accedono alla memoria ho alcuni problemi.

Qualsiasi aiuto o idee è apprezzato, grazie.


.

modifica

Grazie per le risposte. Vedo che non mi ha spiegato abbastanza bene.

Prima di provare ad eliminare i problemi di sincronizzazione nella mia applicazione ho fatto un semplice test che controlla la migliore parallelizzazione possibile che potrebbe essere raggiunta. Il codice è il seguente:

public class TestMultiThreadingArrayAccess {
    private final static int arrSize = 40000000;

    private class SimpleLoop extends Thread {
        public void run() {
            int array[] = new int[arrSize];
            for (long i = 0; i < arrSize * 10; i++) {
                array[(int) ((i * i) % arrSize)]++; // randomize a bit the access to the array
            }
            long sum = 0;
            for (int i = 0; i < arrSize; i++)
                sum += array[i];
        }
    }

    public static void main(String[] args) {
        TestMultiThreadingArrayAccess test = new TestMultiThreadingArrayAccess();
        for (int threadsNumber : new int[] { 1, 2, 4, 8 }) {
            Statistics timer = new Statistics("Executing " + threadsNumber+ " threads"); // Statistics is a simple helper class that measures the times
            timer.start();
            test.doTest(threadsNumber);
            timer.stop();
            System.out.println(timer.toString());
        }
    }

    public void doTest(int threadsNumber) {
        Thread threads[] = new Thread[threadsNumber];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new SimpleLoop();
            threads[i].start();
        }

        for (int i = 0; i < threads.length; i++)
            try {
                threads[i].join();
            } catch (InterruptedException e) {
            };
    }
}
.

Così come vedi non c'è sincronizzazione in questo minitest e anche l'allocazione dell'array è all'interno del filo in modo da poter essere posizionato nel blocco della memoria a cui è possibile accedere rapidamente. Inoltre non ci sono contenzioni di memoria in questo codice. Ancora per 4 fili c'è una goccia del 30% nel tempo di esecuzione, e 8 fili vengono eseguiti due volte più lenti. Dato che dal codice, attendo che tutte le discussioni finiscano il loro lavoro, e dal momento che il loro lavoro è indipendente il numero di fili non deve influenzare il tempo totale che l'esecuzione prende.

Sulla macchina installata 2 processori Nehalem iperthreaded quad-core (totalmente 16 cpus), quindi con 8 fili ciascuno potrebbe catturare la CPU esclusivamente.

Quando ho provato a eseguire questo test con array più piccolo (voci 20k), la goccia del tempo di esecuzione di 4 fili è stata del 7% e 8 discussioni - 14%, che è soddisfacente. Ma quando provo a funzionare a caso accessibili su ampio array (voci 40m) gli orari di funzionamento aumentano drasticamente, quindi penso che ci sia un problema che grandi pezzi di memoria (perché non si adattano alla memoria cache?) Accesso in un non -EFFICIENTE VIA.

Ci sono idee come risolvere questo?

Spero che questo chiarisca la domanda in modo migliore, grazie ancora.

È stato utile?

Soluzione

Il collo di bottiglia nel test è la CPU alla memory Bandwith.Anche quando è disponibile la memoria locale, sarà condiviso da un certo numero di discussioni.(La memoria è locale a un nodo, non a un nucleo specifico.) Una volta che la CPU può superare facilmente la larghezza di banda disponibile per un semplice ciclo come il test di cui sopra, e quindi aumentando i thread su un tale test non miglioreranno le prestazioni e può peggiorare le prestazioniA causa della coerenza della cache peggiorata.

Solo un test sanitario, stai anche usando il collezionista parallelo?-XX:+UseParallelGC.Usenuma ha effetto solo allora.

Altri suggerimenti

Senza sapere cosa stai facendo esattamente e qual è il problema che stai cercando di risolvere. Sembra che tu abbia una sincronizzazione pesante attorno al tuo codice, poiché potrebbe essere il motivo principale per non essere abbastanza scalabile. Oltre la sincronizzazione causa per rallentare qualsiasi velocità di velocità, una volta apportata la tua applicazione quasi seriale. Quindi il mio suggerimento a te è ispezionare la tua implementazione e cercare di capirlo.

Aggiungi.

Dopo aver aggiunto la tua implementazione di ciò che stai facendo. Il downgrade delle prestazioni potrebbe essere spiegato da un ampio e massiccio accesso alla memoria. Una volta che si è effettuato di accedere a tutto il thread e devono accedere ai dati della memoria non memorizzati nella cache, poiché sono in esecuzione su diverse CPU, il controller di memoria impedisce che la CPU sia di farlo simultaneamente, il che significa che c'è una sincronizzazione a livello hardware su ciascuna cache. Nel tuo caso è quasi uguale come se stessi eseguendo 10 diversi programmi indipendenti. Immagino che venga lanciato 10 (puoi sostituire 10 da qualsiasi numero di grandi dimensioni) copia il tuo browser web, ad esempio, vedrai lo stesso effetto, ma questo non significa che l'implementazione del browser sia inefficace, crea un enorme peso Memoria del computer.

Come note Artem, è possibile che tu abbia una sincronizzazione inutile. Ma inizierei stabilendo i fatti. La tua app è davvero più lenta come descrivi?

Ecco un articolo perspicace sull'argomento: http://codeidol.com/java/java-concurrency/testing-concurrent-programs/aviiding-performance-testing-pitfalls/

È in realtà abbastanza difficile scrivere utili micro benchmark, specialmente quando si ha a che fare con il codice simultaneo. Ad esempio, è possibile avere "eliminazione del codice morto" in cui il compilatore ottimizza il codice di distanza ritieni che venga eseguito. È anche difficile indovinare quando viene eseguita la raccolta dei rifiuti. L'ottimizzazione runtime di Hotspot rende anche la misurazione più difficile. In caso di thread, è necessario tenere conto del tempo utilizzato per crearli. Quindi potrebbe essere necessario utilizzare un "ciclicoBarrier" ecc. Per avere una misurazione accurata. Cose del genere ..

Avendo detto che, trovo difficile che avrai problemi nell'accesso alla memoria se tutto ciò che stai facendo è leggere. Potremmo essere in grado di aiutarti meglio se puoi pubblicare il codice ...

Ci sono due ovvi problemi potenziali che si prendono in mente.

    .
  • Uso di un thread Thread alloca più matrici che esplodono la cache.Accessi alla memoria principale o ai più bassi livelli di cache sono molto più lenti.
  • Se si utilizza la stessa fonte di istanza di generatore di numeri casuali, quindi le thread si combatteranno sull'accesso ad esso.Potrebbe non essere la sincronizzazione completa, ma invece le barriere di memoria con un algoritmo senza limiti.Generalmente algoritmi di blocco, anche se generalmente veloce, diventano molto più lenti sotto un'elevata contesa.

Oltre ai problemi di concorrenza, la causa più probabile del tuo rallentamento è la cache di memoria la contesa della cache.

Se tutte le discussioni accedono allo stesso pezzo di memoria, le probabilità sono nella cache di memoria di altri processori quando si desidera accedervi.

Se lo stoccaggio è "sola lettura" è possibile fornire a ciascun thread la propria copia che consentirebbe a JVM & Processor di ottimizzare le accchiuse di memoria.

Ho modificato il test con il consiglio dell'articolo che ho pubblicato.Sulla mia macchina da 2 core (è tutto ciò che ho adesso), il risultato sembra ragionevole (nota che ho eseguito 2 test per ogni numero di filetto):

Forse puoi provare questo? (Si prega di notare che dovevo modificare leggermente il tuo test (vedi commento) perché ci è voluto molto tempo per correre sul mio povero hardware)

Si noti inoltre che eseguo questo test utilizzando l'opzione -server.

Test with threadNum 1 took 2095717473 ns
Test with threadNum 1 took 2121744523 ns
Test with threadNum 2 took 2489853040 ns
Test with threadNum 2 took 2465152974 ns
Test with threadNum 4 took 5044335803 ns
Test with threadNum 4 took 5041235688 ns
Test with threadNum 8 took 10279012556 ns
Test with threadNum 8 took 10347970483 ns
.

Codice:

import java.util.concurrent.*;

public class Test{
    private final static int arrSize = 20000000;

    public static void main(String[] args) throws Exception {
        int[] nums = {1,1,2,2,4,4,8,8};//allow hotspot optimization
        for (int threadNum : nums) {
            final CyclicBarrier gate = new CyclicBarrier(threadNum+1);
            final CountDownLatch latch = new CountDownLatch(threadNum);
            ExecutorService exec = Executors.newFixedThreadPool(threadNum);
            for(int i=0; i<threadNum; i++){
                Runnable test = 
                  new Runnable(){
                     public void run() {
                         try{
                             gate.await();
                         }catch(Exception e){
                             throw new RuntimeException(e);
                         }
                         int array[] = new int[arrSize];
                         //arrSize * 10 took very long to run so made it
                         // just arrSize.
                         for (long i = 0; i < arrSize; i++) {
                             array[(int) ((i * i) % arrSize)]++;
                         }//for
                         long sum = 0;
                         for (int i = 0; i < arrSize; i++){
                              sum += array[i]; 
                         }
                         if(new Object().hashCode()==sum){
                              System.out.println("oh");
                         }//if
                         latch.countDown();
                      }//run
                   };//test
                exec.execute(test);
             }//for
             gate.await();
             long start = System.nanoTime();
             latch.await();
             long finish = System.nanoTime();
             System.out.println("Test with threadNum " +
                 threadNum +" took " + (finish-start) + " ns ");
             exec.shutdown();
             exec.awaitTermination(Long.MAX_VALUE,TimeUnit.SECONDS);           
        }//for
    }//main

}//Test
.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top