Les changements de contexte sont beaucoup plus lents dans les nouveaux noyaux Linux

99

Nous cherchons à mettre à niveau le système d'exploitation de nos serveurs d'Ubuntu 10.04 LTS vers Ubuntu 12.04 LTS. Malheureusement, il semble que la latence pour exécuter un thread qui est devenu exécutable a considérablement augmenté du noyau 2.6 au noyau 3.2. En fait, les chiffres de latence que nous obtenons sont difficiles à croire.

Permettez-moi d'être plus précis sur le test. Nous avons un programme qui exécute deux threads. Le premier thread obtient l'heure actuelle (en ticks en utilisant RDTSC) puis signale une variable de condition une fois par seconde. Le deuxième thread attend la variable de condition et se réveille quand il est signalé. Il obtient ensuite l'heure actuelle (en ticks en utilisant RDTSC). La différence entre l'heure du deuxième thread et l'heure du premier thread est calculée et affichée sur la console. Après cela, le deuxième thread attend une fois de plus la variable de condition. Il sera à nouveau signalé par le premier thread après environ un deuxième passage.

Donc, en un mot, nous obtenons une communication thread à thread via une mesure de latence de variable de condition une fois par seconde.

Dans le noyau 2.6.32, cette latence est quelque part de l'ordre de 2,8-3,5 us, ce qui est raisonnable. Dans le noyau 3.2.0, cette latence a augmenté à quelque part de l'ordre de 40-100 us. J'ai exclu toute différence de matériel entre les deux hôtes. Ils fonctionnent sur un matériel identique (processeurs double socket X5687 {Westmere-EP} fonctionnant à 3,6 GHz avec hyperthreading, speedstep et tous les états C désactivés). L'application de test modifie l'affinité des threads pour les exécuter sur des cœurs physiques indépendants du même socket (c'est-à-dire que le premier thread est exécuté sur Core 0 et le deuxième thread est exécuté sur Core 1), il n'y a donc pas de rebond des threads sur cœurs ou rebondissement / communication entre les sockets.

La seule différence entre les deux hôtes est que l'un exécute Ubuntu 10.04 LTS avec le noyau 2.6.32-28 (la boîte de changement de contexte rapide) et l'autre exécute le dernier Ubuntu 12.04 LTS avec le noyau 3.2.0-23 (le contexte lent boîte de commutation). Tous les paramètres du BIOS et le matériel sont identiques.

Y a-t-il eu des changements dans le noyau qui pourraient expliquer ce ralentissement ridicule du temps qu'il faut pour qu'un thread soit programmé pour s'exécuter?

Mise à jour: Si vous souhaitez exécuter le test sur votre hôte et la version Linux, j'ai publié le code dans pastebin pour votre lecture. Compiler avec:

g++ -O3 -o test_latency test_latency.cpp -lpthread

Exécutez avec (en supposant que vous ayez au moins un boîtier double cœur):

./test_latency 0 1 # Thread 1 on Core 0 and Thread 2 on Core 1

Mise à jour 2 : Après de nombreuses recherches dans les paramètres du noyau, des articles sur les modifications du noyau et des recherches personnelles, j'ai compris quel est le problème et j'ai publié la solution en réponse à cette question.

