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 bienjava SynchronizedPerformance 1 synchronized
et prend environ 15 ns par étape.java SynchronizedPerformance 2 synchronized
interfère beaucoup et prend environ 150 ns par étape.- 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?
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. –
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. –