Comparaison équitable de fork() Vs Thread
J'ai eu une discussion sur le coût relatif de fork() Vs thread() pour la parallélisation d'une tâche.
Nous comprendre les différences fondamentales entre les processus Vs Fil
Fil:
- Facile la communication entre les threads
- Rapide changement de contexte.
Processus:
- La tolérance aux pannes.
- Communiquer avec le parent qui n'est pas un problème d'ouvrir un pipe)
- La Communication avec d'autres processus enfants dur
Mais nous étions en désaccord sur le coût de démarrage de processus de Vs threads.
Afin de tester les théories que j'ai écrit le code suivant. Ma question: Est-ce un test valide de mesurer le coût de démarrage ou il me manque quelque chose. Aussi je serais intéressée de savoir comment chaque test exécute sur différentes plates-formes.
fork.cpp
#include <boost/lexical_cast.hpp>
#include <vector>
#include <unistd.h>
#include <iostream>
#include <stdlib.h>
#include <time.h>
extern "C" int threadStart(void* threadData)
{
return 0;
}
int main(int argc,char* argv[])
{
int threadCount = boost::lexical_cast<int>(argv[1]);
std::vector<pid_t> data(threadCount);
clock_t start = clock();
for(int loop=0;loop < threadCount;++loop)
{
data[loop] = fork();
if (data[looo] == -1)
{
std::cout << "Abort\n";
exit(1);
}
if (data[loop] == 0)
{
exit(threadStart(NULL));
}
}
clock_t middle = clock();
for(int loop=0;loop < threadCount;++loop)
{
int result;
waitpid(data[loop], &result, 0);
}
clock_t end = clock();
std::cout << threadCount << "\t" << middle - start << "\t" << end - middle << "\t"<< end - start << "\n";
}
Thread.cpp
#include <boost/lexical_cast.hpp>
#include <vector>
#include <iostream>
#include <pthread.h>
#include <time.h>
extern "C" void* threadStart(void* threadData)
{
return NULL;
}
int main(int argc,char* argv[])
{
int threadCount = boost::lexical_cast<int>(argv[1]);
std::vector<pthread_t> data(threadCount);
clock_t start = clock();
for(int loop=0;loop < threadCount;++loop)
{
if (pthread_create(&data[loop], NULL, threadStart, NULL) != 0)
{
std::cout << "Abort\n";
exit(1);
}
}
clock_t middle = clock();
for(int loop=0;loop < threadCount;++loop)
{
void* result;
pthread_join(data[loop], &result);
}
clock_t end = clock();
std::cout << threadCount << "\t" << middle - start << "\t" << end - middle << "\t"<< end - start << "\n";
}
J'attends de Windows pour faire de pire dans le processus de création.
Mais je m'attends moderne systèmes Unix d'avoir assez de lumière fourchette de coût et d'être au moins comparable à enfiler. Sur les anciens systèmes de type Unix (avant le fork() a été implémenté à l'aide de la copie sur écriture de pages) que ce serait encore pire.
De toute façon Mon calendrier résultats sont les suivants:
> uname -a
Darwin Alpha.local 10.4.0 Darwin Kernel Version 10.4.0: Fri Apr 23 18:28:53 PDT 2010; root:xnu-1504.7.4~1/RELEASE_I386 i386
> gcc --version | grep GCC
i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5659)
> g++ thread.cpp -o thread -I~/include
> g++ fork.cpp -o fork -I~/include
> foreach a ( 1 2 3 4 5 6 7 8 9 10 12 15 20 30 40 50 60 70 80 90 100 )
foreach? ./thread ${a} >> A
foreach? end
> foreach a ( 1 2 3 4 5 6 7 8 9 10 12 15 20 30 40 50 60 70 80 90 100 )
foreach? ./fork ${a} >> A
foreach? end
vi A
Thread: Fork:
C Start Wait Total C Start Wait Total
==============================================================
1 26 145 171 1 160 37 197
2 44 198 242 2 290 37 327
3 62 234 296 3 413 41 454
4 77 275 352 4 499 59 558
5 91 107 10808 5 599 57 656
6 99 332 431 6 665 52 717
7 130 388 518 7 741 69 810
8 204 468 672 8 833 56 889
9 164 469 633 9 1067 76 1143
10 165 450 615 10 1147 64 1211
12 343 585 928 12 1213 71 1284
15 232 647 879 15 1360 203 1563
20 319 921 1240 20 2161 96 2257
30 461 1243 1704 30 3005 129 3134
40 559 1487 2046 40 4466 166 4632
50 686 1912 2598 50 4591 292 4883
60 827 2208 3035 60 5234 317 5551
70 973 2885 3858 70 7003 416 7419
80 3545 2738 6283 80 7735 293 8028
90 1392 3497 4889 90 7869 463 8332
100 3917 4180 8097 100 8974 436 9410
Edit:
Faire un 1000 enfants ont provoqué la fourche version à l'échec.
Donc j'ai réduit les enfants de compter. Mais faire un seul test semble aussi injuste donc, ici, est une plage de valeurs.
- Jetez un oeil à cette réponse quant à la façon dont les threads et les processus sont différents en ce qui concerne les performances: stackoverflow.com/questions/3609469/...
- Difficile de voir comment il serait un test significatif. Vous n'avez rien fait pour mesurer le coût de la mise en place de la communication que de la fourche exige.
- le choix entre la fourche et le fil est (presque?) jamais conduit par les performances. La fonctionnalité est totalement différent; le pilote est "qu'essayez-vous de le faire"
- Passant: Juste point de. Malheureusement, cela devient très spécifique des tâches. Toutes les suggestions. J'essayais juste de montrer que le coût de démarrage n'est pas un facteur dans le choix de savoir si l'utilisation de threads/processus.
- Menuisier: Celles-ci sont toutes de bonnes raisons de penser au moment de choisir météo pour l'utilisation de threads/processus. Je suis en train d'essayer (pas réussi encore) que le coût de démarrage n'est pas un facteur qui devraient influencer votre décision (la différence est mineure et les autres facteurs sont plus importants).
- Je suis d'accord avec cela.
- Gardez à l'esprit que la copie sur écriture des pages n'est pas faire de la surcharge en aller. C'est juste reporté jusqu'à ce que ces pages sont accessibles. Aussi, même avec de la VACHE pages, le coût de démarrage d'un processus est encore plus élevé que pour un thread (mais généralement pas trop donc)
- VACHE permettra d'éviter toutes les pages de code et de tous les lire uniquement les données des pages du besoin d'être copié. Ainsi, seules les pages avec l'écriture de données doivent être copiées. Aussi comme @Hans Passant: souligné il n'y a pas de comminication canal configuré pour transmettre des informations à la maison mère (peut-être supplémentaire de la copie peut être éviter en utilisant la mémoire partagée ici (c'est une question))
- vrai, bien sûr. Pas sûr de ce que je pensais. :p
Vous devez vous connecter pour publier un commentaire.
mumble ... je n'aime pas votre solution pour de nombreuses raisons:
Vous ne prenez pas en compte le temps d'exécution de processus enfants/thread.
Vous devriez comparer cpu, l'utilisation de pas de la nue-temps écoulé. De cette façon, vos statistiques ne dépendent pas de, par exemple, l'accès au disque de la congestion.
Laissez votre enfant à faire quelque chose. Rappelez-vous que le "moderne" de la fourche utilise de copie sur écriture des mécanismes pour éviter d'allouer de la mémoire pour le processus de l'enfant jusqu'à ce que nécessaire. Il est trop facile de le quitter immédiatement. De cette façon, vous éviter bien tous les inconvénients d'une fourchette.
Temps CPU n'est pas le seul coût que vous avez à rendre des comptes. Consommation de la mémoire et de la lenteur de la CIB sont à la fois les inconvénients de la fourche solution.
Vous pourriez utiliser "rusage" au lieu de "l'horloge" à la mesure réelle de l'utilisation des ressources.
P. S. je ne pense pas que vous pouvez vraiment mesurer le processus/thread généraux de l'écriture d'un programme de test simple. Il y a trop de facteurs et, en général, le choix entre les threads et les processus est motivée par d'autres raisons que la simple cpu-utilisation.
Sous Linux
fork
est un appel spécial àsys_clone
, que ce soit dans la bibliothèque ou dans le noyau. Le Clone a beaucoup de commutateurs à flip sur et en dehors, et chacun d'eux les effets de la façon dont elle est chère pour commencer.La bibliothèque réelle fonction
clone
est probablement plus cher quefork
bien car elle n'a plus, bien que la plupart de qui est sur le côté (pile d'échange et l'appel d'une fonction par pointeur).sys_fork
il ya un bon peu de choses à faire.Ce que les micro-benchmark montre que la création de threads et de rejoindre (il n'y a pas de fourchette de résultats au moment où j'écris ceci) prend des dizaines ou des centaines de microsecondes (en supposant que votre système a CLOCKS_PER_SEC=1000000, qu'il a probablement, puisque c'est une XSI exigence).
Puisque vous avez dit que fork() prend 3 fois le coût de threads, nous parlons encore dixièmes de millisecondes au pire. Si c'est visible sur une application, vous pouvez utiliser des groupes de processus/threads, comme Apache 1.3 a fait. En tout cas, je dirais que le temps de démarrage est un point discutable.
La différence importante de threads vs processus (sur Linux et la plupart des systèmes Unix-aime), c'est que sur les processus que vous choisissez explicitement les éléments à partager, à l'aide de l'IPC, de mémoire partagée (SYSV ou mmap-style), les pipes, sockets (vous pouvez envoyer des descripteurs de fichiers sur les sockets AF_UNIX, ce qui signifie que vous obtenez de choisir qui fd est à part), ... Alors que sur les threads presque tout est partagé par défaut, si il y a un besoin de partager ou pas. En fait, c'est la raison pour Plan 9 avait rfork() et Linux a clone() (et récemment départager()), de sorte que vous pouvez choisir les éléments à partager.