[Alg] WinSock vs BSD sockets

Pagina: 1
Acties:

  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
Op verzoek van Qlone ([rml]Qlone in "[ C++] Server met Clients efficient?"[/rml]) open ik een nieuw topic voor deze discussie.
Verwijderd schreef op 24 november 2003 @ 00:40:
Onder windows zijn threads zo handig in het gebruik dat het bijna zonde is om geen gebruik te maken van multithreading :) . Onder unix is threading er later min of meer bij bedacht en blijft de gangbare aanpak toch een beetje zoveel mogelijk single threaded werken of gebruik maken van worker processes (met het geniale fork()) en wat pipes of sockets voor de communicatie tussen deze processen.
Bijna. De nadelen van threads zijn IMO:
Complexere en tragere code door locking.
Complexere debugging, non-deterministic gedrag.
Performance-verlies door thread-switching.
Meer memory nodig voor (thread) stacks.
Select kan wachten op een file descriptor (file, socket, pipe, dat soort spul). Wil je wachten op iets anders dan een filedescriptor (timer, bericht van een andere thread) dan laat select je redelijk in de steek. Je kan dan andere manieren gebruiken om de aandacht van je proces te trekken (SIGALRM, SIGIO) maar dat vind ik zelf minder mooi dan de windows aanpak, waar je met WaitForMultipleEvent kan wachten op (max 64) 'signalable handles'. Alle file handles (gemaakt met CreateFile(), dus dingen als files, devices, named pipes) vallen hier onder, maar ook Events (een windows iets), Thread handles (handle is 'signalled' als thread afsluit... onder unix gebruik je daarvoor als ik me goed herrinner een van de 'wait()' calls), en dan vergeet ik er vast nog wel een paar...
Zeker waar. Maar zijn die zaken ook nodig in servers?
Dat hangt natuurlijk af van het type server, maar voor bijvoorbeeld een FTP server zijn timers IMO niet nodig. Als de server single-threaded is, is ITC ook niet mogelijk.
Ook een leuke in windows (al heb ik het nooit gebruikt) is dat winsock messages kan sturen naar de GUI van een applicatie (via WSAAsyncSelect). Zo kan een GUI app non-blocking werken en toch handig socket dingen doen zonder polling of extra threads...
Voor servers is dat geen issue, voor clients wel. Maar voor clients is de performance weer niet zo'n issue.
offtopic:
Dit is wel een beetje offtopic aan het lopen. Ik ben best te vinden voor een discussie over de voors en tegens van winsock vs BSD sockets, maar mischien handig om dat in een ander topic te doen.

[ Voor 3% gewijzigd door Olaf van der Spek op 25-11-2003 15:55 ]


Verwijderd

Even snel een reply tussendoor, om het topic wat op gang te helpen :) .
Bijna. De nadelen van threads zijn IMO:
Complexere en tragere code door locking.
Complexere debugging, non-deterministic gedrag.
Performance-verlies door thread-switching.
Meer memory nodig voor (thread) stacks.
Code hoeft niet noodzakelijk trager te worden door locking. Het argument opzich is geldig, maar alleen voor die stukken data die door meerdere threads tegelijk benaderd worden. Bij een juiste toepassing van locks zal het echter nooit substantieel trager zijn dan een single threaded oplossing. Als je proces draait op een machine met meerdere processoren of een processor hyperthreading mogelijkheden is een fatsoenlijke multi threaded implementatie vrij makkelijk sneller. Wel is het zo dat behalve de eigen data ook de geheugen heap van het proces gedeeld wordt door meerdere threads en daar treedt wel een klein performance verlies op door allerhande locks (waar de singlethreaded implementatie bij microsoft's bibliotheken die code niet eens bevat, en dus per definitie sneller is).

Complexiteit van multithreaded software is niet noodzakelijk hoger. Natuurlijk zijn er moeilijke stukken aan te wijzen, vooral bij gedeelde data en er is de mogelijkheid tot race condities. Een goed programmaontwerp helpt wel dit soort problemen te vermijden. (En ja, ik weet uit ervaring dat dat nog wel eens fout gaat en race conditions opsporen is *erg* moeilijk...). Tegenover deze problemen staat wel weer dat je precies aan kunt geven wat een thread doet. 1 thread doet meestal 1 soort ding, duidelijk afgebakend, en is daar mogelijk duidelijker dan een applicatie die tientallen dingen tegelijk doet waarbij om performance redenen code daar de neiging heeft ook moeilijk te volgen te zijn.

Performance-verlies door thread-switching. Vergeleken met een singlethreaded applicatie gaat dat op ja. Vergeleken met een unix-style multiprocess applicatie niet. Er zit wel een break-even point ergens in het single vs multithreaded traject denk ik, namelijk daar waar bij een singlethreaded applicatie het bijhouden van state en andere zaken zwaarder gaat wegen dan de overhead die een extra thread kost.

Meer memory nodig voor (thread) stacks. Kan ik niks tegenin brengen, dat is gewoon zo. Wel kun je bij het aanmaken van een thread natuurlijk kiezen je stack klein te houden om zo geheugen te besparen.
Zeker waar. Maar zijn die zaken ook nodig in servers?
Dat hangt natuurlijk af van het type server, maar voor bijvoorbeeld een FTP server zijn timers IMO niet nodig.
Dat hangt een beetje af van je applicatieontwerp. Ik heb ooit een thread pool geimplementeerd die asynchroon threads die verouderd zijn opruimt als de load op een server minder wordt. De opruim-'task' draait dan in een aparte thread en houdt per thread een timer bij voor zijn leeftijd. Als de maximale leeftijd overschreden wordt wordt voor die thread zijn stop-event gesignalled en de thread uit sleep state gehaald, waarna die zichzelf afsluit. Ook voor netwerksessies die te lang ongebruikt zijn is een dergelijke aanpak mogelijk, en is het bij een dusdanig ontwerp handig dat je op meer dan alleen socket activiteit kan wachten. Bij een single-threaded ontwerp gaat dat inderdaad niet helemaal op, maar:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout);

