Pourquoi est std::mutex plus vite que std::atomique?
Je veux mettre des objets dans std::vector
en mode multithread. J'ai donc décidé de comparer les deux approches: l'une utilise des std::atomic
et les autres std::mutex
. Je vois que la seconde approche est plus rapide que le premier. Pourquoi?
J'utilise GCC 4.8.1 et, sur ma machine (8 threads), je vois que la première solution nécessite 391502
microsecondes et la deuxième solution nécessite 175689
microsecondes.
#include <vector>
#include <omp.h>
#include <atomic>
#include <mutex>
#include <iostream>
#include <chrono>
int main(int argc, char* argv[]) {
const size_t size = 1000000;
std::vector<int> first_result(size);
std::vector<int> second_result(size);
std::atomic<bool> sync(false);
{
auto start_time = std::chrono::high_resolution_clock::now();
#pragma omp parallel for schedule(static, 1)
for (int counter = 0; counter < size; counter++) {
while(sync.exchange(true)) {
std::this_thread::yield();
};
first_result[counter] = counter;
sync.store(false) ;
}
auto end_time = std::chrono::high_resolution_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count() << std::endl;
}
{
auto start_time = std::chrono::high_resolution_clock::now();
std::mutex mutex;
#pragma omp parallel for schedule(static, 1)
for (int counter = 0; counter < size; counter++) {
std::unique_lock<std::mutex> lock(mutex);
second_result[counter] = counter;
}
auto end_time = std::chrono::high_resolution_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count() << std::endl;
}
return 0;
}
1. Postez votre compilateur, les options de compilation & la mesure des résultats, s'il vous plaît. 2. Faire quelque chose d'observable avec les données obtenues après la mesure, sinon un "suffisamment bonne" de l'optimiseur peut supprimer le code comme mort.
En 32 bits et la version validée avec Visual Studio 2013-je obtenir 0, 46800 et 64 bits me donne 0, 62400 constante, de sorte qu'il semble atomique est soit super rapide, ou le harnais de test n'est pas vraiment de travail. Vous devez également savoir, dans le cas où vous l'utilisez, que dans Visual Studio 2013 et au-dessous de
Ce code est gravement mis à mal, peu importe. Les opérations atomiques avec
J'ai mis à jour mon code. Maintenant quand j'utilise quatre fils la première solution est plus rapide que la seconde (25-30%). Mais la première solution est plus lent que la seconde si je augmenter le nombre de threads (20-25%).
Qui s'en soucie. Le code est toujours en panne. Quelles conclusions avez-vous pense que vous pouvez en tirer? De code cassé est le plus rapide? De code cassé est plus lent? Comment est l'un de ceux-elle utile?
En 32 bits et la version validée avec Visual Studio 2013-je obtenir 0, 46800 et 64 bits me donne 0, 62400 constante, de sorte qu'il semble atomique est soit super rapide, ou le harnais de test n'est pas vraiment de travail. Vous devez également savoir, dans le cas où vous l'utilisez, que dans Visual Studio 2013 et au-dessous de
high_resolution_clock
n'est pas différents que les system_clock
. stackoverflow.com/q/16299029/920069Ce code est gravement mis à mal, peu importe. Les opérations atomiques avec
memory_order_relaxed
ne sont pas les opérations de synchronisation.J'ai mis à jour mon code. Maintenant quand j'utilise quatre fils la première solution est plus rapide que la seconde (25-30%). Mais la première solution est plus lent que la seconde si je augmenter le nombre de threads (20-25%).
Qui s'en soucie. Le code est toujours en panne. Quelles conclusions avez-vous pense que vous pouvez en tirer? De code cassé est le plus rapide? De code cassé est plus lent? Comment est l'un de ceux-elle utile?
OriginalL'auteur Sergey Malashenko | 2015-04-09
Vous devez vous connecter pour publier un commentaire.
Je ne pense pas que votre question ne peut être répondu en se référant seulement à la norme - les mutex sont aussi dépend de la plateforme, comme ils peuvent l'être. Cependant, il y a une chose qui mérite d'être mentionné.
Mutex ne sont pas lent. Vous avez peut-être vu quelques articles, que de comparer leurs performances à l'encontre de la coutume de spin-serrures et d'autres "léger" choses", mais ce n'est pas la bonne approche, et ne sont pas interchangeables.
Verrous de rotation sont considérablement rapide, quand elles sont verrouillées (acquis) pour une quantité de temps relativement court - acquisition est très bon marché, mais d'autres threads, qui sont aussi en train d'essayer de verrouillage, sont actifs pendant tout ce temps (constamment en cours d'exécution en boucle).
Personnalisé spin-lock pourraient être mises en œuvre de cette façon:
Mutex est un primitif, qui est beaucoup plus compliqué. En particulier, sur Windows, nous avons deux de ces primitives - Section Critique, qui fonctionne dans un processus de base et Mutex, qui n'ont pas cette limitation.
Verrouillage mutex (ou section critique) est beaucoup plus cher, mais l'OS a la capacité de mettre vraiment les autres threads en attente de "sommeil", qui améliore les performances et permet de planificateur de tâches efficace de la gestion des ressources.
Pourquoi j'ai écrit ce texte? Parce que les mutex sont souvent dits "hybrides mutex". Lorsque de tels mutex est verrouillé, il se comporte comme un simple spin-lock - autres threads en attente effectuer un certain nombre de "tours" et puis lourds mutex est verrouillé pour éviter de gaspiller des ressources.
Dans votre cas, mutex est verrouillé dans chaque itération de boucle pour exécuter cette instruction:
Il ressemble à un rapide, tellement "vrai" mutex ne peut jamais être verrouillé. Qui signifie, que, dans ce cas, votre "mutex" peuvent être aussi rapides que les atomique en fonction de la solution (car il devient atomique est basée sur la solution elle-même).
Aussi, dans la première solution que vous avez utilisé une sorte de spin-lock-comme comportement, mais je ne sais pas si ce comportement est prévisible dans l'environnement multi-thread. Je suis assez sûr, que le "verrouillage" doit avoir
acquire
sémantique, tandis que le déverrouillage est unrelease
op.Relaxed
de la mémoire de la commande peut-être trop faible pour ce cas d'utilisation.J'ai édité le code plus compact et plus correcte. Il utilise le
std::atomic_flag
, qui est le seul type (contrairement àstd::atomic<>
spécialisations), qui est garanti pour être sans verrouillage (mêmestd::atomic<bool>
ne vous donne pas que).Également, en se référant au commentaire ci-dessous sur "ne pas céder": c'est une question de cas spécifiques et exigences. Verrous de rotation sont une part très importante de multi-thread de programmation et leur performance peut souvent être améliorée en modifiant légèrement son comportement. Par exemple, la bibliothèque Boost met en œuvre
spinlock::lock()
comme suit:source: boost/smart_ptr/detail/spinlock_std_atomic.php
Où
detail::yield()
est (Win32 version):[source: http://www.boost.org/doc/libs/1_66_0/boost/smart_ptr/detail/yield_k.hpp%5D
Tout d'abord, fil de rotations pour un nombre fixe de fois (4 dans ce cas). Si le mutex est toujours verrouillé,
pause
enseignement (si disponible) ouSleep(0)
est appelé, qui, fondamentalement, les causes du contexte de l'interrupteur et permet planificateur de donner un autre thread bloqué une chance de faire quelque chose d'utile. Ensuite,Sleep(1)
est appelé à effectuer une véritable (court) du sommeil. Très sympa!Aussi, cette déclaration:
n'est pas entièrement vrai. Le but de spinlock est de servir comme un moyen rapide, facile à mettre en œuvre de verrouillage primitive -, mais il doit encore être écrit correctement, avec certains scénarios possibles dans l'esprit. Par exemple, Intel dit (au sujet du coup de pouce de l'utilisation de
_mm_pause()
comme une méthode de rendement à l'intérieur delock()
):Donc, les mises en œuvre comme
void lock() { while(m_flag.test_and_set(std::memory_order_acquire)); }
peut-être pas aussi bon qu'il semble.
Vous devriez ai utilisé le préexistante
std::atomic_flag
classe pour cette. C'est la façon dont "un" spin-lock devrait ressembler.Cela peut ou peut pas être vrai. J'ai mis à jour la réponse à l'adresse de votre commentaire. Même chose pour @bit2shift - de la mise en œuvre de spinlock ne peut pas être "bon" dans tous les cas. Par exemple, de Stimuler l'utilise très agréable personnalisé rendement de la stratégie à l'intérieur de
lock()
pour optimiser les performances de son spinlock mise en œuvre. Concernantstd::atomic_lock
- j'ai mis à jour le code. C'est en effet le seul type qui est garanti pour être sans verrouillage, de sorte qu'il est un choix naturel lors de la rédaction de la coutume spinlock.OriginalL'auteur Mateusz Grzejek