Ik ben bezig met het maken van een eigen webserver. Daarbij heb ik het volgende probeem: ik test de snelheid van mijn server met de tool 'ab' die met Apache wordt meegeleverd. Als ik deze test met keep-alive connecties is de snelheid van mijn webserver bedroevend slecht. 'Beter coden' zou je zeggen, maar het gekke is dat de load van de computer waar ik op test (draait zowel ab als de webserver) bijna 0 is. Wat blijkt: select() duurt relatief lang voordat hij terugkeert met de melding 'er is nieuwe data'. Een setsockopt() met TCP_NODELAY scheelt al een boel, maar nog steeds is 'keep-alive' trager dan 'close' connections. Bij close connections is de load hoog (compu is duidelijk aan het stressen), met keep-alive is ie uit z'n neus aan het vreten. Hoe krijg ik die @#!* sockets zover dat ze gewoon opschieten ipv wachten met sturen/ontvangen van data?
Geen select() probleem, maar een code-probleem denk ik. select() is net zoals de meeste dingen gebonden aan de interrupts frequentie, en die is 100 Hz, maar onder normale omstandigheden heb je daar geen last van. Zodra je een behoorlijk aantal connecties (lees : een paar honderd) moet afhandelen is select() beduidend trager als alternatieven zoals poll(). Verder doen veel threads de performance ook geen goed, evenals veel locking en disk-io.
Mijn code bevat geen sleeps tijdens de afhandeling van een request. Hoe moet ik dan verklaren dat de load op 0 staat??
Ik heb op verschillende plekken in mijn code timers geplaatst om de snelheid te meten. De traagheid zit em echt in de select().
Ik heb op verschillende plekken in mijn code timers geplaatst om de snelheid te meten. De traagheid zit em echt in de select().
Heb je de timeout bij select te groot staan misschien ?
Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.
Wat is traag overigens, en hoe meet je ? Ik heb behoorlijk wat daemons met select() in elkaar gezet, en jouw problemen zijn mij onbekend. De meeste / alle problemen met select / poll zijn te wijten aan problemen met de code.Verwijderd schreef op 07 september 2004 @ 22:58:
Ik heb op verschillende plekken in mijn code timers geplaatst om de snelheid te meten. De traagheid zit em echt in de select().
Mocht je een kernel > 2.4.20 < 2.4.27 gebruiken op een SMP machine : Dan loop je mogelijk tegen een kernel bug aan.
Verwijderd
Keep-Alive is gewoon een dure TCP optie als je een server met hoge troughput en veel cients wilt schijven (volgens deze page 15% overhead bij 51 clients). Als je dus een test-suite gebruikt die honderden of zelfs duizenden clients simuleert is je probleem verklaarbaar.
(Daarnaast is het Nagle-algoritme uitzetten (met TCP_NODELAY) ook niet altijd bevorderlijk voor de troughput van een server met een groot aantal clients.)
(Daarnaast is het Nagle-algoritme uitzetten (met TCP_NODELAY) ook niet altijd bevorderlijk voor de troughput van een server met een groot aantal clients.)
@farlane: timeout staat op 1 sec. Is dat te groot / klein?
@igmar: wat is traag: ongeveer 10x langzamer dan zonder keep-alive. En mijn code ligt het niet: wat in een keep-alive gebeurd is dat buffers ge-free()-ed worden en wat variabelen op default worden gezet. Dat is alles. Daardoor kan ie niet 10x langzamer worden. Hoe ik meet: met timer-checks in de code die tijdverschillen printf()-en: de select() is duidelijk de bottleneck. In een keep-alive is de eerste request (soms de eerste twee) snel, daarna wordt ie trager. Ik gebruik geen SMP.
@mietje: we hebben het hier over HTTP-keepalive, niet over TCP-keepalive. En ik gebruik TCP_NODELAY alleen bij minder dan een bepaald aantal connecties.
@igmar: wat is traag: ongeveer 10x langzamer dan zonder keep-alive. En mijn code ligt het niet: wat in een keep-alive gebeurd is dat buffers ge-free()-ed worden en wat variabelen op default worden gezet. Dat is alles. Daardoor kan ie niet 10x langzamer worden. Hoe ik meet: met timer-checks in de code die tijdverschillen printf()-en: de select() is duidelijk de bottleneck. In een keep-alive is de eerste request (soms de eerste twee) snel, daarna wordt ie trager. Ik gebruik geen SMP.
@mietje: we hebben het hier over HTTP-keepalive, niet over TCP-keepalive. En ik gebruik TCP_NODELAY alleen bij minder dan een bepaald aantal connecties.
[ Voor 6% gewijzigd door Verwijderd op 09-09-2004 20:45 ]
Kwestie van uitproberen of het uberhaupt invloed heeft. Ik kan me voorstellen dat veel 'korte' waits een ander gedrag geven dan minder lange waits.Verwijderd schreef op 09 september 2004 @ 20:21:
@farlane: timeout staat op 1 sec. Is dat te groot / klein?
Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.
Offtopic: kwam er net via logfiles achter dat mijn webserver nu gebruikt wordt door een of ander IT bedrijfje
Ben ik even apetrots
/Offtopic.
/Offtopic.
Verwijderd
Mja, dat nam ik eerst ook aan, maar dan is het wel tamelijk vreemd dat select() trager reageert, want volgens mij is HTTP Keep-Alive niet geïmplementeerd in de kernel maar in je webserver. Dat zou dus al bijna moeten betekenen dat ab trager reageert met keep-alive (cq. dat je server iets doet waardoor ab "van streek raakt" en trager wordt).Verwijderd schreef op 09 september 2004 @ 20:21:
@mietje: we hebben het hier over HTTP-keepalive, niet over TCP-keepalive. En ik gebruik TCP_NODELAY alleen bij minder dan een bepaald aantal connecties.
TCP keep-alive heeft totaal geen invloed op select().Verwijderd schreef op 09 september 2004 @ 20:21:
@farlane: timeout staat op 1 sec. Is dat te groot / klein?
@igmar: wat is traag: ongeveer 10x langzamer dan zonder keep-alive.
Mjah.. Ik zie nog steeds niks concreets, hebben we het over seconden, milliseconden ? Ik heb een squid server die soms 120 concurrent connecties afhandeld, en dat gebeurd ook met select().En mijn code ligt het niet: wat in een keep-alive gebeurd is dat buffers ge-free()-ed worden en wat variabelen op default worden gezet. Dat is alles. Daardoor kan ie niet 10x langzamer worden. Hoe ik meet: met timer-checks in de code die tijdverschillen printf()-en: de select() is duidelijk de bottleneck. In een keep-alive is de eerste request (soms de eerste twee) snel, daarna wordt ie trager. Ik gebruik geen SMP.
[/quote]@mietje: we hebben het hier over HTTP-keepalive, niet over TCP-keepalive. En ik gebruik TCP_NODELAY alleen bij minder dan een bepaald aantal connecties.
Wat heeft dat met select() te maken ? Ik vrees dat je het probleem toch in je eigen code moet zoeken.
Gebruik eens poll in plaats van select. Juist ook voor schaalbaarheidsproblemen.
Ik denk dus niet dat het met poll vs select te maken heeft.Example timings (Pentium 100):
1021 file descriptors (3-1023), check for activity on 1014-1023
Using a select(2) loop like this:
memcpy (&i_fds, &input_fds, sizeof i_fds);
memcpy (&o_fds, &output_fds, sizeof i_fds);
memcpy (&e_fds, &exception_fds, sizeof i_fds);
nready = select (max_fd + 1, &i_fds, &o_fds, &e_fds, &tv);
takes 362 microseconds.
Using a poll(2) loop like this:
nready = poll (pollfd_array + start_index, num_to_poll, 0);
takes 93 microseconds.
Now, if we change the test to do checks on descriptors 924-1023:
select(2) takes 1274 microseconds
poll(2) takes 1023 microseconds.
And now if we check descriptors 24-1023:
select(2) takes 3897 microseconds
poll(2) takes 4221 microseconds.
So we can see that poll(2) has a distinct advantage with modest
numbers of high-valued fds. It starts to loose when the number of fds
is increased, since it has to push more bits around than select(2).
The point of my poll2(2) proposal is that less bits are pushed, both
in kernel space as well as user space, compared with poll(2). poll(2)
maintains the advantage of poll(2) when watching modest numbers of
high-valued fds, but also reduces the cost when watching large numbers
of fds with only a few returning active status.
Ik heb dat ook nooit beweerd. Ik had het over de timeout van select.igmar schreef op 10 september 2004 @ 09:46:
TCP keep-alive heeft totaal geen invloed op select().
Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.
Is de snelheid van select() niet afhankelijk van de prioriteit waarmee je programma draait? Want volgens mij kost de select() je in ieder geval één task-switch. De delay is dan de tijd tussen dat je proces pre-empted wordt en weer de processor toegewezen krijgt. Net als WaitForSingleObject() en Sleep. Als jouw proces dus op een hogere prioriteit loopt, zal hij sneller weer de processor toegewezen krijgen (zou je zeggen).
Een Sleep(0) duurt geen 0 seconden. Maar het zorgt er wel voor dat je task released wordt en de scheduler eerst alle andere tasks hun tijd gaat geven voordat hij weer terug komt bij jou proces. Een Sleep(0) duurt altijd langer dan 0 milliseconden.
Overigens: als je echt snel socket connecties wil afhandelen moet je eens kijken naar I/O Completion Ports (check bijvoorbeeld GetQueuedCompletionStatus).
Een Sleep(0) duurt geen 0 seconden. Maar het zorgt er wel voor dat je task released wordt en de scheduler eerst alle andere tasks hun tijd gaat geven voordat hij weer terug komt bij jou proces. Een Sleep(0) duurt altijd langer dan 0 milliseconden.
Overigens: als je echt snel socket connecties wil afhandelen moet je eens kijken naar I/O Completion Ports (check bijvoorbeeld GetQueuedCompletionStatus).
[ Voor 8% gewijzigd door RetepV op 10-09-2004 14:38 ]
Macbook Pro
Verwijderd
Dat verklaart niet waarom zijn server 10x zo traag wordt bij het zetten van een bep. optie die in principe weinig rekencapaciteit en bandbreedte kost.RetepV schreef op 10 september 2004 @ 14:37:
Is de snelheid van select() niet afhankelijk van de prioriteit waarmee je programma draait? Want volgens mij kost de select() je in ieder geval één task-switch. De delay is dan de tijd tussen dat je proces pre-empted wordt en weer de processor toegewezen krijgt. Net als WaitForSingleObject() en Sleep. Als jouw proces dus op een hogere prioriteit loopt, zal hij sneller weer de processor toegewezen krijgen (zou je zeggen).
Er zijn compilers die een sleep(0) "weg optimaliseren" tot een NOP, als je wilt dat een proces taskswitched (zonder te blocken) dan gebruik je sched_yield() (onder POSIX dan, maar ik neem aan dat dit niet over Windows gaat).Een Sleep(0) duurt geen 0 seconden. Maar het zorgt er wel voor dat je task released wordt en de scheduler eerst alle andere tasks hun tijd gaat geven voordat hij weer terug komt bij jou proces. Een Sleep(0) duurt altijd langer dan 0 milliseconden.
ivm dit probleem heeft men epoll() uitgevonden. Daar krijg je gewoon een set met alles wat aan de criteria voldoet, dus je hoeft niet meer fd's te testen. Het vereist helaas een patch voor 2.4, maar zit wel standaard in 2.6.Zoijar schreef op 10 september 2004 @ 10:14:
Ik denk dus niet dat het met poll vs select te maken heeft.
bugs ? select() is in linux vanaf 2.4 volledig gelijk aan poll(). Verder is mij onduidelijk wat een 'bepaalde optie is', aangezien men het zowel over TCP, HTTP als select opties heeft inmiddels.Verwijderd schreef op 10 september 2004 @ 16:35:
Dat verklaart niet waarom zijn server 10x zo traag wordt bij het zetten van een bep. optie die in principe weinig rekencapaciteit en bandbreedte kost.
Verwijderd
Lees het ff in de context igmar, ik ben het namelijk vrijwel geheel met je eens; de TS geeft aan dat het over HTTP Keep-Alive gaat, dus de enige (voor mij bendenkbare) manier waarop select() trager kan worden is als de client aan de de andere kant van de connection trager wordt.igmar schreef op 11 september 2004 @ 13:34:
bugs ? select() is in linux vanaf 2.4 volledig gelijk aan poll(). Verder is mij onduidelijk wat een 'bepaalde optie is', aangezien men het zowel over TCP, HTTP als select opties heeft inmiddels.
In dat geval is het geen select() bug, maar een bug in de code. TCP keepalives hebben overigens net zo min een invloed op select().Verwijderd schreef op 11 september 2004 @ 16:44:
Lees het ff in de context igmar, ik ben het namelijk vrijwel geheel met je eens; de TS geeft aan dat het over HTTP Keep-Alive gaat, dus de enige (voor mij bendenkbare) manier waarop select() trager kan worden is als de client aan de de andere kant van de connection trager wordt.
Een vriend van me heeft met een of andere tool van Apple (shark) m'n webserver doorgemeten. 99% van de runtime gaat zitten in de select(). Heb select() vervangen door poll(), geen verschil. Ik heb echt geen idee wat er nu mis gaat.igmar schreef op 12 september 2004 @ 13:20:
In dat geval is het geen select() bug, maar een bug in de code. TCP keepalives hebben overigens net zo min een invloed op select().
Hoe wordt dat gemeten ? Als de tool in kwestie de tijd ook meet die select() in sleep doorbrengt kan dat wel eens kloppen. Verder is select() in de kernel een wrapper voor poll(), dus dat zal inderdaad weinig schelen. select() is overigens O(n), waarbij n de het nummer van fd's is die je als parameter doorgeeft. Die moet je dus zo klein mogelijk houden.Verwijderd schreef op 13 september 2004 @ 07:48:
Een vriend van me heeft met een of andere tool van Apple (shark) m'n webserver doorgemeten. 99% van de runtime gaat zitten in de select(). Heb select() vervangen door poll(), geen verschil. Ik heb echt geen idee wat er nu mis gaat.
Profilen kun je overigens zelf ook doen met gprof, of met een valgrind skin.
Heb je al geprobeerd of het met een kleinere select timeout beter gaat ?
Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.
Verwijderd
Even een logische opbouw van wat ik denk dat er aan de hand is:Verwijderd schreef op 13 september 2004 @ 07:48:
Een vriend van me heeft met een of andere tool van Apple (shark) m'n webserver doorgemeten. 99% van de runtime gaat zitten in de select(). Heb select() vervangen door poll(), geen verschil. Ik heb echt geen idee wat er nu mis gaat.
- Zowel select() als poll() moeten wachten op data die de clients over de lijn sturen.
- Als je dus een identieke test uitvoert (met evenveel clients ed.) met en zonder Keep-Alive dan moet je vergelijkbare wachttijden voor select() cq. poll() uitkrijgen.
- Als die wachttijden niet gelijk zijn, dan betekent dat dat select() cq. poll() langer moet wachten op data van de clients.
- Aangezien de enige verandering in de test de Keep-Alive optie is, moet die vertraging in de clients worden veroorzaakt door die Keep-Alive.
- Daar je mag aannemen dat de Keep-Alive optie in de clients goed geïmplementeerd is, mag je aannemen dat de fout in de Keep-Alive code van je server zit; een fout die de clients "van streek" maakt en ze trager laat worden.
Als ik ab draai met keep alive en het aantal concurrent connections opschroef komt de performance steeds meer bij de zonder keep alive test. Dus het ligt dan echt aan die ene thread die die ene (of meer, zo diep heb ik niet in je source gekeken) socket afhandeld.
Heb je de test al uitgeprobeerd met een grote hoeveelheid GET of for that matter POST data? (is mij niet gelukt, kon geen php niet aan de praat krijgen om post data te lezen
en had geen zin om een perl cgi te proberen ).
Als je geen keep alive hebt past elke request makkelijk in 1 packetje, die krijg je dus altijd helemaal in 1x binnen. Bij keep alive en bij grote requests wordt de hele loop om je select heen (en de select zelf) belangrijk. Als je wel snel bent met grote requests en traag met keep alive, tja dan kan het niet aan de select liggen.
Heb je de test al uitgeprobeerd met een grote hoeveelheid GET of for that matter POST data? (is mij niet gelukt, kon geen php niet aan de praat krijgen om post data te lezen
Als je geen keep alive hebt past elke request makkelijk in 1 packetje, die krijg je dus altijd helemaal in 1x binnen. Bij keep alive en bij grote requests wordt de hele loop om je select heen (en de select zelf) belangrijk. Als je wel snel bent met grote requests en traag met keep alive, tja dan kan het niet aan de select liggen.
Heb je de input van select al gecontroleerd?Verwijderd schreef op 13 september 2004 @ 07:48:
Een vriend van me heeft met een of andere tool van Apple (shark) m'n webserver doorgemeten. 99% van de runtime gaat zitten in de select(). Heb select() vervangen door poll(), geen verschil. Ik heb echt geen idee wat er nu mis gaat.
Misschien vergeet je bepaalde fds aan select te geven waardoor select pas returned als andere fds getriggered worden.
En wat doet select als je geen timeout meegeeft?
@igmar: geen idee, ik zag die tool toen voor het eerst.
"Verder is select() in de kernel een wrapper voor poll(),...": Niet altijd. MacOSX implementeert poll() m.b.v. select(), net omgekeerd dus
@farlane: ja. ipv x maal 1 seconde wachten, wacht ik nu gewoon x seconde. Geen verschil.
@mietje: "II Als je dus een identieke test uitvoert (met evenveel clients ed.) met en zonder Keep-Alive dan moet je vergelijkbare wachttijden voor select() cq. poll() uitkrijgen.": En daar gaat het dus al mis......
"Aangezien de enige verandering in de test de Keep-Alive optie is, moet die vertraging in de clients worden veroorzaakt door die Keep-Alive.": en die is bijna niks. Zoals je in connection_handler() (regel 827 in hiawatha.c) kunt zien is het enige dat ie tussen 2 requests doet een reset_session() (staat in session.c) die alleen wat pointers free()-ed. Lijkt me niet dat dat de vertraging kan opleveren.
Ik ga nog eens wat meer tests uitvoeren.....
"Verder is select() in de kernel een wrapper voor poll(),...": Niet altijd. MacOSX implementeert poll() m.b.v. select(), net omgekeerd dus
@farlane: ja. ipv x maal 1 seconde wachten, wacht ik nu gewoon x seconde. Geen verschil.
@mietje: "II Als je dus een identieke test uitvoert (met evenveel clients ed.) met en zonder Keep-Alive dan moet je vergelijkbare wachttijden voor select() cq. poll() uitkrijgen.": En daar gaat het dus al mis......
"Aangezien de enige verandering in de test de Keep-Alive optie is, moet die vertraging in de clients worden veroorzaakt door die Keep-Alive.": en die is bijna niks. Zoals je in connection_handler() (regel 827 in hiawatha.c) kunt zien is het enige dat ie tussen 2 requests doet een reset_session() (staat in session.c) die alleen wat pointers free()-ed. Lijkt me niet dat dat de vertraging kan opleveren.
Ik ga nog eens wat meer tests uitvoeren.....
Verwijderd
Ik snap dat je Keep-Alive code licht is, wat ik vermoed is dat je Keep-Alive code het RFC2616 protocol niet goed implementeert. Als jouw code volgens het protocol nog iets naar de clients zou moeten sturen (bv. een 100 Continue status) maar dat niet doet (of te laat doet, of niet in de juiste volgorde), kan het best zijn dat die clients daarop gaan zitten wachten, terwijl je server op dataverkeer van de clients wacht; met als gevolg dat de select()/poll() tijden op je server de pan uitrijzen.Verwijderd schreef op 14 september 2004 @ 18:18:
@mietje: "II Als je dus een identieke test uitvoert (met evenveel clients ed.) met en zonder Keep-Alive dan moet je vergelijkbare wachttijden voor select() cq. poll() uitkrijgen.": En daar gaat het dus al mis......
"Aangezien de enige verandering in de test de Keep-Alive optie is, moet die vertraging in de clients worden veroorzaakt door die Keep-Alive.": en die is bijna niks. Zoals je in connection_handler() (regel 827 in hiawatha.c) kunt zien is het enige dat ie tussen 2 requests doet een reset_session() (staat in session.c) die alleen wat pointers free()-ed. Lijkt me niet dat dat de vertraging kan opleveren.
Ik heb hier een RFC2616 als bijbel naast me liggen. Die is goed geimplementeert (1xx codes mogen btw niet gestuurd worden door een server).Verwijderd schreef op 14 september 2004 @ 18:36:
Ik snap dat je Keep-Alive code licht is, wat ik vermoed is dat je Keep-Alive code het RFC2616 protocol niet goed implementeert. Als jouw code volgens het protocol nog iets naar de clients zou moeten sturen (bv. een 100 Continue status) maar dat niet doet (of te laat doet, of niet in de juiste volgorde), kan het best zijn dat die clients daarop gaan zitten wachten, terwijl je server op dataverkeer van de clients wacht; met als gevolg dat de select()/poll() tijden op je server de pan uitrijzen.
Daarbij, ik heb zelf een stresstest tool geschreven en ook die is langzamer bij een keep-alive connectie (wacht dus niet op wat voor een extra tussen bericht dan ook).
Ik heb het gevoel dat het toch iets te maken heeft met een socket instelling/optie....
[ Voor 5% gewijzigd door Verwijderd op 14-09-2004 18:56 ]
Verwijderd
Klopt niet, 1xx codes mogen niet gestuurd worden naar een HTTP/1.0 client maar moeten gestuurd worden naar een HTTP/1.1 client:Verwijderd schreef op 14 september 2004 @ 18:54:
Ik heb hier een RFC2616 als bijbel naast me liggen. Die is goed geimplementeert (1xx codes mogen btw niet gestuurd worden door een server).
Maw. staat hier duidelijk dat de 1xx codes gestuurd dienen te worden door de HTTP/1.1 server (en geaccepteerd door de HTTP/1.1 client).10.1 Informational 1xx
This class of status code indicates a provisional response,
consisting only of the Status-Line and optional headers, and is
terminated by an empty line. There are no required headers for this
class of status code. Since HTTP/1.0 did not define any 1xx status
codes, servers MUST NOT send a 1xx response to an HTTP/1.0 client
except under experimental conditions.
A client MUST be prepared to accept one or more 1xx status responses
prior to a regular response, even if the client does not expect a 100
(Continue) status message. Unexpected 1xx status responses MAY be
ignored by a user agent.
[...]
10.1.1 100 Continue
The client SHOULD continue with its request. This interim response is
used to inform the client that the initial part of the request has
been received and has not yet been rejected by the server. The client
SHOULD continue by sending the remainder of the request or, if the
request has already been completed, ignore this response. The server
MUST send a final response after the request has been completed. See
section 8.2.3 for detailed discussion of the use and handling of this
status code.
Oh ja, dat was het
Maar iig, de 100 codes zijn niet het probleem/oplossing. Het is echt een socket probleem....
Pagina: 1