C++ processing pipeline, threading advies

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

  • TheWickedD
  • Registratie: Juli 2002
  • Laatst online: 02-04-2024
Ik heb twee USB3 machine vision camera's en heb al een stuk software geschreven om deze te besturen en frames direct in videobestanden te dumpen met ffmpeg. Nu wil ik een image processing pipeline toevoegen aan mijn software om bijvoorbeeld wat tracking uit te voeren om de imaging ROI (stuk van sensor wat uitgelezen wordt) real-time mee te bewegen met een bewegend object of interest. Ik wil dit graag zelf schrijven omdat 1) volledige flexibiliteit; 2) beter begrip van wat de code doet; 3) ter lering. Een voorbeeld processing pipeline zou zoiets kunnen zijn:
Afbeeldingslocatie: https://tweakers.net/i/7M95EMlJ0iiwr70u9RN5MnG8t4M=/800x/filters:strip_exif()/f/image/4YMfVxpa3N5Bty5s5tyGSV9n.png?f=fotoalbum_large
Per camera voer ik een zooi bewerkingen uit, gedeeltelijk parallelizeerbaar, en de resultaten van beide camera's neem ik samen en daar voer ik nog wat verdere berekeningen op uit. Het systeem moet real-time zijn waarmee ik bedoel dat het de camera's moet kunnen bijhouden. Als resultaten met een paar frames lag uit het einde van de pipeline komen is niet erg (dus niet real-time in die zin). Toch wil ik graag dat er parallel gedraaid wordt wat er parallel gedraaid kan worden (er zitten zat cores in het systeem waar dit op gaat draaien), zodat het resultaat zo snel mogelijk klaar staat.

Ik schrijf aardig modern C++ vind ik zelf, en heb al wat simpele threading ervaring, maar iets als bovenstaands schrijven is een flinke nieuwe stap. Nu ben ik flink aan het Googlen geweest en heb heel wat gevonden. Ik zou graag advies hebben hoe zo'n systeem te bouwen, welke keuzes te maken. Ik ben drie opties tegengekomen die denk ik interessant zijn:
1) Taskflow
2) Zelf iets rollen met cppcoro, zie e.g. dit
3) Naios continuable

Taskflow maakt het heel makkelijk om een pipeline in elkaar te schroeven, maar laat de resultaten van een task niet voeren aan de volgende task, dus zou nog een hele zooi datastructuren eromheen moeten bouwen.

De andere twee opties maken het wel mogelijk voor de verschillende taken om op een natuurlijke manier met elkaar te communiceren.

coroutines via bijvoorbeeld cppcoro lijkt me een zeer natuurlijke manier van werken (nog niet uitgeprobeerd dus kan tegenvallen) omdat ik eigenlijk bijna geheel gewone code kan schrijven en alleen wat returns hoef te vervangen naar andere statements zodat het geheel lazy wordt en op een threadpool gescheduled kan worden. Het lijkt me wel erg barebones.

Als ik het goed begrijp is Naios continuable een beetje higher level met mooie syntatic sugar om het geheel in elkaar te schroeven. Dus misschien wel de best of both worlds betreffende de andere twee opties. Lijkt niet heel populair though, wat tot zorgen leidt over hoe fully featured het is en hoe goed het gesupport zal blijven.

Nu moet ik kiezen, hoe kies ik tussen deze? Ik weet dat dit een beetje een ongefocusde post is, dat komt omdat ik niet goed weet hoe hierover na te denken. Is er nog een andere techniek/library die ik zou moeten bekijken? Ik wil (heel) goede performance, en vind het niet erg als ik mezelf eerst tien keer in de voet schiet, ik wil tenslotte flink wat leren hier ook. Bedankt voor jullie gedachten!

Beste antwoord (via TheWickedD op 15-05-2020 07:12)


  • BastiaanCM
  • Registratie: Juni 2008
  • Laatst online: 19:05
Geen idee van die frameworks en of het een goede aanpak is, maar het klinkt me vergelijkbaar in de oren als een probleempje dat ik enige tijd geleden had. Wat ik toen gedaan heb is de input data in memory opslaan. Pointers naar elk frame in een queue, en een threadpool waarbij elke thread de queue uitleest en dan verwerkt. Resultaat komt dan weer in memory (en de pointer daarnaartoe) in een andere queue // buffer, die werd denk ik ook verwerkt door de main thread. Daarbij moest ik er ook voor zorgen dat mn output weer op volgorde kwam; door de threadpool kon het zijn dat de data out-of-order in de output zou komen.

Het was relatief eenvoudig, alle threads in de pool moesten exact dezelfde (CPU-based) functies doen en dat was dus redelijk hard-coded.. Maar dat kan ook vrij gemakkelijk dynamischer natuurlijk. Het werkte voor mij goed genoeg, maar vraag me af of er technisch ook veel op aan te merken is. Elke thread heeft toegang tot het geheugen van het proces dus dat hoeft niet gekopieerd. De queue's slaan enkel een pointer op en zijn dus klein. Overhead ivm multi-threaded queue lijkt me ook klein..

