ExecutorService: le seuil de rentabilité surprenant des performances - règles empiriques?
Je suis à essayer de comprendre comment utiliser correctement Java Exécuteurs testamentaires. Je me rends compte de la soumission de tâches à une ExecutorService
a ses propres généraux. Cependant, je suis surpris de voir qu'il est aussi élevé qu'il l'est.
Mon programme doit traiter d'énormes quantités de données (données boursières) avec un temps de latence faible que possible. La plupart des calculs sont assez simples opérations arithmétiques.
J'ai essayé de tester quelque chose de très simple: "Math.random() * Math.random()
"
Le test le plus simple ce calcul s'exécute dans une boucle simple. La deuxième épreuve a fait le même calcul à l'intérieur d'un anonyme Exécutable (c'est censé mesurer le coût de la création de nouveaux objets). Le troisième test passe le Runnable
à un ExecutorService
(cette mesure le coût de l'instauration d'exécuteurs testamentaires).
J'ai couru les tests sur mes dinky ordinateur portable (2 processeurs), 1.5 go de ram):
(in milliseconds)
simpleCompuation:47
computationWithObjCreation:62
computationWithObjCreationAndExecutors:422
(environ une fois sur quatre pistes, les deux premiers numéros de la fin de l'égalité)
Avis que les exécuteurs de prendre beaucoup plus de temps que l'exécution sur un seul thread. Les chiffres étaient à peu près de même pour le pool de thread des tailles comprises entre 1 et 8.
Question: Suis-je raté quelque chose d'évident ou ce sont les résultats attendus? Ces résultats me dire que toute tâche je passe à un exécuteur testamentaire doit faire quelques non-trivial de calcul. Si je suis le traitement de millions de messages, et j'ai besoin d'effectuer de très simple (et bon marché) des transformations sur chaque message, je risque de ne pas être en mesure d'utiliser les exécuteurs...en essayant de propagation des calculs sur plusieurs Processeurs pourrait être plus coûteux de le faire simplement dans un seul thread. La conception de décision devient beaucoup plus complexe que ce que j'avais pensé à l'origine. Toutes les pensées?
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ExecServicePerformance {
private static int count = 100000;
public static void main(String[] args) throws InterruptedException {
//warmup
simpleCompuation();
computationWithObjCreation();
computationWithObjCreationAndExecutors();
long start = System.currentTimeMillis();
simpleCompuation();
long stop = System.currentTimeMillis();
System.out.println("simpleCompuation:"+(stop-start));
start = System.currentTimeMillis();
computationWithObjCreation();
stop = System.currentTimeMillis();
System.out.println("computationWithObjCreation:"+(stop-start));
start = System.currentTimeMillis();
computationWithObjCreationAndExecutors();
stop = System.currentTimeMillis();
System.out.println("computationWithObjCreationAndExecutors:"+(stop-start));
}
private static void computationWithObjCreation() {
for(int i=0;i<count;i++){
new Runnable(){
@Override
public void run() {
double x = Math.random()*Math.random();
}
}.run();
}
}
private static void simpleCompuation() {
for(int i=0;i<count;i++){
double x = Math.random()*Math.random();
}
}
private static void computationWithObjCreationAndExecutors()
throws InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(1);
for(int i=0;i<count;i++){
es.submit(new Runnable() {
@Override
public void run() {
double x = Math.random()*Math.random();
}
});
}
es.shutdown();
es.awaitTermination(10, TimeUnit.SECONDS);
}
}
source d'informationauteur Shahbaz
Vous devez vous connecter pour publier un commentaire.
Edit: j'ai changé ton exemple et je laisse courir mon petit dual-core x200 ordinateur portable.
Comme vous le voyez dans le code source, j'ai pris le lot de provisionnement et exécuteur testamentaire du cycle de vie de la mesure, trop. C'est plus juste par rapport aux deux autres méthodes.
Voir les résultats par vous-même...
Ce n'est pas un test juste pour le pool de threads pour les raisons suivantes,
Considérant suivant les étapes supplémentaires le pool de threads a à faire à part la création de l'objet et l'exécution de la tâche,
Lorsque vous avez un vrai travail et plusieurs threads, au profit du pool de threads sera évidente.
Je ne pense pas que cela soit réaliste puisque vous êtes en train de créer un nouvel exécuteur testamentaire de service chaque fois que vous faites l'appel de la méthode. Sauf si vous avez très étrange exigences qui semble irréaliste - en général, vous devez créer le service lorsque votre application démarre, puis de soumettre des travaux.
Si vous essayez de l'analyse comparative de nouveau, mais initialiser le service comme un champune fois, à l'extérieur de la boucle de chronométrage; alors vous verrez les frais généraux réels de la soumission Runnables pour le service par rapport à l'exécution par vous-même.
Mais je ne pense pas que vous aurez compris le point entièrement Exécuteurs testamentaires ne sont pas censés être là pour l'efficacité, ils sont là pour faire de la coordination et de la remise en arrêt de travail pour un pool de threads plus simple. Ils seront toujours moins efficace que de simplement en invoquant
Runnable.run()
vous-même (car, à la fin de la journée, l'exécuteur testamentaire de service a encore besoin pour ce faire, après avoir fait quelques un service de ménage supplémentaire à l'avance). C'est quand vous les utilisez à partir de plusieurs threads ayant besoin d'un traitement asynchrone, qu'ils brillent vraiment.Également considérer que vous êtes à la recherche relative de la différence de temps de essentiellement des coûts fixes (Exécuteur testamentaire de frais généraux est le même, que vos tâches prendre 1ms ou 1hr) par rapport à une très petite quantité variable (de votre trivial exécutables). Si l'exécuteur testamentaire prend 5ms supplémentaire pour exécuter un 1ms tâche, qui n'est pas très favorable de la figure. Si elle prend 5ms supplémentaire pour exécuter un 5 seconde tâche (p. ex. non-trivial de requête SQL), c'est tout à fait négligeable et entièrement en vaut la peine.
Donc, dans une certaine mesure, cela dépend de votre situation - si vous avez une très-section critique, l'exécution de beaucoup de petites tâches, que vous n'avez pas besoin d'être exécutées en parallèle ou de manière asynchrone ensuite, vous aurez rien d'un Exécuteur testamentaire. Si vous êtes de traitement plus lourd des tâches en parallèle et que vous voulez répondre de manière asynchrone (par exemple, une webapp) puis Exécuteurs sont grands.
Qu'ils sont le meilleur choix pour vous, dépend de votre situation, mais vraiment, vous devez essayer les tests avec des données représentatives. Je ne pense pas qu'il serait prématuré d'en tirer des conclusions à partir des tests que vous avez fait, à moins que vos tâches sont vraiment que trivial (et vous ne voulez pas de réutiliser l'exécuteur exemple...).
Mathématiques.random() en fait synchronise sur un seul générateur de nombre Aléatoire. L'Appel De Mathématiques.random() résultats dans significative contention pour le générateur de nombre. En fait, le plus de threads que vous avez, le plus lent, il va être.
De la Mathématique.random() javadoc:
Voici les résultats sur ma machine (OpenJDK 8 sur 64 bits Ubuntu 14.0, Thinkpad W530)
Il y a certainement des frais généraux. Mais rappelez-vous ce que ces nombres sont: millisecondes pour 100 itérations. Dans votre cas, la surcharge a été d'environ 4 microsecondes par itération. Pour moi, la surcharge a été d'environ un quart de la microseconde.
La surcharge est de la synchronisation, les structures de données internes, et peut-être un manque de JIT optimisation dus à la complexité des chemins de code (certainement plus complexe que votre boucle for).
Les tâches qui vous fait envie pour paralléliser en vaudrait la peine, malgré le quart de la microseconde frais généraux.
Pour info, ce serait un très mauvais calcul pour paralléliser. J'ai haussé le fil à 8 (le nombre de cœurs):
Il n'a pas fait les choses plus vite. C'est parce que
Math.random()
est synchronisé.Tout d'abord, il y a quelques problèmes avec la microbenchmark. Vous ne réchauffer l'atmosphère, ce qui est bon. Cependant, il est préférable d'exécuter le test à plusieurs reprises, ce qui devrait donner une idée de savoir si elle a vraiment réchauffé et la variance des résultats. Elle tend aussi à être mieux à faire le test de chaque algorithme en pistes séparées, sinon, vous risquez de provoquer deoptimisation lorsqu'un algorithme de changements.
La tâche est très petite, même si je ne suis pas entièrement sûr de savoir comment les petits. Si nombre de fois plus vite est assez vide de sens. En multithread situations, elle va toucher le même volatile des emplacements threads peuvent causer vraiment de la mauvaise performance (l'utilisation d'un
Random
instance par thread). Aussi une course de 47 millisecondes est un peu court.Va certainement un autre thread pour une petite opération ne va pas être rapide. Se répartir les tâches en plus grande taille si possible. JDK7 semble que si il aura un fork-join-cadre, qui vise à soutenir fine des tâches à partir de diviser et conquérir des algorithmes en préférant d'exécuter les tâches sur le même fil, dans l'ordre, avec de plus grandes tâches tiré par les threads inactifs.
Fixe ThreadPool ultime de porpose est de réutiliser déjà créé des threads. Donc, les gains de performance sont vu dans l'absence de la nécessité de recréer un nouveau thread à chaque fois qu'une tâche est soumise. D'où le temps d'arrêt doit être prise à l'intérieur de l'soumis tâche. Seulement dans la dernière déclaration de la méthode run.
Vous avez besoin de quelque groupe de l'exécution, afin de présenter de plus grandes portions de calcul pour chaque thread (par exemple, construire des groupes basés sur le symbole).
J'ai obtenu de meilleurs résultats dans des scénarios similaires en utilisant le Perturbateur. Il a une très faible par poste de frais généraux. Toujours important de le groupe emplois, naïf round robin crée habituellement beaucoup de défauts de cache.
voir http://java-is-the-new-c.blogspot.de/2014/01/comparision-of-different-concurrency.html