Inter process semaphores in shared memory

Door stereohead op zondag 22 september 2013 16:11 - Reacties (7)
Categorie: -, Views: 2.137

Intro


Voor een projectje wat misschien ooit gaat plaatsvinden heb ik behoefte aan snelle (low latency) en snelle (high bandwidth) communicatie tussen processen. Hier heeft men een naam voor bedacht, namelijk: Inter Process Communication oftewel IPC.

Goed. Ik wil dus grote (eerder MB's dan KB's) blokken data transporteren tussen twee processen. Probleem: relatief grote blokken data kopieren kost relatief veel tijd. Maar wat als we de date niet hoeven te kopieren...?

Enter shared memory.

Shared memory is een IPC techniek waarbij twee (of meer) processen het zelfde geheugenblok in hun address ruimte hebben staan. Wanneer het ene proces dan naar dat geheugenblok schrijft zal dat vervolgens door een ander proces uit dat gehugenblok gelezen kunnen worden.

Probleem: hoe weet ik dat het ene proces klaar is met schrijven, zodat het andere proces veilig uit het gedeelde gehugen kan lezen? Of: Hoe weet ik dat het andere proces iets nieuws in het gedeelde geheugen heeft gezet?

Enter semaphores.

Met semaphores kan men er voor zorgen dat maar een proces tegelijk een bepaalde resource gebruikt. De resource is in ons geval dus het gedeelde geheugenblok. Wanneer het ene proces iets wil lezen uit het gedeelde geheugen zal het sem_wait aanroepen, welke net zolang blockt totdat een ander proces sem_post heeft aangeroepen.

Nu heeft men op linux beschikking over twee soorten semaphoren, namelijk de named semaphores, en de unnamed semaphores*.

Het verschil:

Named semaphores geeft men een naam, waarna deze door elk ander proces** op het system te zien en te gebruiken is. Het aantal named semaphores is gelimiteerd omdat het voor elke semaphore een bestand opend, en het aantal open bestanden per proces door de kernel gelimiteerd word (standaard 1024, is wel aan te passen, maar toch).

Unnamed semaphores hebben geen naam, en zijn alleen te gebruiken binnen threads of processen die een gedeeld stuk geheugen hebben. (Hey toevallig, dat hebben we).

Het liefst gebruik ik unnamed semaphores, omdat ik me dan geen zorgen hoef te maken over het verwijderen van de semphores bij het afsluiten (of crashen) van het programma, en omdat ik op die manier vrijwel geen limiet op het aantal semaphores heb.


Named vs unnamed semaphores


Maargoed, in het begin zei ik dat low latency een eis was, tijd dus om te kijken hoelang het wachten op een semaphore eigenlijk duurt. Hierbij wil ik kijken wat het verschil is tussen named en unnamed semaphores, en kijken welke vorm van shared memory het snelste is.

De eerste kandidaad: de named semahore!

Hiervoor heb ik twee programmatjes geschreven, de ene maakt de semaphore, gaat wachten tot de semaphore 'gepost' is, en print vervolgens de huidige tijd. Het tweede programmatje opent de gemaakte semaphore, print de huidige tijd en doet een 'post' op de semaphore. Het eerst programma wordt opgestart deze gaat vervolgens wachten op de semaphore. Nu wordt het tweede programma opgestart, deze print de tijd en 'post' de semaphore, waardoor het eerste programma niet meer hoeft te wachten, vervolgens de tijd print en afsluit. De waarden die hier uitkomen heb ik genoteerd en zo komen we tot de volgende uitkomst:

0.0700 ms.

De tweede kandidaad: de unnamed semaphore!

Weer twee programmatjes, nu wordt er een stuk geheugen gedeeld via mmap, waarbij de mapping wordt gedaan op een regulier bestand***. De semaphores worden geinitializeerd in deze mapping, en vervolgens de programmatjes gedraait. Het resultaat:

35.1566 ms.

Oef een flinke tegenvaller, semaphores via shared memory zijn dus een stuk trager.
Maar wacht, ik deed een mmap op een regulier bestand, wat als ik een mmap doe op een gedeeld geheugen verkregen door shm_open.

Kandidaad drie, de unnamed semaphore in een mmap op shared memory verkregen via shm_open!

Het resultaat:

0.0681 ms.

Yes, dat lijkt er meer op, maar waarom is dit zoveel sneller? Even verdiepen in de manpages leert mij dat shared memory via shm_open terecht komt in de map /dev/shm, 'toevallig' is deze map gemount op een tmpfs bestandssysteem, zou het daarom sneller zijn dan mijn vorige poging?

