2010-09-29 2 views
6

J'expérimente avec quelques constructions de multithreading, mais de toute façon il semble que le multithreading n'est pas plus rapide qu'un seul thread. Je l'ai réduit à un test très simple avec une boucle imbriquée (1000x1000) dans laquelle le système ne compte que.
Ci-dessous j'ai posté le code pour les threads simples et multithread et comment ils sont exécutés.
Le résultat est que le thread unique termine la boucle dans environ 110 ms, tandis que les deux threads prennent également environ 112 ms .
Je ne pense pas que le problème soit la surcharge du multithreading. Si je ne soumets que l'un des deux Runnables à ThreadPoolExecutor, il s'exécute dans la moitié du temps du thread unique, ce qui est logique. Mais ajouter que Runnable deuxième le rend 10 fois plus lent. Les deux cœurs 3.00Ghz fonctionnent à 100%.
Je pense que cela peut être spécifique au PC, car le PC de quelqu'un d'autre a montré des résultats à double vitesse sur le multithreading. Mais alors, que puis-je faire à ce sujet? J'ai un Intel Pentium 4 3.00GHz (2 processeurs) et Java jre6.

Code d'essai:
Multithreading pas plus rapide qu'un simple thread (test de boucle simple)

// 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 

Ce code est exécuté dans environ 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 

Ce code est exécuté en environ 112 ms . Edit: J'ai changé les Runnables en Threads et je me suis débarrassé de l'ExecutorService (pour simplifier le problème).

Edit: a essayé quelques suggestions

+0

Alors, avez-vous essayé les suggestions? –

+0

Ah, Pentium4 - voir ma mise à jour réponse :) – snemarch

Répondre

11

Vous ne voulez certainement pas continuer à interroger Thread.isAlive() - cela brûle beaucoup de cycles du processeur sans raison valable. Utilisez Thread.join() à la place.

De même, ce n'est probablement pas une bonne idée que les threads incrémentent les tableaux de résultats directement, les lignes de cache et tout le reste. Mettez à jour les variables locales et faites un seul magasin lorsque les calculs sont terminés.

EDIT:

totalement perdre de vue que vous utilisez un Pentium 4. Pour autant que je sache, il n'y a pas de version multi-cœurs du P4 - pour donner l'illusion de multi-cœurs, il a Hyper-Threading: deux noyaux logiques partager les unités d'exécution d'un noyau physique. Si vos threads dépendent des mêmes unités d'exécution, vos performances seront les mêmes que (ou pires que!) Les performances mono-thread. Vous auriez besoin, par exemple, de calculs à virgule flottante dans un thread et de calculs d'entiers dans un autre pour améliorer les performances.

L'implémentation de P4 HT a été beaucoup critiquée, les implémentations plus récentes (core2 récent) devraient être meilleures.

+0

+1 - Le premier paragraphe est probablement là où se trouve la plus grande différence. –

+0

+1 - En fait, les deux suggestions accélèrent considérablement le processus, merci. Mais il y a quelque chose d'étrange: utiliser Thread.isAlive() en combinaison avec des tableaux incrémentés directement, est plus rapide (800 ms) qu'en utilisant Thread.join() (2200 ms), mais en utilisant isAlive() en combinaison avec votre seconde suggestion, est plus lent (190 ms) que join() (114 ms). Quoi qu'il en soit, en utilisant vos deux suggestions accélère le système de 2200 ms à 114: D. Cependant, votre deuxième suggestion accélère également le thread unique à environ 110 ms alors maintenant, il n'y a encore aucune différence. – RemiX

+0

Une différence de moins de 10 ms ne vous dit vraiment rien lorsque vous utilisez un système d'exploitation multitâche - vous devrez augmenter les itérations pour mesurer la différence de vitesse de manière plus fiable :) – snemarch

1

Vous ne faites rien avec moi, de sorte que votre boucle est probablement optimisé loin.

+0

En fait, j'ai imprimé la valeur de i en bas (mais ce n'est pas indiqué dans le code). – RemiX

+0

Les temps correspondent à une optimisation mais pas à une optimisation. J'aimerais voir le test répété (sans redémarrer le processus). Dans ce contexte, les threads d'un problème peuvent être: HotSpot s'exécute dans un thread différent et le thread supplémentaire peut finir par exécuter le code non optimisé pendant un certain temps. –

+0

Un autre thread faisant exactement la même chose que t2 (seulement 10000x10000) est terminé en 107 ms (plus rapide que t1 et t2 ensemble), ou n'est-ce pas ce que vous vouliez dire? – RemiX

2

Je ne suis pas du tout surpris par la différence. Vous utilisez le framework de concurrence de Java pour créer vos threads (bien que je ne vois aucune garantie que deux threads soient créés car le premier job pourrait se terminer avant que le second ne commence)

Il y a probablement toutes sortes de verrouillage et de synchronisation dans les coulisses que vous n'avez pas réellement besoin pour votre test simple. En bref, je ne pense que le problème est la surcharge de multithreading.

+0

J'ai également testé avec seulement deux Threads et en utilisant thread1.start(), montrant le même résultat. En outre, un Runnable dans ExecutorService fonctionne très rapidement et finalement, une autre machine fonctionne bien avec ce code. – RemiX

4

Essayez d'augmenter la taille du tableau un peu. non, vraiment.

Les petits objets alloués séquentiellement dans le même thread ont tendance à être initialement alloués séquentiellement. habilement dans la même ligne de cache. Si vous avez deux cœurs accéder à la même ligne de cache (et alors que le micro-benhcmark fait essentiellement juste une séquence d'écritures à la même adresse) alors ils devront se battre pour l'accès.

java.util.concurrentjava.util.concurrent Il existe une classe java.util.concurrent comportant un nombre de champs long non utilisés. Leur but est de séparer les objets qui peuvent être fréquemment utilisés par différents threads dans différentes lignes de cache.

+0

J'utilise un tableau différent pour chaque Thread, donc je ne pense pas qu'ils doivent se battre pour l'accès ... ou ai-je mal compris? – RemiX

+4

@RemiX: ils sont tous deux alloués sur le tas, i2 est alloué juste après i1. Il y a une très forte probabilité qu'ils se retrouvent dans la même cacheline. – snemarch

+0

+1 - 2200 ms à 280 ms en augmentant simplement la taille des tableaux à 10. Malheureusement, en utilisant vos autres suggestions, l'effet n'est plus si grand. Bon à retenir, cependant. – RemiX

1

Avez-vous vérifié le nombre de cœurs disponibles sur votre PC avec Runtime.getRuntime(). AvailableProcessors()?

+0

Juste fait, et il dit 2 processeurs. Aussi, je peux les voir travailler dans le Gestionnaire des tâches. – RemiX

0

Votre code incrémente simplement une variable - c'est une opération très rapide de toute façon. Vous ne gagnez pas beaucoup de l'utilisation de plusieurs threads ici. Les gains de performances sont plus prononcés lorsque le thread-1 doit attendre une réponse externe ou effectuer des calculs plus complexes, alors que votre thread principal ou un autre thread peut continuer le traitement et n'est pas bloqué en attente. Vous pourriez avoir l'impression d'avoir plus de gains si vous avez compté plus ou utilisé plus de threads (probablement un nombre sûr est le nombre de processeurs/cœurs dans votre machine).

Questions connexes