0
votes

Piscine multithreading en C ++

Je développe un programme qui reçoit continuellement des cadres d'un flux vidéo et informatique une valeur estimée de la motion entre chaque paire de cadres.

En raison des limitations matérielles, je dois calculer l'algorithme d'estimation de mouvement (moi) dans la CPU, quelque chose qui prend environ 2 secondes par calcul. Pour cette raison, je veux mettre en œuvre l'algorithme ME avec multithreading. L'idée serait de recevoir les trames suivants du flux dans un fil principal lorsque la valeur de mouvement est calculée dans un autre thread.

Je l'ai fait en utilisant un fil par tâche, c'est-à-dire chaque fois qu'une paire de cadres est reçue, j'ai créé un nouveau thread pour calculer la valeur de mouvement. Cependant, en raison du temps écoulé dans le calcul de mouvement, de nombreux threads sont créés et fonctionnent simultanément, ce que je suppose que ce n'est pas très efficace.

Je pense que la meilleure façon de réimplacer est en utilisant une piscine de fil. Par exemple, d'une part ayant un fil principal qui reçoit les cadres et les stocker dans un tampon ou une file d'attente et d'autre part ayant 4 ou 8 threads fonctionnant simultanément et en lisant du tampon de réception, que si je ne me trompe pas devrait être protégé. par mutex. Cependant, le fil principal recevrait une trame beaucoup plus rapide qu'un calcul de mouvement se terminant et je ne sais pas comment gérer cela.

Je suis très nouveau pour C ++ et nouveau dans les threads, alors j'apprécierais que si vous pouvez me fournir une solution à Pseudocode juste pour commencer ma réimplémentation.

Merci beaucoup


5 commentaires

Eh bien, comme vous l'avez dit, vous pourriez utiliser Mutex! Donc, chaque fois que vous créez un fil, verrouillez le mutex, incrémentez le filetage. puis débloquer mutex. Créez uniquement un mutex lorsque le compteur est inférieur au nombre maximal ...


Quelle est votre stratégie pour sauter des cadres? Si vous commencez à traiter aveuglément le traitement des premiers cadres entrants, vous vous retrouverez dans un rythme où vous ne couvrez que les 16 premiers cadres de chaque seconde, au lieu d'un échantillonnage uniformément tout au long de la seconde.


Vos cadres entrants sont-ils en temps réel ou décodés d'un fichier? S'ils sont en temps réel, vous allez bien tomber derrière avec un calcul de deux secondes et avez besoin d'une bonne manière de faire face. S'ils d'un fichier, vous pouvez simplement arrêter de lire lorsque la file d'attente est pleine et attendez que vous rattrapiez un peu de retard.


@ Inutile, ils sont des cadres entrants en temps réel à 30fps, mais il est vrai que nous n'allons pas traiter toutes, l'idée serait d'obtenir environ 5-6 fps. Merci d'avoir répondu


OK, vous vous attendez donc à avoir besoin d'environ 10-12 threads pour produire 5-6 images par seconde. Avez-vous de nombreux noyaux gratuits?


3 Réponses :


1
votes

J'éviterais d'utiliser une piscine de fil dans ce cas. De Wikipedia (emphase mine):

[Un piscine de thread] augmente les performances et évite la latence dans l'exécution due à la création fréquente et à la destruction de threads pour Tâches de courte durée . .

Vos calculs de longue durée Nain Le temps nécessaire pour créer et détruire un fil, donc créer un fil pour chaque tâche semble raisonnable pour moi. Plus vous pouvez éviter les mutiles et le co., Mieux c'est. En ce qui concerne l'exécution de nombreux filets à la fois, le temps nécessaire pour basculer entre les threads est également nain par le temps de calcul, ce qui limite le nombre de fils utilisés ne vous donnerait qu'une très petite vitesse 1 .

Où vous pourriez avoir un problème, c'est si votre machine ne peut pas terminer les calculs suffisamment rapidement pour suivre les données entrantes. Si tous vos cœurs CPU fonctionnent à 100%, la seule chose que vous puissiez faire est de rendre vos calculs plus efficaces (peut-être descendre vos cadres vidéo?) Ou d'obtenir plus de puissance informatique.


Ce sont des cadres entrants en temps réel à 30fps.

1 Je dois noter que pour des applications en temps réel, vous devez limiter le nombre de threads utilisés au nombre de cœurs (ou un ou deux plus haut, profilé) . Cela réduira la latence entre recevoir un cadre et produire le résultat sans affecter la performance globale.


4 commentaires

Dépend de ce que vous appelez une "tâche". Si le traitement d'une paire de châssis est une "tâche", alors c'est relativement de courte durée.


Je pars de "prend environ 2 secondes par calcul" que j'ai pris pour signifier le traitement d'une paire de trame. Comparez vers le temps de création de fil microsecond selon Stackoverflow.com/a/27764581/7619380 .


Mon problème est que j'ai écrit de code pour tant de systèmes différents - des grands serveurs de base de données aux microcontrôleurs exécutant aux tarifs de l'horloge Sub-MHz - que je n'associe plus de valeurs numériques particulières avec des idées telles que courtes / longues, rapides / lentes , grand petit. J'identifie des significations abstraites à ces concepts. Quand j'entends "tâche de courte durée", je comprends que cela signifie "" fait une chose, puis se termine. " Quand j'entends "fil à long terme," je pense, "un nombre indéfini de choses, ..." Mais oui! Vous avez raison. Si le programme utilise 2 secondes de CPU par image, il ne va pas fonctionner en temps réel.