Het is gissen zonder details maar veel computer vision taken zijn prima te accelereren op een GPU dmv OpenCL / CUDA, als je dat al niet gebruikt via OpenCV. Dat zou dan al aardig moeten presteren afhankelijk van de taak en je hardware. Die taken door meerdere threads uit laten voeren heeft dan niet zo veel zin denk ik.

Alle reacties


Acties:
  • 0 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Wat is de granulariteit van je parallellisme op een globaal niveau? Is dat per frame? Of kun je per frame meerdere threads gebruiken? Weet je al zeker dat het nu te traag is?

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

  • TheWickedD
  • Registratie: Juli 2002
  • Laatst online: 02-04-2024
Sandor_Clegane schreef op maandag 11 mei 2020 @ 20:05:
Wat is de granulariteit van je parallellisme op een globaal niveau? Is dat per frame?
Sorry, ik begrijp je vraag niet. Elk frame zal door dezelfde pipeline gaan, en in die pipeline zitten er mogelijkheden voor parallelisatie (zie figuur). Daarnaast zal er zeker overlap zijn in het processen van de frames (dus meerdere frames tegelijk door de pipeline). Is dat een antwoord?

Ik heb nog niets gebouwd en weet dus niet of ik per frame extra parallelisatie nodig heb in mijn implementatie, of allen processing voor de frames parallel moet laten verlopen. Maar gezien er twee frames elke 2ms binnen gaan komen (2 cameras op 500 Hz elk, mogelijk een stuk meer later) en processing niet geheel trivial is, verwacht ik dat elk beetje extra snelheid dat parallelisatie kan bieden goed van pas zal komen.

Dank voor het meedenken!

Acties:
  • Beste antwoord
  • 0 Henk 'm!

  • BastiaanCM
  • Registratie: Juni 2008
  • Laatst online: 19:05
Geen idee van die frameworks en of het een goede aanpak is, maar het klinkt me vergelijkbaar in de oren als een probleempje dat ik enige tijd geleden had. Wat ik toen gedaan heb is de input data in memory opslaan. Pointers naar elk frame in een queue, en een threadpool waarbij elke thread de queue uitleest en dan verwerkt. Resultaat komt dan weer in memory (en de pointer daarnaartoe) in een andere queue // buffer, die werd denk ik ook verwerkt door de main thread. Daarbij moest ik er ook voor zorgen dat mn output weer op volgorde kwam; door de threadpool kon het zijn dat de data out-of-order in de output zou komen.

Het was relatief eenvoudig, alle threads in de pool moesten exact dezelfde (CPU-based) functies doen en dat was dus redelijk hard-coded.. Maar dat kan ook vrij gemakkelijk dynamischer natuurlijk. Het werkte voor mij goed genoeg, maar vraag me af of er technisch ook veel op aan te merken is. Elke thread heeft toegang tot het geheugen van het proces dus dat hoeft niet gekopieerd. De queue's slaan enkel een pointer op en zijn dus klein. Overhead ivm multi-threaded queue lijkt me ook klein..

Het is gissen zonder details maar veel computer vision taken zijn prima te accelereren op een GPU dmv OpenCL / CUDA, als je dat al niet gebruikt via OpenCV. Dat zou dan al aardig moeten presteren afhankelijk van de taak en je hardware. Die taken door meerdere threads uit laten voeren heeft dan niet zo veel zin denk ik.

Acties:
  • 0 Henk 'm!

  • TheWickedD
  • Registratie: Juli 2002
  • Laatst online: 02-04-2024
@BastiaanCM, bedankt voor het meedenken en delen van jouw ervaring. Het doet me denken aan dat ik nu bezig ben met premature optimization, en misschien nog wel in niet de beste richting ook. Laat me eerst het maar eens simpel bouwen, kijken of het snel genoeg is, en zo niet kijken wat ik kan versnellen. Misschien is GPU inzetten daar wel een stuk beter idee dan kleine stukjes naar verdere threads uitzaaien--zou wel logisch zijn als dat zo is.

Ik ben inderdaad ook van plan om met pointers naar data (e.g. video frames) te werken om veel onnodig gekopieer te voorkomen--zo werkt het nu al intern voor wat ik heb. Het probleem van out-of-order output weer op volgorde zetten zie ik ook aankomen. Kun je globaal schetsen hoe jij dat hebt opgelost?

Acties:
  • +1 Henk 'm!

  • Bob
  • Registratie: Mei 2005
  • Laatst online: 23:59

Bob

Maak het niet moeilijker dan nodig. Met een simpele threadpool (+1 voor hierboven dus) heb ik vroeger in beeldcompressie met meerdere stappen bijna perfecte speedup gehaald. Focus zoals je al zelf voorstelt op goede data layout, vermijd onnodig kopiëren en zorg dat je goed inzicht hebt in je algoritmes.

