2015-06-07 4 views
6

Ho una CPU a quattro core. Creo 4 thread ed eseguo un ciclo intensivo della cpu, e richiede> 4x più tempo rispetto a eseguirlo tutto proceduralmente in un thread.Threading Java di base (4 thread) più lento rispetto a non threading

Ho creato due progetti da confrontare, uno con threading e uno senza. Mostrerò il codice e i tempi di esecuzione. Basta notare il motivo per cui il progetto senza threading sembra strano è che volevo replicare il sovraccarico della memoria, perché non ero sicuro di quanto avrebbe effetto il tempo di esecuzione. Quindi, ecco il codice senza threading:

class TimeTest implements Runnable { 
    private Thread t; 
    private String name; 

    TimeTest(String name) { 
     this.name = name; 
     System.out.println("Creating class " + name); 
    } 

    public void run() { 
     System.out.println("Running class " + name); 

     int value = 100000000; 
//  try { 
      while (--value > 0) { 
       Math.random(); 
//    Thread.sleep(1); 
//    System.out.println("Class " + name + " " + value); 
      } 
//  } catch (InterruptedException e) { 
//   System.out.println("Interrupted " + name); 
//  } 

     System.out.println("Class " + name + " exiting..."); 
    } 

    public void start() { 
     System.out.println("Starting class " + name); 

     if (t == null) { 
      t = new Thread(this, name); 
//   t.start(); 
      this.run(); 
     } 
    } 
} 

public class ThreadComp { 
    public static void main(String[] args) { 
     TimeTest one = new TimeTest("Class-1"); 
     one.start(); 

     TimeTest two = new TimeTest("Class-2"); 
     two.start(); 

     TimeTest three = new TimeTest("Class-3"); 
     three.start(); 

     TimeTest four = new TimeTest("Class-4"); 
     four.start(); 
    } 
} 

Questo funziona in circa 11 secondi.

Ecco il codice con filettatura:

class RunnableTest implements Runnable { 
    private Thread t; 
    private String name; 

    RunnableTest(String name) { 
     this.name = name; 
     System.out.println("Creating thread " + name); 
    } 

    public void run() { 
     System.out.println("Running thread " + name); 
     int value = 100000000; 
//  try { 
      while (--value > 0) { 
       Math.random(); 
//    Thread.sleep(1); 
//    System.out.println("Thread " + name + " " + value); 
      } 
//  } catch (InterruptedException e) { 
//   System.out.println("Interrupted " + name); 
//  } 

     System.out.println("Thread " + name + " exiting..."); 
    } 

    public void start() { 
     System.out.println("Starting thread " + name); 

     if (t == null) { 
      t = new Thread(this, name); 
      t.start(); 
     } 
    } 
} 

public class ThreadTest { 
    public static void main(String[] args) { 
     RunnableTest one = new RunnableTest("Thread-1"); 
     one.start(); 

     RunnableTest two = new RunnableTest("Thread-2"); 
     two.start(); 

     RunnableTest three = new RunnableTest("Thread-3"); 
     three.start(); 

     RunnableTest four = new RunnableTest("Thread-4"); 
     four.start(); 
    }  
} 

Questo viene eseguito in circa 1 minuto 13 secondi.

Ora, nell'esempio da cui sto imparando, chiamano Thread.sleep durante il lead per 50ms. Se faccio questo, i thread girano più velocemente SE chiamo anche Thread.sleep (50) sulla classe non-threaded.

Che è fantastico, so come farlo funzionare. Ma la ragione per cui sto imparando questo è che sto facendo pathfinding, e non aggiungerò una chiamata Sleep su qualcosa che richiede già molto tempo e non ha bisogno di mettere in pausa e non fare nulla nemmeno per 1ms (a meno che non assolutamente deve).

Quindi, quello che mi chiedo è che cosa mi manca? I thread devono assolutamente essere messi a dormire o l'oggetto deve attendere affinché funzionino come intendo (cioè eseguendo tutti e quattro i loop in parallelo)?

Anche se sto facendo un errore, perché ci vorrà molto più tempo? Penserei allo scenario peggiore, funzionerebbe ancora in 11 secondi, finirebbe semplicemente in un ordine imprevedibile ...

+1

così - ci vorranno 4 donne 36 mesi per produrre un bambino? – ZhongYu

+10

Posso quasi garantire che l'uso di 'Math.random' è la causa di questo problema. Vedi https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#random-- –

+1

^^ Cosa dice @JAtkin. Il tuo codice è essenzialmente tutto conteso. –

risposta

11

L'enorme differenza nel tempo di esecuzione è causata dal metodo Math.random(). Se scaverai nella sua implementazione vedrai che usa lo statico randomNumberGenerator condiviso da tutti i thread. Se si procede più in profondità, si noterà che l'esecuzione si basa sul metodo int next(int), che a sua volta utilizza Random.seed, ovvero AtomicLong (si consideri che tutti i thread utilizzano la stessa istanza di Random!). E ora stiamo arrivando a AtomicLong, che è implementato attraverso optimistic locking - e questo è il problema. Le serrature ottimistiche non sono progettate per un carico elevato, risentono molto quando più thread tentano di accedervi contemporaneamente, questo è il calo di prestazioni che si sta osservando.

TL; DR: Utilizzare ThreadLocalRandom (grazie a @ bayou.io per averlo menzionato) e godersi l'aumento delle prestazioni.

+3

meglio. usa ThreadLocalRandom – ZhongYu

+1

ThreadLocalRandom - https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadLocalRandom.html – kervin

+0

Ciò ha funzionato. Senza threading, 1999000000 nextFloat() s richiede 5 secondi. Con il threading 2 (ed: in realtà 5 secondi potrebbe essere stato un colpo di fortuna) secondi. In effetti, è sorprendente per me perché pensavo che ci sarebbero voluti ancora 5 secondi per tutti e quattro alla fine. Ma lo prenderò. Quello che chaps my hide è che ho scelto Math.random() perché avevo bisogno di qualcosa con abbastanza overhead, ma avevo paura di stampare sulla console per forzare tutto a un thread. Comunque grazie. – user1844160

1

Il problema è che si sta utilizzando Math.random(). La documentazione su questo metodo:

...

Questo metodo sia sincronizzata correttamente per consentire un corretto utilizzo da più di un filo. Tuttavia, se molti thread devono generare numeri pseudocasuali a una velocità elevata, è possibile ridurre la contesa per ciascun thread per avere il proprio generatore di numeri pseudocasuali.

(sottolineatura mia)

Quindi la soluzione è quella di creare un nuovo Random per ciascun thread.