NOTES
The default size of FD_SETSIZE is currently 1024. In order to accommo-
date programs which might potentially use a larger number of open files
with select(), it is possible to increase this size by having the program
define FD_SETSIZE before the inclusion of any header which includes
<sys/types.h>.
Het is vast niet handig om vooraf te gaan bepalen wat het maximum aantal open sockets is in een stuk serversoftware. In windows heb je een soortgelijk probleem, WaitForMultipleObjects kan op max 64 handles wachten, en voor select geldt vrijwel hetzelfde verhaal als onder unix... Een makkelijke manier om hier omheen te werken is door bijv voor elke 64 client verbindingen een nieuwe thread op te starten. Levert extra performance op op een multiprocessor machine, maar betekent wel een multithreaded of multiprocess aanpak, met alle voor en nadelen daarvan als gevolg.

  • Olaf van der Spek
  • Registratie: September 2000
  • Niet online
Verwijderd schreef op 25 november 2003 @ 16:35:
Code hoeft niet noodzakelijk trager te worden door locking. Het argument opzich is geldig, maar alleen voor die stukken data die door meerdere threads tegelijk benaderd worden. Bij een juiste toepassing van locks zal het echter nooit substantieel trager zijn dan een single threaded oplossing. Als je proces draait op een machine met meerdere processoren of een processor hyperthreading mogelijkheden is een fatsoenlijke multi threaded implementatie vrij makkelijk sneller. Wel is het zo dat behalve de eigen data ook de geheugen heap van het proces gedeeld wordt door meerdere threads en daar treedt wel een klein performance verlies op door allerhande locks (waar de singlethreaded implementatie bij microsoft's bibliotheken die code niet eens bevat, en dus per definitie sneller is).
Elke keer dat je een locked lock tegenkomt heb je minimaal twee extra thread switches nodig, hoeveel vertraging dat precies oplevert weet ik niet, maar positief is het natuurlijk niet.
Zelfs als jouw app single-threaded is, zijn er nog andere threads op het systeem. IRQs moeten afgehandeld worden en de netwerk stack kost volgens mij ook redelijk wat CPU tijd. Bij een HT CPU is het dus niet zeker dat je voordeel hebt. Bij MP waarschijnlijk wel, mits je app CPU bound is natuurlijk.
Complexiteit van multithreaded software is niet noodzakelijk hoger. Natuurlijk zijn er moeilijke stukken aan te wijzen, vooral bij gedeelde data en er is de mogelijkheid tot race condities. Een goed programmaontwerp helpt wel dit soort problemen te vermijden. (En ja, ik weet uit ervaring dat dat nog wel eens fout gaat en race conditions opsporen is *erg* moeilijk...). Tegenover deze problemen staat wel weer dat je precies aan kunt geven wat een thread doet. 1 thread doet meestal 1 soort ding, duidelijk afgebakend, en is daar mogelijk duidelijker dan een applicatie die tientallen dingen tegelijk doet waarbij om performance redenen code daar de neiging heeft ook moeilijk te volgen te zijn.
Ik heb een IRC-like server geschreven en daar heb je 'erg' veel shared data. Bij andere servers kan het inderdaad minder zijn. In een enkele thread kun je natuurlijk nog steeds verschillende functies gebruiken voor verschillende taken, dus ik denk niet dat dat echt een issue is.
Performance-verlies door thread-switching. Vergeleken met een singlethreaded applicatie gaat dat op ja. Vergeleken met een unix-style multiprocess applicatie niet. Er zit wel een break-even point ergens in het single vs multithreaded traject denk ik, namelijk daar waar bij een singlethreaded applicatie het bijhouden van state en andere zaken zwaarder gaat wegen dan de overhead die een extra thread kost.
Een single-threaded app schrijven en dan meerdere processen gebruiken is inderdaad niet slim, maar ik bedoelde echt single-process, single-thread.
Het is vast niet handig om vooraf te gaan bepalen wat het maximum aantal open sockets is in een stuk serversoftware. In windows heb je een soortgelijk probleem, WaitForMultipleObjects kan op max 64 handles wachten, en voor select geldt vrijwel hetzelfde verhaal als onder unix... Een makkelijke manier om hier omheen te werken is door bijv voor elke 64 client verbindingen een nieuwe thread op te starten. Levert extra performance op op een multiprocessor machine, maar betekent wel een multithreaded of multiprocess aanpak, met alle voor en nadelen daarvan als gevolg.
Een extra FD in een FD set kost maximaal 24 bytes (3 sets, 8 bytes/FD).
16k FDs reserveren kost slechts 384 kb, dus dat mag geen issue zijn.

Het is echter wel een zwak punt van select, maar in plaats van select kun je in Linux ook poll gebruiken zonder de rest van de code aan te passen.