Al dat erna nog eens in een fancy pipeline framework gieten is imo meer om het op je cv te kunnen zetten dan dat het praktisch nut zal hebben ;)

Acties:
  • +1 Henk 'm!

  • gekkie
  • Registratie: April 2000
  • Laatst online: 22:08
Geen idee of je bewerkingen al mogelijk zijn met de nieuwe G-API, beetje het zicht verloren op de laatste ontwikkelingen van openCV.
Het idee daarvan was om een pipeline te kunnen beschrijven van bewerkingen die dan automagisch zo veel mogelijk geoptimaliseerd en zero-copy is, waar je vervolgens je data door heen kunt streamen.

Acties:
  • +1 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
TheWickedD schreef op donderdag 14 mei 2020 @ 23:37:
Het doet me denken aan dat ik nu bezig ben met premature optimization, en misschien nog wel in niet de beste richting ook. Laat me eerst het maar eens simpel bouwen, kijken of het snel genoeg is, en zo niet kijken wat ik kan versnellen.
Dat denk ik ook. Wat je meestal ziet in dit soort gevallen is dat de data verwerkt wordt door dingen als pytorch/tensorflow/opencv terwijl de logica gewoon in python draait. Al zoek je op motion detection code (of object detection code) vind je vast genoeg voorbeelden. Dus de problemen die hier worden geschetst vallen in de praktijk wel mee, als de processing niet snel genoeg is drop je gewoon wat frames en die frameworks gebruiken de capaciteit die er is (aan cpu/gpu).

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • TheWickedD
  • Registratie: Juli 2002
  • Laatst online: 02-04-2024
Bedankt voor de antwoorden en het meedenken. Ik heb er nu zeker genoeg vertrouwen in dat simpel wel goed genoeg zal zijn. Dus simpel houden is een goed plan! Ik zal gapi van opencv goed bekijken, cool.

Betreffende out-of-order data output en dat weer op volgorde zetten, hoe pak ik dat aan? Ik zie twee problemen:
  1. Zorgen dat output op de goede volgorde in een vector (ofdeque ofzo) terecht komt
  2. Een pointer bijhouden tot waar in de vector er geen gaten inzitten (een gat is een frame waarvoor de output nog niet beschikbaar is terwijl output van een later frame al wel beschikbaar is)
Eerste probleem is makkelijk opgelost omdat frames een volgnummer hebben: ik kan dus zo berekenen waar in de output vector ze terecht komen en dat ook preallocaten als een frame binnenkomt. Maar hoe pak je probleem twee efficiënt aan? Elke keer als je een output in de vector schrijft kijken hoe ver je naar voren kunt lopen tot er geen output meer te vinden is in de vector en dan de pointer naar die laatste die klaar is te laten verwijzen? Het lijkt me dat het een veelvoorkomend probleem is, en ben benieuwd hoe dat professioneel opgelost wordt. Waar google ik op/hoe hebben jullie dit opgelost?

Acties:
  • +1 Henk 'm!

  • BastiaanCM
  • Registratie: Juni 2008
  • Laatst online: 19:05
Ik heb even die code bekeken maar zo te zien heb ik de output niet op volgorde gezet. Dat hoefte in mijn geval ook niet: alle output ging naar een file en die kon later gesorteerd als dat file een keer werd ingelezen, de data van elk frame had inderdaad gewoon het frame-nummer. Als ik het wel zou implementeren zou ik het misschien als volgt hebben gedaan, let wel, dan denk ik wel vooral vanuit mijn situatie:

- Elke thread schrijft zijn output naar een buffer. Elke keer als er een nieuwe output die buffer in gaat, kijk je naar het eerstvolgende frame-nummer dat wordt verwacht. Als dat niet gelijk is, laat je het in de buffer. Als het wel gelijk is dan mag dat natuurlijk naar de output, en moet je ook de gehele buffer checken of het volgende frame er ook al in staat. Ja; herhaal dit zolang de buffer niet leeg is.

Waar je dan natuurlijk wel rekening mee moet houden is dat de buffer niet vol kan komen zodanig dat het oudste frame er niet meer in kan.. Dat kan slordig met kans op deadlock (buffer die diep genoeg is) of je zorgt er voor dat threads geen nieuw frame uit de input-queue halen als de output-buffer daar door vol kan komen..

Een andere, simpelere oplossing kan ook zijn om elk thread pas zijn data te laten outputten als zij het volgende framenummer hebben. Dat kan ook wel goed werken afhankelijk van hoe variabel de processing-tijd is tussen de frames.

Even tussendoor ik zou mezelf nog geen pro noemen hoor.

Acties:
  • 0 Henk 'm!

  • TheWickedD
  • Registratie: Juli 2002
  • Laatst online: 02-04-2024
@BastiaanCM thanks, dat klinken als twee goeie strategien, ik ga maar eens aan de slag.
Pagina: 1