Michael Goldshteyn
la source
1
juste une supposition, mais peut-être que changer un paramètre de /proc/sys/kernel/*peut fonctionner? Si vous trouvez quelque chose qui fonctionne, placez cette configuration /etc/sysctl.confou un fichier /etc/sysctl.d/pour le faire persister lors des redémarrages.
Carlos Campderrós
1
J'ai comparé / proc / sys / kernel entre les deux hôtes, mais je ne vois aucune différence significative, en particulier dans les éléments de configuration liés à la planification.
Michael Goldshteyn
Je me souviens vaguement d'une rumeur selon laquelle RDTSC n'est pas nécessairement correctement synchronisé entre les cœurs, mais je m'attendrais à ce que si c'était un problème, vous verriez un renversement de temps. Avez-vous essayé de manipuler les affinités pour exécuter les deux threads sur le même noyau et voir ce qui se passe?
David donné
Sur les cœurs Intel, ce nouveau RDTSC fonctionne parfaitement sur tous les cœurs, en particulier les cœurs sur le même processeur (c'est-à-dire le même socket). Fait intéressant, si les deux threads sont exécutés sur le même noyau, les latences descendent à 4-10 us sur le noyau le plus récent et env. 3 us sur l'ancien noyau.
Michael Goldshteyn
Juste un commentaire général - se fier aux TSC pour être synchronisés est au mieux incertain, bien que dans votre cas spécifique, puisque vous utilisez deux cœurs sur une puce physique, cela devrait fonctionner.
twalberg

Réponses:

95

La solution au problème de performances de réveil des threads défectueux dans les noyaux récents a à voir avec le passage au intel_idlepilote cpuidle de acpi_idle, le pilote utilisé dans les noyaux plus anciens. Malheureusement, le intel_idlepilote ignore la configuration du BIOS de l'utilisateur pour les états C et danse sur sa propre mélodie . En d'autres termes, même si vous désactivez complètement tous les états C dans le BIOS de votre PC (ou serveur), ce pilote les forcera toujours pendant les périodes de brève inactivité, qui se produisent presque toujours à moins d'un benchmark synthétique consommant tout le cœur (par exemple, stress ) est en cours d'exécution. Vous pouvez surveiller les transitions d'état C, ainsi que d'autres informations utiles liées aux fréquences du processeur, à l'aide du merveilleux outil Google i7z sur la plupart des matériels compatibles.

Pour voir quel pilote cpuidle est actuellement actif dans votre configuration, il suffit de saisir le current_driverfichier dans la cpuidlesection ci- /sys/devices/system/cpudessous:

cat /sys/devices/system/cpu/cpuidle/current_driver

Si vous souhaitez que votre système d'exploitation Linux moderne ait la latence de changement de contexte la plus faible possible, ajoutez les paramètres de démarrage du noyau suivants pour désactiver toutes ces fonctionnalités d'économie d'énergie:

Sur Ubuntu 12.04, vous pouvez le faire en les ajoutant à l' GRUB_CMDLINE_LINUX_DEFAULTentrée dans /etc/default/grubpuis en les exécutant update-grub. Les paramètres de démarrage à ajouter sont:

intel_idle.max_cstate=0 processor.max_cstate=0 idle=poll

Voici les détails sanglants sur ce que font les trois options de démarrage:

La mise intel_idle.max_cstateà zéro rétablira soit votre pilote cpuidle acpi_idle(au moins selon la documentation de l'option), soit le désactivera complètement. Sur ma boîte, il est complètement désactivé (c'est-à-dire que l'affichage du current_driverfichier dans /sys/devices/system/cpu/cpuidleproduit une sortie de none). Dans ce cas , la deuxième option de démarrage, processor.max_cstate=0est inutile. Cependant, la documentation indique que la définition de max_cstate à zéro pour le intel_idlepilote doit rétablir le système d'exploitation vers le acpi_idlepilote. Par conséquent, je mets la deuxième option de démarrage au cas où.

L' processor.max_cstateoption définit l'état C maximum du acpi_idlepilote sur zéro, en le désactivant également. Je n'ai pas de système sur lequel je puisse tester cela, car il intel_idle.max_cstate=0supprime complètement le pilote du cpuidle sur tout le matériel dont je dispose. Cependant, si votre installation vous ramène de intel_idleà acpi_idleavec uniquement la première option de démarrage, veuillez me faire savoir si la deuxième option a processor.max_cstatefait ce qu'elle a été documentée dans les commentaires afin que je puisse mettre à jour cette réponse.

Enfin, le dernier des trois paramètres, idle=pollest un vrai porc de puissance. Il désactivera C1 / C1E, ce qui supprimera le dernier bit de latence restant au détriment d'une consommation d'énergie beaucoup plus élevée, alors n'utilisez celui-ci que lorsque c'est vraiment nécessaire. Pour la plupart, ce sera exagéré, car la latence C1 * n'est pas si grande. En utilisant mon application de test fonctionnant sur le matériel que j'ai décrit dans la question initiale, la latence est passée de 9 us à 3 us. Il s'agit certainement d'une réduction significative pour les applications très sensibles à la latence (par exemple, le trading financier, la télémétrie / suivi de haute précision, l'acquisition de données haute fréquence, etc.), mais cela ne vaut peut-être pas le coup de puissance électrique encouru pour la grande majorité des applications de bureau. La seule façon de savoir avec certitude est de profiler l'amélioration des performances de votre application par rapport à

Mettre à jour:

Après des tests supplémentaires avec divers idle=*paramètres, je l' ai découvert que la mise idleà mwaitsi votre matériel est une bien meilleure idée. Il semble que l'utilisation des MWAIT/MONITORinstructions permette au CPU d'entrer en C1E sans qu'aucune latence notable ne soit ajoutée au temps de réveil du thread. Avec idle=mwait, vous obtiendrez des températures de processeur plus fraîches (par rapport à idle=poll), une consommation d'énergie moindre et vous conserverez toujours les excellentes latences faibles d'une boucle d'inactivité d'interrogation. Par conséquent, mon ensemble recommandé mis à jour de paramètres de démarrage pour la latence de réveil des threads à faible processeur basé sur ces résultats est:

intel_idle.max_cstate=0 processor.max_cstate=0 idle=mwait

L'utilisation de idle=mwaitau lieu de idle=pollpeut également aider à l'initiation de Turbo Boost (en aidant le processeur à rester en dessous de son TDP [Thermal Design Power]) et de l'hyperthreading (pour lequel MWAIT est le mécanisme idéal pour ne pas consommer tout un noyau physique tout en étant en même temps. temps en évitant les états C supérieurs). Cela n'a pas encore été prouvé lors des tests, ce que je continuerai de faire.

Mise à jour 2:

L' mwaitoption idle a été supprimée des noyaux 3.x plus récents (merci à l'utilisateur ck_ pour la mise à jour). Cela nous laisse avec deux options:

idle=halt- Doit fonctionner aussi bien que mwait, mais testez pour être sûr que c'est le cas avec votre matériel. L' HLTinstruction est presque équivalente à une MWAITindication d'état avec 0. Le problème réside dans le fait qu'une interruption est nécessaire pour sortir d'un état HLT, alors qu'une écriture mémoire (ou une interruption) peut être utilisée pour sortir de l'état MWAIT. En fonction de ce que le noyau Linux utilise dans sa boucle d'inactivité, cela peut rendre MWAIT potentiellement plus efficace. Donc, comme je l'ai dit test / profil et voyez si cela répond à vos besoins de latence ...

et

idle=poll - L'option la plus performante, au détriment de la puissance et de la chaleur.

Michael Goldshteyn
la source
Désolé, mais pourquoi vous attendiez-vous à ce que les états C soient gérés par le firmware? Les états de suspension sont des états d'exécution, et ils sont gérés par le système d'exploitation par conception. Comme vous l'avez découvert, si vous ne voulez pas suspendre l'exécution, ne l'utilisez pas.
Andy Ross
6
Désolé, mais les états C, EIST et C1E peuvent être désactivés dans le BIOS. Je m'attends à ce que le système d'exploitation respecte mes paramètres BIOS. Cela est particulièrement vrai, étant donné les outils et la documentation épouvantables dans ce cas.
Michael Goldshteyn
4
Éteint via votre bios, peut-être. Je ne connais rien dans une spécification pertinente qui l'exige. Désolé, mais "attendre" quelque chose du BIOS va vous mordre à plusieurs reprises. La meilleure chose que le micrologiciel puisse faire sur un PC moderne est rien. Je suis désolé que vous ayez été surpris, mais franchement, c'est une erreur de l'utilisateur. Votre référence mesurait les temps de suspension et de reprise.
Andy Ross
19
L'un des rôles de la sélection des fonctionnalités du BIOS est d'activer / de désactiver les périphériques. Dans certains cas, ces sélections sont forcées sur le système d'exploitation (par exemple, USB sur la carte mère, eSATA et NIC). Dans d'autres, le système d'exploitation est censé respecter vos souhaits (par exemple, EIST, états C, Hyperthreading, Execute Disable, AES-NI, virtualisation, etc.). Le BIOS fournit une seule surface centrale de sélection de périphériques / fonctionnalités qui est indépendante du système d'exploitation. Cela permet à l'utilisateur d'installer plusieurs systèmes d'exploitation (peut-être très différents) sur l'hôte qui utilisent tous les mêmes fonctionnalités matérielles. Cependant, cette réponse est subjective et devra donc accepter d'être en désaccord.
Michael Goldshteyn
1
idle = mwait n'est plus pris en charge dans le noyau 3.x récent lkml.org/lkml/2013/2/10/21 un autre conseil?
ck_
8

Peut-être que ce qui est devenu plus lent, c'est le futex, la pierre angulaire des variables de condition. Cela éclairera un peu:

strace -r ./test_latency 0 1 &> test_latency_strace & sleep 8 && killall test_latency

puis

for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done

qui montrera les microsecondes prises pour les appels système intéressants, triées par temps.

Sur le noyau 2.6.32

$ for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done
futex
 1.000140 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000129 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000124 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000119 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000106 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000103 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000102 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 0.000125 futex(0x7f98ce4c0b88, FUTEX_WAKE_PRIVATE, 2147483647) = 0
 0.000042 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000038 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000037 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000030 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000029 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 0
 0.000028 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000027 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000018 futex(0x7fff82f0ec3c, FUTEX_WAKE_PRIVATE, 1) = 0
nanosleep
 0.000027 nanosleep({1, 0}, {1, 0}) = 0
 0.000019 nanosleep({1, 0}, {1, 0}) = 0
 0.000019 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, 0x7fff82f0eb40) = ? ERESTART_RESTARTBLOCK (To be restarted)
 0.000017 nanosleep({1, 0}, {1, 0}) = 0
rt_sig
 0.000045 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000040 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000038 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000034 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000033 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000032 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000032 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000028 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000028 rt_sigaction(SIGRT_1, {0x37f8c052b0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x37f8c0e4c0}, NULL, 8) = 0
 0.000027 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000027 rt_sigaction(SIGRTMIN, {0x37f8c05370, [], SA_RESTORER|SA_SIGINFO, 0x37f8c0e4c0}, NULL, 8) = 0
 0.000027 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000023 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000023 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000022 rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
 0.000022 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000021 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000021 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000019 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0

Sur le noyau 3.1.9

$ for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done
futex
 1.000129 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000126 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000122 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000115 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000114 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000112 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000109 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 0.000139 futex(0x3f8b8f2fb0, FUTEX_WAKE_PRIVATE, 2147483647) = 0
 0.000043 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000041 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000037 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000036 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000034 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000034 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
nanosleep
 0.000025 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000022 nanosleep({1, 0}, {0, 3925413}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
 0.000021 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
rt_sig
 0.000045 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000044 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000043 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000040 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000038 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000037 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000036 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000036 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000035 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000034 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigaction(SIGRT_1, {0x3f892067b0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x3f8920f500}, NULL, 8) = 0
 0.000026 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000026 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000024 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000023 rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
 0.000023 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000022 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000019 rt_sigaction(SIGRTMIN, {0x3f89206720, [], SA_RESTORER|SA_SIGINFO, 0x3f8920f500}, NULL, 8) = 0

J'ai trouvé ce rapport de bogue vieux de 5 ans qui contient un test de performance "ping-pong" qui compare

  1. mutex libpthread à thread unique
  2. variable de condition libpthread
  3. vieux signaux Unix

Je devais ajouter

#include <stdint.h>

afin de compiler, ce que j'ai fait avec cette commande

g++ -O3 -o condvar-perf condvar-perf.cpp -lpthread -lrt

Sur le noyau 2.6.32

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    29085 us; per iteration:   29 ns / 9.4e-05 context switches.
c.v. ping-pong test   elapsed:  4771993 us; per iteration: 4771 ns / 4.03 context switches.
signal ping-pong test elapsed:  8685423 us; per iteration: 8685 ns / 4.05 context switches.

Sur le noyau 3.1.9

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    26811 us; per iteration:   26 ns / 8e-06 context switches.
c.v. ping-pong test   elapsed: 10930794 us; per iteration: 10930 ns / 4.01 context switches.
signal ping-pong test elapsed: 10949670 us; per iteration: 10949 ns / 4.01 context switches.

Je conclus qu'entre les noyaux 2.6.32 et 3.1.9, le changement de contexte a effectivement ralenti, mais pas autant que vous l'observez dans le noyau 3.2. Je me rends compte que cela ne répond pas encore à votre question, je vais continuer à creuser.

Edit: J'ai trouvé que changer la priorité en temps réel du processus (les deux threads) améliore les performances sur 3.1.9 pour correspondre à 2.6.32. Cependant, définir la même priorité sur 2.6.32 le ralentit ... allez comprendre - je vais l'examiner plus en détail.

Voici mes résultats maintenant:

Sur le noyau 2.6.32

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    29629 us; per iteration:   29 ns / 0.000418 context switches.
c.v. ping-pong test   elapsed:  6225637 us; per iteration: 6225 ns / 4.1 context switches.
signal ping-pong test elapsed:  5602248 us; per iteration: 5602 ns / 4.09 context switches.
$ chrt -f 1 ./condvar-perf 1000000
NPTL
mutex                 elapsed:    29049 us; per iteration:   29 ns / 0.000407 context switches.
c.v. ping-pong test   elapsed: 16131360 us; per iteration: 16131 ns / 4.29 context switches.
signal ping-pong test elapsed: 11817819 us; per iteration: 11817 ns / 4.16 context switches.
$ 

Sur le noyau 3.1.9

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    26830 us; per iteration:   26 ns / 5.7e-05 context switches.
c.v. ping-pong test   elapsed: 12812788 us; per iteration: 12812 ns / 4.01 context switches.
signal ping-pong test elapsed: 13126865 us; per iteration: 13126 ns / 4.01 context switches.
$ chrt -f 1 ./condvar-perf 1000000
NPTL
mutex                 elapsed:    27025 us; per iteration:   27 ns / 3.7e-05 context switches.
c.v. ping-pong test   elapsed:  5099885 us; per iteration: 5099 ns / 4 context switches.
signal ping-pong test elapsed:  5508227 us; per iteration: 5508 ns / 4 context switches.
$ 
amdn
la source
Je l'ai exécuté sur Fedora et CentOS, je n'ai pas Ubuntu. Je publierai mes résultats.
amdn
OK, je l'ai exécuté sur les deux hôtes (c'est-à-dire et sur différents noyaux) et les résultats ne montrent presque aucune disparité. Donc, ce test n'a pas mis en évidence de différences. Le temps d'appel futex diffère à la quatrième décimale - une réduction insignifiante des performances. Attendez, les nombres entiers sont-ils en secondes? Je viens de voir que vous avez publié vos résultats et ils semblent similaires aux miens ...
Michael Goldshteyn
Ok, cela exclut l'implémentation de futex - nous revenons à votre théorie du changement de contexte .... n'hésitez pas à supprimer cette réponse car elle appartient vraiment aux commentaires ... Je voulais juste la possibilité de formater les commandes.
amdn
Oui, les temps sont en secondes ... les appels au futex qui durent plus d'une seconde sont pour le thread en attente de la condition.
amdn
Alors, que faites-vous si quelque chose glanez-vous des résultats?
Michael Goldshteyn
1

Vous pouvez également voir les processeurs cliqueter dans les processus plus récents et les noyaux Linux en raison du pilote pstate qui est séparé des états c. Donc, en plus, pour désactiver cela, vous le paramètre de noyau suivant:

intel_pstate=disable

Kyle Brandt
la source