Kandidaad vier, die unnamed semaphore in een shared memory op een regulier bestand op een tmpfs bestandsysteem.

Ik heb mijn /tmp/ folder gemount tmpfs. Voor de vierde kandidaad heb ik dus een bestand aangemaakt in mijn /tmp/ folder, de programmatjes aangepast zodat de mmap gebeurt op dit bestand en hoppa.

Het resultaat:

0.0713 ms.


Conclusie


Wat hebben we geleerd:

Named semaphores zijn niet trager dan unnamed semaphores via shared memory, afhankelijk van waar dit shared memory op gebasseerd is. Ik kwam er later trouwens achter dat named semaphores ook een entry krijgen in /dev/shm en dus waarschijnlijk ook op de achtergrond een shared memory hebben op een bestand op een tmpfs.

Eigenlijk verbaasd mij dit wel, ik had verwacht dat de kernel de pages van het shared memory gewoon in het werkgeheugen heeft staan en het onderliggende bestandssysteem dus eigenlijk helemaal geen invloed zou moeten hebben.

Nog een leuk detail, tijdens het schrijven van deze post kwam ik er achter dat ik bij de berekende tijd ook de tijd opnam van het 'printf' gedeelte. Gelukkig maakte dit niet erg veel verschil.




* Niet helemaal waar, er zijn ook nog de oude System V semaphores, maar dat is geen POSIX standaard, en het wordt niet meer aanbevolen om ze te gebruiken.

** Wanneer het daar de rechten toe heeft.

*** Een normaal, leesbaar bestand op een ext4 bestandssysteem.

Source:
https://gist.github.com/stereohead/6662398
https://gist.github.com/stereohead/6662424

Volgende: RE: Klereherrie 03-'10 RE: Klereherrie

Reacties


Door Tweakers user Rowdy.nl, maandag 23 september 2013 08:44

Zeer interessante blogpost! :)

Door Tweakers user Skamiplan, maandag 23 september 2013 09:26

Dat begint lekker op de maandagochtend :)

Door Tweakers user RoadRunner84, maandag 23 september 2013 09:31

Dus: semaphores via shared memory kosten ca. 70 us, semaphores via hardeschijf kosten 35 ms. Ergens is het niet zo heel verassend, toch?

Door Tweakers user sebastius, maandag 23 september 2013 12:12

RoadRunner84 schreef op maandag 23 september 2013 @ 09:31:
Dus: semaphores via shared memory kosten ca. 70 us, semaphores via hardeschijf kosten 35 ms. Ergens is het niet zo heel verassend, toch?
Redelijk in lijn met toegangstijden van beide opslagsystemen toch?

Door Tweakers user H!GHGuY, maandag 23 september 2013 12:19

Als je nog sneller wil gaan, kun je beter gewoon een circular buffer + memory barriers gebruiken in je shared memory:

read kant:


code:
1
2
3
4
memory_barrier()
ptr = read_ptr();
if pointer_advanced(ptr)
__read_data()


write kant:

code:
1
2
3
4
write data
memory_barrier()
write_ptr(ptr)
memory_barrier() // hoeft niet noodzakelijk maar kan latency nog iets verlagen

Door Tweakers user stereohead, maandag 23 september 2013 17:16

RoadRunner84 schreef op maandag 23 september 2013 @ 09:31:
Dus: semaphores via shared memory kosten ca. 70 us, semaphores via hardeschijf kosten 35 ms. Ergens is het niet zo heel verassend, toch?
Nee. Behalve dan dat ik niet verwacht had dat shared memory op een file ook echt via de hdd zou gaan.
H!GHGuY schreef op maandag 23 september 2013 @ 12:19:
Als je nog sneller wil gaan, kun je beter gewoon een circular buffer + memory barriers gebruiken in je shared memory:
Hee, dat is nieuw voor mij! Ik zal me er eens in verdiepen.

Door Tweakers user analog_, woensdag 25 september 2013 17:03

Hoeveel data is je post want bereken je bandbreedte eens in MB/s. Run een benchmark over verschillende data sizes om te zien hoe je 'iops' schalen. Doe elke type operatie statisch vaak genoeg (10 minuten lang?) en genereer een cumulatieve frequentie grafiek van je bewerkingstijden.

[Reactie gewijzigd op woensdag 25 september 2013 17:06]


Reageren is niet meer mogelijk