2010-10-26 4 views
1

Je souhaite comprendre les performances dans des environnements multithread. Pour cela j'ai écrit un petit test que j'ai couru sur ma machine (Intel quad-core, Windows XP, Sun JDK 1.6.0_20), avec des résultats surprenants.Performances dans une application Java multithread

Le test est fondamentalement un compteur thread-safe qui est synchronisé à l'aide du mot-clé synchronized ou d'un verrou explicite. Voici le code:

import java.util.concurrent.locks.ReentrantLock; 

public class SynchronizedPerformance { 

    static class Counter { 

    private static final int MAX = 1 << 24; 

    int count; 
    long lastLog = 0; 

    private final ReentrantLock lock = new ReentrantLock(); 

    private int incrementAndGet() { 
     count++; 
     if (count == MAX) { 
     long now = System.nanoTime(); 
     if (lastLog != 0) { 
      long elapsedTime = now - lastLog; 
      System.out.printf("counting took %.2f ns\n", Double.valueOf((double)elapsedTime/MAX)); 
     } 
     lastLog = now; 
     count = 0; 
     } 
     return count; 
    } 

    synchronized int synchronizedIncrementAndGet() { 
     return incrementAndGet(); 
    } 

    int lockedIncrementAndGet() { 
     lock.lock(); 
     try { 
     return incrementAndGet(); 
     } finally { 
     lock.unlock(); 
     } 
    } 
    } 

    static class SynchronizedCounterAccessor implements Runnable { 

    private final Counter counter; 

    public SynchronizedCounterAccessor(Counter counter) { 
     this.counter = counter; 
    } 

    @Override 
    public void run() { 
     while (true) 
     counter.synchronizedIncrementAndGet(); 
    } 
    } 

    static class LockedCounterAccessor implements Runnable { 

    private final Counter counter; 

    public LockedCounterAccessor(Counter counter) { 
     this.counter = counter; 
    } 

    @Override 
    public void run() { 
     while (true) 
     counter.lockedIncrementAndGet(); 
    } 
    } 

    public static void main(String[] args) { 
    Counter counter = new Counter(); 
    final int n = Integer.parseInt(args[0]); 
    final String mode = args[1]; 

    if (mode.equals("locked")) { 
     for (int i = 0; i < n; i++) 
     new Thread(new LockedCounterAccessor(counter), "ca" + i).start(); 
    } else if (mode.equals("synchronized")) { 
     for (int i = 0; i < n; i++) 
     new Thread(new SynchronizedCounterAccessor(counter), "ca" + i).start(); 
    } else { 
     throw new IllegalArgumentException("locked|synchronized"); 
    } 
    } 
} 

J'ai fait les observations suivantes:

fonctionne assez bien
  1. java SynchronizedPerformance 1 synchronized et prend environ 15 ns par étape.
  2. java SynchronizedPerformance 2 synchronized interfère beaucoup et prend environ 150 ns par étape.
  3. Lorsque je démarre deux processus indépendants de java SynchronizedPerformance 2 synchronized chacun d'eux prend environ 100 ns par étape. C'est-à-dire que démarrer le processus une seconde fois rend le premier (et le second) plus rapide.

Je ne comprends pas la troisième observation. Quelles explications plausibles existent pour ce phénomène?

+2

Vous devez exécuter vos micro-tests à plusieurs reprises, sinon la variance naturelle submergera tout signal réel dans vos mesures. Courir les dizaines ou des centaines de fois. –

+0

Je le fais déjà. L'effet est le même. En particulier dans le scénario 3, l'application est lente lorsque je ne démarre qu'un seul processus, et elle est plus rapide chaque fois que je lance le second processus. Lorsque j'arrête plus tard le deuxième processus, le premier ralentit à nouveau. –

Répondre

1

Vous vous trouvez dans une situation où les performances dépendent entièrement du fonctionnement du planificateur. Dans # 3, quand n'importe quel autre processus dans le système veut un peu de temps (même un petit peu), il suspendra l'un de vos 4 threads. Si ce thread n'arrive pas à maintenir le verrou au moment où il est suspendu, sa "paire" peut maintenant fonctionner sans conteste, et fera beaucoup de progrès (tourne à une vitesse de 20x par rapport à la situation contestée).

Bien sûr, si elle est permutée lorsqu'elle est verrouillée, sa "paire" ne progressera pas. Vous avez donc deux facteurs concurrents, et le temps d'exécution global dépend de la fraction de temps pendant laquelle le verrou est tenu par un fil et de la pénalité/bonus que vous obtenez pour chaque situation. Votre bonus est important donc je m'attendrais à une accélération globale comme vous l'avez vu.

+0

Suggérez-vous que les verrous en Java sont partagés entre différents processus JVM? Parce que ce n'est pas vrai. –

+0

Désolé, je crois que j'ai utilisé "processus" dans un couple d'endroits où j'aurais dû utiliser "thread". Je vais éditer. –

1

Le plus probable est qu'il existe certaines charges fixes fixes, quel que soit le nombre de threads existants, par exemple, la récupération de place ou la gestion d'autres ressources.

Questions connexes