Serrure circulaire sans tampon
Je suis dans le processus de conception d'un système qui se connecte à un ou plusieurs flux de flux de données et de faire une analyse sur les données de déclencher des événements en fonction du résultat. Dans un typique multi-thread producteur/consommateur, de l'installation, je vais avoir plusieurs threads producteurs de mettre les données dans une file d'attente, et plusieurs consommateurs fils de la lecture des données, et les consommateurs ne sont intéressés que dans le dernier point de données, plus le nombre n de points. Le producteur threads vont bloquer si lente à la consommation ne peut pas suivre, et bien sûr des consommateurs threads bloc quand il n'y a pas non transformés, les mises à jour. À l'aide d'un typique simultanées de la file d'attente avec un lecteur/graveur de verrouillage fonctionnent bien, mais le taux de données pourrait être énorme, donc j'ai voulu réduire mon surcharge de verrouillage surtout écrivain serrures pour les producteurs. Je pense qu'une circulaire sans verrouillage de la mémoire tampon est ce dont j'avais besoin.
Maintenant deux questions:
-
Est circulaire sans verrouillage de la mémoire tampon la réponse?
-
Si oui, avant que je roule mes propres, savez-vous tout public mise en œuvre qui permettra de s'adapter à mon besoin?
Tous les pointeurs dans la mise en œuvre d'une circulaire sans verrouillage de la mémoire tampon sont toujours les bienvenus.
BTW, faire cela en C++ sur Linux.
Quelques informations supplémentaires:
Le temps de réponse est critique pour mon système. Idéalement, le consommateur threads souhaitez voir les mises à jour à venir dès que possible parce qu'un supplément de 1 milliseconde retard pourrait rendre le système de valeur, ou dont la valeur est beaucoup moins.
L'idée de design, je suis penchée vers est un semi-lock-gratuit tampon circulaire où le producteur thread mettre les données dans la mémoire tampon aussi vite qu'il le peut, appelons le chef de la mémoire tampon A, sans blocage, sauf si le tampon est plein, lors d'Une rencontre à la fin de tampon Z. Consommation des threads de chaque titulaire de deux pointeurs du tampon circulaire, P et Pn, où P est le fil de la mémoire tampon locale tête, et Pn est nième élément après P. Chaque thread consommateur de promouvoir ses P et Pn une fois terminé le traitement actuel P et la fin de pointeur de tampon Z est avancé, plus lent Pn. Lorsque P rattraper à Un, ce qui signifie plus de nouvelle mise à jour de processus, le consommateur tours et ne occupés à attendre pour faire avancer de nouveau. Si thread consommateur spin pendant trop longtemps, il peut être mis pour dormir et d'attendre une variable de condition, mais je suis d'accord avec la consommation prise de cycle du PROCESSEUR en attente de mise à jour parce que de ne pas augmenter mon temps de latence (je vais avoir plus de cœurs que les threads). Imaginez que vous avez une piste circulaire, et le producteur est en cours d'exécution en face d'un tas de consommateurs, la clé est de régler le système de sorte que le producteur est généralement en passant à seulement quelques pas d'avance sur les consommateurs, et la plupart de ces opérations peuvent être effectuées à l'aide d'un lock-gratuit techniques. Je comprends les détails de la mise en œuvre n'est pas simple...ok, très dur, c'est pourquoi je veux apprendre des erreurs des autres avant de faire un peu de mes propres.
- Je pense qu'il serait utile si vous esquissez l'API que vous souhaitez que cette structure de données à mettre en œuvre.
- Quelque chose que j'ai apprise, c'est prendre de gros morceaux de travail. Je ne sais pas la taille de vos éléments de travail, mais vous pouvez augmenter l'efficacité si vous pouvez produire des gros morceaux et consommer de gros morceaux. Vous pouvez aussi augmenter par la consommation de taille variable morceaux pour les consommateurs n'ont pas tous fini à la fois et affronter la file d'attente de données.
- Une autre chose à penser est que si vous avez besoin d'un tampon ou d'une série de tampons. Vous pourriez avoir de producteur/consommateur paires partageant un tampon, et quand un tampon est plein, le producteur ou le consommateur passe temporairement à un autre tampon. C'est une forme de travail de voler.
- l'api est probablement juste une mise en file d'attente méthode plus une file d'attente de la méthode ... deux d'entre eux étant synchrone/blocage de modes, à savoir dequeue devrait bloquer s'il n'y avait rien dans la file d'attente, et de mettre en file d'attente du bloc si le buffer circulaire étaient pleins.
- Efficace sans verrouillage des algorithmes uniques flocons de neige dont la découverte habituellement mérite un article de recherche. Je ne vais pas tenter de répondre à cette question jusqu'à ce que l'OP se distingue par une des exigences réelles de ce qu'il pense d'une solution devrait ressembler.
- Zan Lynx: Oui, s'emmitoufler travail peut réduire de verrouillage de frais généraux. J'ai cela dans mes précédents systèmes. J'ai également de la dynamique de regroupement de la taille de la base sur la charge de travail. Il a fonctionné assez bien, mais cette fois, le débit est trop rapide pour mon ancien système, c'est pourquoi j'ai à repenser l'ensemble de la chose.
- Une milliseconde est très rapide, calendrier de l'échéance non modifiée de Linux. Si un autre processus à exécuter, alors vous pourriez facilement passer à côté. Vous devez utiliser des priorités en temps réel, et même alors, je ne suis pas sûr que vous pouvez répondre de façon fiable aux ces délais. Êtes-vous sûr que vous devez être que sensible? Pouvez-vous faire seulement les producteurs de fast, les mettent en œuvre par exemple un pilote de périphérique, et détendez-vous sur les exigences sur les consommateurs?
- Doug, ce que je voulais dire c'est 1 milliseconde fera une grande différence. Je n'ai pas fait tout de profilage pour voir si d'autres processus du système peut-être causer la latence sur mon système, mais je pense que sur la moyenne de la préemption par le processus de système ne va pas avoir un impact significatif.
- pourquoi ne pas simplement utiliser les sémaphores? Pas de verrouillage et de blocage, le consommateur ne va dormir lorsque le tampon est vide, producteur lorsque le tampon est plein. Quel est le mal?
- Si vous avez vraiment besoin de dur-comportement en temps réel, c'est probablement la peine de regarder dans l'exécution du programme comme un dur en temps réel de la tâche sur une unité de système d'exploitation temps réel. Pour un Linux-friendly version, découvrez Xenomai, il vous permettra de courir en temps réel et régulier (soft real-time/non temps réel) processus simultanément dans le même environnement.
Vous devez vous connecter pour publier un commentaire.
J'ai fait une étude particulière de sans verrouillage des structures de données dans les deux dernières années. J'ai lu la plupart des articles sur le sujet (il n'y a environ quarante ou bien que seulement environ dix ou quinze sont une vraie utilité 🙂
Autant que je sache, un lock-gratuit tampon circulaire n'a pas été inventé. Le problème sera de s'occuper de la situation complexe où un lecteur dépasse un écrivain ou vis-versa.
Si vous n'avez pas passé au moins six mois d'études sans verrouillage des structures de données, n'essayez pas d'écrire un vous-même. Vous vous trompez, et il peut ne pas être évident pour vous que des erreurs existent, jusqu'à ce que votre code ne fonctionne pas, après le déploiement, sur de nouvelles plateformes.
Je crois cependant qu'il existe une solution à votre exigence.
Vous devez le lier à un lock-free file d'attente avec un lock-gratuit-liste.
Le libre-liste va vous donner de pré-affectation et afin de parer à la (fiscalement cher) exigence d'un lock-free allocateur; lorsque le libre-la liste est vide, vous reproduire le comportement d'un tampon circulaire par instantanément la file d'attente un élément de la file d'attente et en l'utilisant à la place.
(Bien sûr, dans une serrure à base de mémoire tampon circulaire, une fois que le verrouillage est obtenu, l'obtention d'un élément est très rapide fondamentalement juste un déréférencement de pointeur - mais vous n'obtiendrez pas que, dans tous les sans verrouillage de l'algorithme; ils ont souvent d'aller bien sortir de leur façon de faire les choses; les frais généraux de l'échec d'un libre-liste pop suivie par une file d'attente est sur un pied d'égalité avec la quantité de travail tout sans verrouillage de l'algorithme devra faire).
Michael et Scott développé un très bon sans verrouillage de la file d'attente en 1996. Un lien ci-dessous va vous donner suffisamment de détails pour traquer le PDF de leur article; Michael et Scott, FIFO
Un lock-gratuit-liste est la plus simple sans verrouillage de l'algorithme et en fait, je ne pense pas que j'ai vu un papier réel pour elle.
volatile
est nécessaire pour s'assurer que le compilateur ne sera pas tenter de réorganiser ou d'éliminer les lectures et les écritures. Par exemple, siSPI_*
ne sont pas volatils,for(i=0;i<16;i++){SPI_DAT = tx[i]; temp=16000; do {} while(!SPI_RDY && --temp); if (!temp) break; rx[i] = SPI_DAT;}
pourrait être réécrite (en supposant que nitemp
nii
a été utilisé plus tard dans le code)if (SPI_RDY) {memcpy(rx,tx,16); SPI_DAT = tx[15];} else SPI_DAT = tx[0];
. Le dernier code serait beaucoup plus "efficace", mais ne serait pas très efficace pour la lecture ou l'écriture de données SPI.volatile
les accès à la mémoire de ne pas être réorganisée avec d'autresvolatile
accès. Mais dans la norme ISO C, c'est tout ce que vous obtenez. Dans MSVC,volatile
va bien au-delà de cela, mais ces jours, vous devriez simplement utiliserstd::atomic
avecmemory_order_release
ouseq_cst
ou ce que vous voulez.Le terme de l'art de ce que vous voulez est un sans verrouillage de la file d'attente. Il y a un excellente série de notes avec des liens vers le code et papiers par Ross Bencina. Le gars dont le travail que j'ai le plus confiance est Maurice Herlihy (pour les Américains, il prononce son prénom comme "Morris").
L'obligation pour les producteurs ou les consommateurs bloc si le tampon est vide ou plein suggère que vous devriez utiliser une normale de verrouillage de la structure de données, avec des sémaphores ou les variables de condition de faire les producteurs et les consommateurs de bloc jusqu'à ce que les données sont disponibles. Sans verrouillage code, en général, ne bloque pas sur de telles conditions - il spins ou abandonne les opérations qui ne peut pas être fait au lieu de la bloquer à l'aide de l'OS. (Si vous pouvez vous permettre d'attendre jusqu'à ce qu'un autre thread produit ou consomme de données, alors pourquoi en attente d'un verrou pour un autre thread pour terminer la mise à jour de la structure de données pire?)
Sur (x86/x64) Linux, intra-synchronisation des threads à l'aide de mutex est raisonnablement pas cher si il n'y a pas de conflit. Se concentrer sur la façon de réduire le temps que les producteurs et les consommateurs doivent se tenir sur leurs verrous. Étant donné que vous avez dit que vous ne se soucient que les N dernières données enregistrées points, je pense qu'un tampon circulaire serait faire raisonnablement bien. Cependant, je ne comprends pas vraiment comment cela s'inscrit dans le blocage de l'exigence et de l'idée de consommateurs fait de consommer (enlever) les données lues. (Voulez-vous consommateur de ne regarder le dernier N de points de données, et de ne pas les supprimer? Voulez-vous les producteurs à s'en soucient pas si les consommateurs ne peuvent pas suivre, et juste écraser les anciennes données?)
Aussi, comme Zan Lynx a commenté, vous pouvez agréger/tampon de vos données en gros morceaux quand vous avez beaucoup de venir dans. Vous pourriez tampon d'un nombre fixe de points, ou de toutes les données reçues dans un certain laps de temps. Cela signifie qu'il y aura moins d'opérations de synchronisation. Elle introduit la latence, cependant, mais si vous n'utilisez pas Linux temps réel, alors vous aurez à faire face à une étendue de toute façon.
Il y a une assez bonne série d'articles sur ce sur DDJ. Comme un signe de la difficulté de ce genre de choses peut être, c'est une correction sur un précédent article qui a mal. Assurez-vous de comprendre les erreurs avant de vous lancer à votre propre )-;
La mise en œuvre dans la bibliothèque boost est utile d'examiner. Il est facile à utiliser et assez haute performance. J'ai écrit un test & il a couru sur un quad core i7 ordinateur portable (8 threads) et d'obtenir ~4M de mise en file/file d'attente des opérations à la seconde. Une autre mise en œuvre ne sont pas mentionnés jusqu'à présent est la MPMC file d'attente à http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue. J'ai fait quelques tests simples avec cette mise en œuvre sur le même ordinateur portable avec 32 producteurs et 32 des consommateurs. Il est, comme annoncé, plus rapide que le coup de pouce lockless file d'attente.
Comme la plupart des autres réponses de l'état lockless programmation est dur. La plupart des implémentations sera difficile de détecter un coin de cas que de prendre un grand nombre de tests & de débogage pour le fixer. Ce sont généralement fixe avec attention le placement de barrières de mémoire dans le code. Vous trouverez également des preuves de l'exactitude publié de nombreux articles académiques. Je préfère tester ces implémentations avec une force brutale de l'outil. Tout lockless algorithme vous prévoyez sur l'utilisation dans la production doit être vérifiée de justesse à l'aide d'un outil comme http://research.microsoft.com/en-us/um/people/lamport/tla/tla.html.
Une technique utile pour réduire la contention est de hachage les articles dans plusieurs files d'attente et ont chaque consommateur dédié à un "sujet".
Pour la plus récente du nombre d'éléments que vos consommateurs sont intéressés dans l' - vous ne voulez pas de verrouillage de l'ensemble de la file d'attente et d'itérer sur de trouver un élément à remplacer - juste de publier les articles des N-uplets, c'est à dire tous les N derniers éléments. Des points Bonus pour la mise en œuvre où le producteur de bloc sur la totalité de la file d'attente (lorsque les consommateurs ne peuvent pas suivre) avec un délai d'attente, la mise à jour de ses locaux tuple cache - de cette façon vous ne mettez pas de contre-pression sur la source de données.
Je suis d'accord avec cet article et vous déconseillons l'utilisation de lock-libre de structures de données. Relativement récent article sur la serrure sans files d'attente fifo est cette, de rechercher d'autres documents rédigés par le même auteur(s), il y a aussi une thèse de Doctorat sur Chalmers concernant sans verrouillage des structures de données (j'ai perdu le lien). Cependant, vous ne dites pas que la taille de vos éléments sont -- sans verrouillage des structures de données travailler efficacement avec mot-de taille moyenne, de sorte que vous aurez à allouer dynamiquement de vos éléments s'ils sont plus grands qu'une machine mot (32 bits ou 64 bits). Si vous allouer dynamiquement des éléments, vous déplacer le (supposé, puisque vous n'avez pas profilé de votre programme et vous êtes essentiellement faire l'optimisation prématurée) goulot d'étranglement pour l'allocateur de mémoire, si vous avez besoin d'un lock-free allocateur de mémoire, par exemple, L'écoulement, et de les intégrer à votre application.
Sutter la file d'attente est sous-optimal, et il le sait. L'Art de la programmation Multicœur est une grande référence, mais ne faites pas confiance à la Java gars sur les modèles de mémoire, période. Ross liens que vous obtenez pas de réponse définitive car ils avaient leurs bibliothèques dans de tels problèmes, et ainsi de suite.
Faire sans verrouillage de la programmation est d'avoir des ennuis, sauf si vous voulez passer beaucoup de temps sur quelque chose qui vous sont clairement au-dessus de génie avant de résoudre le problème (à en juger par la description, c'est une commune de la folie de "à la recherche de la perfection" dans de cohérence de cache). Il faut des années et conduit à ne pas résoudre les problèmes et d'optimiser plus tard, une maladie commune.
volatile size_t m_rIndex, m_wIndx;
au lieu de C++11 std::atomique pour les indices, mais il me semble que cela dépend de l'acquisition-charge / release magasin sur le comportement (par exemple, dans d'autres threads doivent voir lem_buffer[m_wIndex] = value
magasin avant de voirm_wIndex = Next(m_wIndex)
). Ainsi, il arrive de travailler sur des systèmes x86, mais s'arrête sur le BRAS/PowerPC/whatever. Il est également inefficace, parce qu'au lieu de chargement à partir de lavolatile
dans une variable locale, elle maintient le re-référencement de la volatilité de la valeur dans laGet()
etPut()
fonctions.Je ne suis pas expert de matériel de modèles de mémoire et de verrouillage de libre-structures de données et j'ai tendance à éviter d'utiliser ces dans mes projets et je pars avec la traditionnelle verrouillé structures de données.
Cependant, j'ai récemment remarqué que la vidéo :
Lockless SPSC file d'attente basé sur l'anneau de la mémoire tampon
Ceci est basé sur de l'open source de haute performance de la bibliothèque Java appelé LMAX distruptor utilisé par un système d'échange : LMAX Distruptor
Basé sur la présentation ci-dessus, vous faire la tête et de la queue des pointeurs atomique et atomiquement vérifier l'état où la tête attrape la queue par derrière, ou vice versa.
Ci-dessous vous pouvez voir un très de base de C++11) mise en œuvre: la
size
, de sorte que le%
(modulo) est un peu juste au niveau du bit. Par ailleurs, la conservation d'un numéro de séquence dans votre slots serait de réduire les conflits entre le producteur et le consommateur. En cela, le producteur doit lire leswrite
position, et vice-versa, de sorte que la ligne de cache contenant ces atomique variables de ping-pongs entre les cœurs. Voir stackoverflow.com/questions/45907210/... pour un logement de numéro de séquence manière. (C'est un multi-producteur multi-consommation de file d'attente et pourrait être grandement simplifiée à un seul producteur/consommateur file d'attente comme ça.)memory_order_acquire
ourelease
, pas la valeur par défautseq_cst
. C'est une grande différence sur x86, oùseq_cst
magasins besoinmfence
(ouxchg
), maisrelease
magasins sont tout simplement x86 magasins. StoreLoad obstacles sont le plus cher de la barrière sur la plupart des autres architectures. (preshing.com/20120710/...)read
aprèsbuffer
dans la catégorie mise en page, il est dans une autre ligne de cache dewrite
. Donc, les deux fils ne seront mémoire cache de lecture des lignes écrites par les autres, plutôt que tous les deux dans la même ligne de cache. En outre, ils doivent êtresize_t
: il n'y a aucun point d'avoir 64 bits compteurs avec des pointeurs 32 bits. Et un type non signé rend modulo beaucoup plus efficace (godbolt.org/g/HMVL5C). Mêmeuint32_t
serait raisonnable pour presque toutes les utilisations. Il serait probablement préférable à un modèle de cette taille, ou d'allouer dynamiquement de la mémoire tampon.n
bits avec unAND
. par exemple,x % 8
=x & 7
, et au niveau du bit ET est beaucoup moins cher quediv
, ou même des trucs que vous pouvez faire avec de la compilation constante de temps diviseurs.Juste pour être complet: il y a bien testé sans verrouillage mémoire tampon circulaire dans OtlContainers, mais il est écrit en Delphi (TOmniBaseBoundedQueue est tampon circulaire et TOmniBaseBoundedStack est délimitée de la pile). Il y a également une surabondance de la file d'attente dans la même unité (TOmniBaseQueue). La surabondance de la file d'attente est décrit dans Dynamique sans verrouillage de file d'attente, en train de faire. La mise en œuvre initiale de la délimitée de la file d'attente (tampon circulaire) a été décrit dans Un lock-free file d'attente, enfin! mais le code a été mis à jour depuis.
C'est un vieux thread, mais comme il n'a pas été mentionné, mais - il y a un lock-free, circulaire, 1 producteur -> 1 à la consommation, FIFO disponibles dans la JUCE C++ cadre.
https://www.juce.com/doc/classAbstractFifo#details
Découvrez Perturbateur (Comment l'utiliser) qui est un anneau de la mémoire tampon que plusieurs threads peuvent s'abonner à:
Bien que c'est une vieille question, personne n'a indiqué DPDK's lockless anneau de la mémoire tampon. C'est un haut débit de l'anneau de la mémoire tampon qui prend en charge plusieurs producteurs et plusieurs consommateurs. Il fournit également de consommateur et de producteur modes, et l'anneau de la mémoire tampon, c'est d'attendre sans en SPSC mode. Il est écrit en C et prend en charge plusieurs architectures.
En outre, il prend en charge en Vrac et l'Éclatement des modes où les éléments peuvent être mis en file d'attente/dequeued en vrac. La conception de laisser multiples de consommateurs ou de producteurs multiples écrire à la file d'attente à la même époque par simple de réserver l'espace à travers le déplacement atomique pointeur.
Voici comment j'allais le faire:
Insertion consiste en l'utilisation d'un CAS avec un incrément de rouler sur la prochaine écriture. Une fois que vous avez un logement, ajouter de la valeur et puis définissez le vide/plein bits correspondant.
Déménagements exigent un contrôle de la peu avant de les tester sur underflows mais dans les autres, sont les mêmes que pour l'écrire, mais à l'aide de lecture d'index et de compensation du vide/plein bits.
Être averti,
Vous pouvez essayer lfqueue
Il est simple à utiliser, il est circulaire conception de verrouillage gratuit
Il y a des situations que vous n'avez pas besoin de verrouillage pour empêcher la condition de course, surtout quand vous avez un seul producteur et le consommateur.
Envisager de ce point de LDD3: