Je jouais avec Lors de la compilation du code ci-dessus sans optimisations à l'aide de Clang ++, j'ai eu le Suivre les points de repère: p> J'ai ensuite changé mon code ci-dessous: (Utilisation de seulement 1 thread) p> std :: thread code> et quelque chose bizarre surgivé:
real 0m2.304s
user 0m2.298s
sys 0m0.003s
4 Réponses :
Vous avez deux threads qui se battent sur la même variable, k code>. Donc, vous passez du temps où les processeurs disent «Processeur 1: Hé, savez-vous quelle valeur
k code> a? Processeur 2: Bien sûr, vous y allez! . Étant donné que
k code> n'est pas atomique, il n'y a pas non plus de garantie que Thread2 n'écrire pas une "vieille" valeur de
k code> de sorte que le prochain thread thread 1 lit la valeur, elle saute Retour 1, 2, 10 ou 100 étapes, et doit le refaire - en théorie qui ne pouvait conduire à aucune des boucles toutes les finitions, mais cela nécessiterait un peu de malchance. p>
C'est logique. Merci.
@ Magtheridon96: En plus de ce que dit les tapis, votre programme a une course de données et ainsi un comportement formellement indéfini. n'importe quoi i> pourrait arriver parce que vous accédez à une variable non atomique partagée de deux threads sans synchronisation. Le modèle de mémoire des processeurs X86 est quelque peu indicatif mais vous ne devriez vraiment pas compter sur cela.
@Johannesd Au début, je l'essais avec un std :: atomic
int code>. Les deux tests étaient plus rapides, mais les ratios entre les temps étaient à peu près les mêmes. Merci les gars. Vous m'avez montré l'importance d'avoir des unités de stockage atomique.
@ Magtheridon96: Il suffit de faire k code> un
std :: atomic
K = k + 1 code> ne serait toujours pas atomique. Cependant,
k ++ code> ou
k + = 1 code> serait. Mais la synchronisation entre les processeurs appliqués par les opérations atomiques, même si le verrouillage entraînerait toujours une surcharge - ce n'est tout simplement pas un cas d'utilisation pouvant être accéléré à l'aide de threads. Pensez-y comme essayant d'écrire une histoire avec votre ami - ce ne serait pas plus rapide que de le faire seul parce que vous devriez faire demi-tour.
@ Magtheridon96: std :: atomique code> ne permettra pas que les données soient partagées entre les deux threads - en fait, cela en fait un peu pisponible dans le sens où chaque écrit sur une force atomique Le processeur qui n'est pas à l'intérieur de la partie "atomique" pour "abandonner" sa copie de cette valeur et attendre que "Atomic" se termine avant de pouvoir chercher la nouvelle valeur. C'est pourquoi le partage de nombreuses données entre les threads est généralement plus lent que de fonctionner dans un seul fil.
@Johannesd explique également pourquoi j'ai eu une belle vitesse de boost lorsque j'ai changé k = k + 1 code> sur
k ++ code>. (D'environ 1 minute 11 secondes à 35 secondes après avoir changé le code pour utiliser un
std :: atomic
@Johannesd +1 pour l'analogie de l'histoire.
Cela devrait vraiment être un commentaire en réponse à la réponse de Matts Peterson, mais je voulais fournir des exemples de code.
Le problème est la conflit d'une ressource spécifique, ainsi qu'une Cacheline. P>
Alternative 1: P>
#include <cstdint> #include <thread> #include <vector> #include <stdlib.h> static const uint64_t ITERATIONS = 10000000000ULL; #define CACHE_LINE_SIZE 128 int main(int argc, const char** argv) { size_t numThreads = 1; if (argc > 1) { numThreads = strtoul(argv[1], NULL, 10); if (numThreads == 0) return -1; } std::vector<std::thread> threads; std::mutex kMutex; uint64_t k = 0; for (size_t t = 0; t < numThreads; ++t) { threads.emplace_back([=, &k]() { alignas(CACHE_LINE_SIZE) uint64_t myK = 0; // Imperfect division of labor, we'll fall short in some cases. for (uint64_t i = 0; i < ITERATIONS / numThreads; ++i) { myK++; } kMutex.lock(); k += myK; kMutex.unlock(); }); } for (size_t t = 0; t < numThreads; ++t) { threads[t].join(); } return 0; }
Merci d'avoir pris le temps de faire tout ça :). J'aime la 3ème variation.
Essayez le 3ème avec un objet qui est Alignas (128) (ou quelle que soit la taille de votre ligne de cache) - L'objectif étant de déplacer l'espace de travail du fil à un emplacement qui n'a pas aucun i> chevauchement avec quoi D'autres processeurs travaillent sur.
Enfin, sur un ordinateur au lieu d'un téléphone, corrigez les erreurs du compilateur.
me semble être la question plus importante que "Pourquoi ce travail n'a-t-il pas fait?" est "Comment puis-je obtenir cela pour travailler?" Pour la tâche à la main, je pense que std :: async code> (malgré
std :: thread code> directement.
#include <future>
#include <iostream>
int k = 0;
unsigned tasks = std::thread::hardware_concurrency();
unsigned reps = 1000000000 / tasks;
int main() {
std::vector<std::future<int>> f;
for (int i=0; i<tasks; i++)
f.emplace_back(std::async(std::launch::async,
[](){int j; for (j=0; j<reps; j++); return j;})
);
for (int i=0; i<tasks; i++) {
f[i].wait();
k += f[i].get();
}
std::cout << k << "\n";
return 0;
}
Je rencontre ce problème. Mon opinion est que, pour certains types de travail, le coût de la gestion du thread peut être plus que le bénéfice que vous obtenez de l'exécution de threads. Voici mon exemple de code, faisant du vrai travail dans une boucle, un grand nombre d'itérations, j'ai donc eu un numéro très cohérent avec la commande horaire.
Thead No_thread ========================= real 4m24 2m44s usr 0m54s 2m44s sys 0m6.2s 0m0.016s
Tous les threads font sont des têtes de bashing essayant de lire et d'écrire à k. Et quand la première finition, il doit encore attendre la seconde.
@chris:
k code> n'est pas
volatile code>, de sorte que les threads ne sont pas en compétition, car ils ne partagent pas efficacement
k code>.
@MOOINGDUCK:
volatile code> assure les écritures à la mémoire, mais pas d'avoir
volatile code> ne les empêche pas. La question indique spécifiquement "... sans optimisations ..." et il est typique des constructions non optimisées de suivre les instructions du programme très littéralement et non de "cache" des valeurs dans des registres. Sur le matériel Intel / AMD récent, il y a un rinçage automatique entre les caches de noyau accédant à la même adresse mémoire qui ralentiserait ceci.
@Tontyd: C'est un bon point sur les bâtiments de débogage ...
Le compilateur ne serait probablement pas en cache une variable globale même si vous avez laissé de côté volatile. Ce qui se passe consiste à écrire de vieilles valeurs qui déplace le compteur de 1 à 100000 surtout si un fil a été préempté après avoir lu K mais avant d'écrire K + 1.