Merci @joshwilson Nous allons utiliser une machine de 16ram. Un moyen possible de rendre le calcul plus efficace serait de calculer l'estimation de mouvement dans le GPU, pas de la CPU, car elle prend moins de 0,2 secondes par calcul. Comme je l'ai dit, je travaille avec des ressources limitées. Mais merci de votre approche, je pensais que l'utilisation d'une piscine de fil serait certainement meilleure que ma mise en œuvre actuelle



0
votes

Vous devez jeter un coup d'œil à Planificateur de tâches Intel TBB . Vous feriez chaque calcul une tâche (une classe dérivée avec exécution fonction) et laissez le planificateur planifiez sur la CPU disponible pour vous.

L'avantage clé des tâches par rapport aux threads logiques est que les tâches sont beaucoup plus légères que les fils logiques. Sur les systèmes Linux, le démarrage et la terminaison d'une tâche sont environ 18 fois plus rapides que de démarrer et de terminer un thread. Sur les systèmes Windows, le rapport est supérieur à 100. En effet, un thread a sa propre copie de nombreuses ressources, telles que l'état du registre et une pile. Sur Linux, un thread a même son propre identifiant de processus. Une tâche dans Intel® threading Building Blocks, en revanche, est généralement une petite routine et ne peut pas être préemptée au niveau de la tâche (bien que son fil logique puisse être préempté).

Le planificateur fait l'équilibrage de la charge. En plus d'utiliser le bon nombre de threads, il est important de distribuer du travail uniformément sur ces threads. Tant que vous cassez votre programme dans suffisamment de petites tâches, le planificateur fait généralement un bon travail d'attribution de tâches aux threads pour équilibrer la charge. Avec une programmation à thread, vous êtes souvent coincé de faire face à vous-même, ce qui peut être difficile à obtenir.

Enfin, le principal avantage d'utiliser des tâches au lieu de threads est qu'ils vous permettent de penser à un niveau supérieur, basé sur la tâche. Avec la programmation à base de thread, vous êtes obligé de penser au niveau bas de threads physiques pour obtenir une bonne efficacité, car vous avez un fil logique par fil physique pour éviter la sous-lavage ou la sursouscription. Vous devez également faire face au grain de fils relativement grossier. Avec des tâches, vous pouvez vous concentrer sur les dépendances logiques entre tâches et laisser la planification efficace au planificateur.

Alternativement, si vous ne pouvez pas utiliser la bibliothèque, vous pouvez implémenter votre propre planificateur de tâches, en fonction de ces idées. Une implémentation simple serait une file d'attente multiple-producteur-consommateur entretenue par un nombre fixe de fils de longue durée dans la piscine (pour une piscine de fil de calcul que vous ne voulez pas plus de threads que le nombre de cœurs de processeur disponibles). Un fil d'inactivité attendrait la file d'attente, prenez une tâche lorsque vous devenez disponible et l'exécuterait.


1 commentaires

Vous voulez plus de threads que de noyaux. En particulier, vous voulez que le nombre soit correct pour que vous puissiez expérimenter avec elle. Même si les threads sont purement liés à la CPU, la n + 1 est standard. S'il y a un bloc d'E / S bloquant, vous voudrez certainement plus de threads que de noyaux.



1
votes

Estimation approximative de la faisabilité

... prend environ 2 secondes par calcul.

... avoir 4 ou 8 threads fonctionnant simultanément ...

... environ 5-6 fps

Bien ces contraintes évidemment ne fonctionnent pas.

huit threads produisant 0,5 images par seconde vous donnent au mieux quatre cadres par seconde.

Si vous avez besoin de 6 images par seconde, vous avez besoin de 12 threads. De plus, ces threads doivent réellement être liés à de vrais noyaux matériels.

Ensuite, vous devez décrire votre plate-forme matérielle. Si ce n'est pas avoir au moins 12 cœurs, vous ne pouvez pas faire ce que vous demandez, du moins dans la façon dont vous suggérez.

S'il a 12 "noyaux" hyperthreading, cela pourrait ne pas être suffisant non plus: un thread peut probablement saturer tout votre alus. Vous n'avez pas dit à quel point vos cadres sont gros, mais la pression L1 pourrait également être un problème.

Si vous n'avez pas beaucoup de noyaux, vous devez soit calculer chaque image plus rapidement, soit un compromis sur des cadres de sortie-par-sec.

Mise en œuvre

Vous avez dit que vous souhaitez estimer le mouvement entre deux cadres successifs. Cela signifie-t-il des les images ou successives les cadres

Le premier cas signifie que vous échantillonnez l'entrée, lisant deux nouveaux images pour chaque sortie, ce qui est plus de données, mais vos threads peuvent procéder en parallèle:

OUT 0 = ME (IN 0 , dans 1 )

OUT 1 = ME (IN 6 , dans 7 )

(ou moi (0,6), moi (6,12), ... ou quelque chose).

Le second cas signifie que vous n'avez besoin que un cadre d'entrée par sortie, mais vous ne pouvez pas démarrer la deuxième image de sortie tant que le premier n'est pas terminé (vous comparez. la première sortie avec le nième cadre d'entrée):

OUT 0 = IN 0

OUT 1 = ME (OUT 0 , dans 6 )

OUT 2 = ME (OUT 1 , dans 12 )

tl; dr Il y a des choses de base dont vous avez besoin pour clarifier avant de pouvoir vraiment commencer à coder quoi que ce soit.


0 commentaires