Je doute que vous puissiez trouver ces frais généraux quelque part sur le web pour une quelconque plateforme existante. Il existe simplement trop de plateformes différentes. L'overhead dépend de deux facteurs :
- L'unité centrale de traitement (UC), car les opérations nécessaires peuvent être plus faciles ou plus difficiles sur différents types d'UC.
- Le noyau du système, car des noyaux différents devront effectuer des opérations différentes sur chaque commutateur.
Parmi les autres facteurs, citons la manière dont le changement s'opère. Une commutation peut avoir lieu lorsque
-
le fil a utilisé tous ses quantum de temps. Lorsqu'un thread est lancé, il peut fonctionner pendant un temps donné avant de devoir rendre le contrôle au noyau qui décidera de la suite.
-
le thread a été préempté. Cela se produit lorsqu'un autre thread a besoin de temps CPU et a une priorité plus élevée. Par exemple, le thread qui gère l'entrée de la souris/du clavier peut être un tel thread. Quel que soit le thread possède l'unité centrale en ce moment, lorsque l'utilisateur tape ou clique sur quelque chose, il ne veut pas attendre que le quantum de temps du thread en cours soit complètement utilisé, il veut voir le système réagir immédiatement. C'est pourquoi certains systèmes arrêtent immédiatement le thread en cours et redonnent le contrôle à un autre thread ayant une priorité plus élevée.
-
le thread n'a plus besoin de temps CPU, parce qu'il bloque sur une opération ou qu'il a appelé sleep() (ou similaire) pour arrêter de fonctionner.
Ces 3 scénarios pourraient avoir des temps de commutation de fils différents en théorie. Par exemple, je m'attendrais à ce que le dernier soit le plus lent, car un appel à sleep() signifie que le CPU est rendu au noyau et que le noyau doit mettre en place un appel de réveil qui s'assurera que le thread est réveillé après environ le temps qu'il a demandé à dormir, il doit ensuite retirer le thread du processus de planification, et une fois que le thread est réveillé, il doit ajouter le thread à nouveau au processus de planification. Toutes ces étapes prennent un certain temps. Ainsi, l'appel de sommeil réel peut être plus long que le temps nécessaire pour passer à un autre thread.
Je pense que si vous voulez en être sûr, vous devez faire des tests de référence. Le problème est que vous devrez généralement soit mettre les threads en sommeil, soit les synchroniser en utilisant des mutex. La mise en sommeil ou le verrouillage/déverrouillage des mutex a en soi une surcharge. Cela signifie que votre benchmark devra également inclure ces frais généraux. Sans un puissant profileur, il est difficile de dire plus tard combien de temps CPU a été utilisé pour le switch réel et combien pour le sleep/mutex-call. D'autre part, dans un scénario de la vie réelle, vos threads vont soit dormir, soit se synchroniser via des verrous. Un benchmark qui mesure uniquement le temps de changement de contexte est un benchmark synthétique car il ne modélise aucun scénario réel. Les benchmarks sont beaucoup plus "réalistes" s'ils se basent sur des scénarios réels. À quoi sert un benchmark GPU qui me dit que mon GPU peut en théorie gérer 2 milliards de polygones par seconde, si ce résultat ne peut jamais être atteint dans une application 3D réelle ? Ne serait-il pas beaucoup plus intéressant de savoir combien de polygones une application 3D réelle peut faire gérer par le GPU par seconde ?
Malheureusement, je ne connais rien à la programmation Windows. Je pourrais écrire une application pour Windows en Java ou peut-être en C#, mais le C/C++ sur Windows me fait pleurer. Je peux seulement vous offrir du code source pour POSIX.
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>
uint32_t COUNTER;
pthread_mutex_t LOCK;
pthread_mutex_t START;
pthread_cond_t CONDITION;
void * threads (
void * unused
) {
// Wait till we may fire away
pthread_mutex_lock(&START);
pthread_mutex_unlock(&START);
pthread_mutex_lock(&LOCK);
// If I'm not the first thread, the other thread is already waiting on
// the condition, thus Ihave to wake it up first, otherwise we'll deadlock
if (COUNTER > 0) {
pthread_cond_signal(&CONDITION);
}
for (;;) {
COUNTER++;
pthread_cond_wait(&CONDITION, &LOCK);
// Always wake up the other thread before processing. The other
// thread will not be able to do anything as long as I don't go
// back to sleep first.
pthread_cond_signal(&CONDITION);
}
pthread_mutex_unlock(&LOCK); //To unlock
}
int64_t timeInMS ()
{
struct timeval t;
gettimeofday(&t, NULL);
return (
(int64_t)t.tv_sec * 1000 +
(int64_t)t.tv_usec / 1000
);
}
int main (
int argc,
char ** argv
) {
int64_t start;
pthread_t t1;
pthread_t t2;
int64_t myTime;
pthread_mutex_init(&LOCK, NULL);
pthread_mutex_init(&START, NULL);
pthread_cond_init(&CONDITION, NULL);
pthread_mutex_lock(&START);
COUNTER = 0;
pthread_create(&t1, NULL, threads, NULL);
pthread_create(&t2, NULL, threads, NULL);
pthread_detach(t1);
pthread_detach(t2);
// Get start time and fire away
myTime = timeInMS();
pthread_mutex_unlock(&START);
// Wait for about a second
sleep(1);
// Stop both threads
pthread_mutex_lock(&LOCK);
// Find out how much time has really passed. sleep won't guarantee me that
// I sleep exactly one second, I might sleep longer since even after being
// woken up, it can take some time before I gain back CPU time. Further
// some more time might have passed before I obtained the lock!
myTime = timeInMS() - myTime;
// Correct the number of thread switches accordingly
COUNTER = (uint32_t)(((uint64_t)COUNTER * 1000) / myTime);
printf("Number of thread switches in about one second was %u\n", COUNTER);
return 0;
}
Sortie
Number of thread switches in about one second was 108406
Plus de 100'000, ce n'est pas si mal et ce, même si nous avons des verrouillages et des attentes conditionnelles. Je pense que sans tout cela, il y aurait au moins deux fois plus de changements de threads par seconde.
4 votes
Je pense que le changement de thread dépend fortement de la quantité de "mémoire" et d'état qu'un seul thread "contient". Si tous vos threads font beaucoup de travail sur des bitmaps énormes, un changement de thread peut être très coûteux. Un thread qui ne fait qu'incrémenter un seul compteur a une très faible surcharge de changement de thread.
0 votes
La réponse acceptée est fausse. Le changement de contexte est coûteux à cause de l'invalidation du cache. Bien sûr, si vous évaluez uniquement le changement de thread avec un incrément de compteur, cela semble rapide, mais c'est une évaluation irréaliste et sans valeur. Ce n'est même pas vraiment un changement de contexte quand le contexte est juste le registre du compteur.