Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Non-blocking socket gir større latens

2 views
Skip to first unread message

Asbjørn Sæbø

unread,
Apr 1, 2005, 5:57:32 AM4/1/05
to

Kortversjon: Dersom eg set mottakar-socketen min (UDP) til å vere
ikkje-blokkerande aukar totallatensen i overføringa frå titals
millisekund til hundretals millisekund. Kva er årsaka til det?


Eg arbeider med eit program for å overføre audio over UDP/IP med lav
latens. OS er Linux. Programmet er todelt, med ein sendardel som
enkelt og greit les data frå lydkortet, pakkar dei og sender dei over
UDP.

Mottakardelen har ei hovedløkke som, prinsipielt sett, går slik:

while (do_continue) {
nread = recvfrom(sockfd, packet->data, ...); /* Lese pakkedata frå socket */
queueenqueuepacket( &queue, &packet ); /* Leggje pakke inn i kø */
queuecopydata(&queue, &data, ...); /* Bygge buffer frå data i kø */
snd_pcm_writei(soundcardh, data, ...); /* Spele av data i buffer */
}

Dersom eg lar socketen vere blokkerande får eg lav latens i
overføringa, med ei total forseinking på vel ti millisekund.
(Dette inkluderar A/D og D/A-omvandling, buffring i lydkort hos
avsendar, nettverkslag i OS, transporttid i nettet og køa hos
mottakaren.)

P.g.a. at mottakar og avsendar ikkje har heilt synkrone klokker (på
lydkorta) vil det før eller seinare oppstå overskot eller underskot på
pakker hos mottakaren. Derfor kan eg ikkje bruke ei slik enkel
blokkerande løkke som over. Derfor gjer eg socketen ikkje-blokkerande
(set flagget O_NONBLOCK), og sjekkar om errno er EWOULDBLOCK etter
kallet til recvfrom(). Men dermed går total-latensen opp frå titals
millisekund til fleire hundre millisekund. Og ein så høg latens er er
ikkje akseptabelt i denne samanhengen. (Programmet skal brukast til
f.eks. musikalsk samspel over nettet.)

Eg hadde ein teori om at årsaka kunne vere buffring i nettverkslaga i
OS-et. Derfor har eg prøvd meg fram med å leggje til eit ekstra
recvfrom() i løkka. Men det returnerar alltid med EWOULDBLOCK, som
skulle tyde på at det ikkje ligg data på vent her.

Eg har og sett på køinga mi. Men kølengda, og den tida som går frå ei
pakke blir motteka til ho blir avspela, er lik i dei to tilfella.
D.v.s. at den ekstra latensen ser ut til å oppstå "før" kallet til
recvfrom(). Det skulle tyde på at forskjellen ligg i nettverksstakken
til OS-et.

Eg har ikkje greidd å finne ut av kvifor dette skjer, og har heller
ikkje hatt noko hell med meg med å spørje meg fram mellom dei
datakunnige her på bruket, så derfor spør eg her: Kan nokon hjelpe meg
med forklaringar på, eller idear til, kvar, og kvifor, denne store
latensen oppstår?


(Og ja, eg veit at dette antakeleg bør gjerast med interrupt-styring
eller callback-funksjonar. Og eg kjem nok til å gå over til det. Men
det er lite tilfredsstillande å gjere det utan først å ha forstått
kvifor det eg gjer no ikkje verkar.)


Asbjørn
--
Asbjørn Sæbø, post.doc.
Centre for Quantifiable Quality of Service in Communication Systems
Norwegian University of Science and Technology
<URL: http://www.q2s.ntnu.no/ >

Hallvard B Furuseth

unread,
Apr 1, 2005, 9:02:07 AM4/1/05
to
Asbjørn Sæbø <as...@stud.ntnu.no> writes:
> while (do_continue) {
> nread = recvfrom(sockfd, packet->data, ...); /* Lese pakkedata frå socket */
> queueenqueuepacket( &queue, &packet ); /* Leggje pakke inn i kø */
> queuecopydata(&queue, &data, ...); /* Bygge buffer frå data i kø */
> snd_pcm_writei(soundcardh, data, ...); /* Spele av data i buffer */
> }

> (...) Derfor gjer eg socketen ikkje-blokkerande (set flagget


> O_NONBLOCK), og sjekkar om errno er EWOULDBLOCK etter kallet til
> recvfrom(). Men dermed går total-latensen opp frå titals millisekund
> til fleire hundre millisekund.

Med en ikke-blokkerende løkke som det hopper vel loaden på maskinen
og blir konstant rundt 1, og da går alt tregere.

Foreslår at du i stedet har en egen tråd som gjår blokkerende
recvfrom(), og en annen tråd som gjør resten. Forhåpentligvis slik at
den andre tråden også kan blokkere når det ikke er noe å gjøre.
Muligens vil du trenge en tredje tråd til å styre det hele.

Dermed kan recvfrom() også lese data _samtidig_ med at den andre tråden
gjør noe annet, hvis ikke begge trådene trenger CPUen på en gang. (Vet
ikke hvor mye det gjør forskjell for UDP-sockets.) På den annen side
trengs litt mutexer og kanskje pthread_cond_signal(), og det tar litt
tid.

--
Hallvard

Asbjørn Sæbø

unread,
Apr 1, 2005, 9:53:51 AM4/1/05
to
Hallvard B Furuseth <h.b.fu...@usit.uio.no> writes:

> Asbjørn Sæbø <as...@stud.ntnu.no> writes:
> > while (do_continue) {
> > nread = recvfrom(sockfd, packet->data, ...); /* Lese pakkedata frå socket */
> > queueenqueuepacket( &queue, &packet ); /* Leggje pakke inn i kø */
> > queuecopydata(&queue, &data, ...); /* Bygge buffer frå data i kø */
> > snd_pcm_writei(soundcardh, data, ...); /* Spele av data i buffer */
> > }
>
> > (...) Derfor gjer eg socketen ikkje-blokkerande (set flagget
> > O_NONBLOCK), og sjekkar om errno er EWOULDBLOCK etter kallet til
> > recvfrom(). Men dermed går total-latensen opp frå titals millisekund
> > til fleire hundre millisekund.
>
> Med en ikke-blokkerende løkke som det hopper vel loaden på maskinen
> og blir konstant rundt 1, og da går alt tregere.

Der var ein ting eg hadde gløymt å nemne, ja.

Nei, det blir ikkje slik. Kallet til snd_pcm_writei() blokkerar til
lydkortet kan ta mot data. Slik blir det lydkortet som styrer tempoet
i heile greia, som det må vere.

Programmet brukar nesten ikkje CPU i det heile. Stort sett ligg det
nede ved null prosent, av og til så langt opp som til to prosent.


> Foreslår at du i stedet har en egen tråd som gjår blokkerende

> recvfrom(), og en annen tråd som gjør resten. [...]

Det er eit alternativ, og eg har tenkt på det. Men som eg sa, det er
lite tilfredsstillande å skifte tilnærming til problemet utan å ha
skjønt kvifor denne metoden ikkje verkar.

Markus B. Krüger

unread,
Apr 1, 2005, 1:40:20 PM4/1/05
to
Asbjørn Sæbø <as...@stud.ntnu.no> writes:

> Hallvard B Furuseth <h.b.fu...@usit.uio.no> writes:
>
> > Asbjørn Sæbø <as...@stud.ntnu.no> writes:
> >
> > > (...) Derfor gjer eg socketen ikkje-blokkerande (set flagget
> > > O_NONBLOCK), og sjekkar om errno er EWOULDBLOCK etter kallet til
> > > recvfrom(). Men dermed går total-latensen opp frå titals
> > > millisekund til fleire hundre millisekund.
> >
> > Med en ikke-blokkerende løkke som det hopper vel loaden på
> > maskinen og blir konstant rundt 1, og da går alt tregere.
>
> Der var ein ting eg hadde gløymt å nemne, ja.
>

> Kallet til snd_pcm_writei() blokkerar til lydkortet kan ta mot data.
> Slik blir det lydkortet som styrer tempoet i heile greia, som det må
> vere.

Hva med å la socketen blokkere hvis og bare hvis køen er tom? Noe
slikt som

int flags = fcntl(sockfd, F_GETFL);
while (do_continue) {
if (queueempty(&queue)) {
/* Make socket blocking */
fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK);
} else {
/* Make socket non-blocking */
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);


}
nread = recvfrom(sockfd, packet->data, ...);

queueenqueuepacket( &queue, &packet );
queuecopydata(&queue, &data, ...);
snd_pcm_writei(soundcardh, data, ...);
}

Da slipper du at lydkortet venter på en blokkert socket hvis det er
data i køen, samtidig som du får fatt i data så fort som mulig hvis
køen er tom. Hvis fcntl() er en dyr operasjon, kan du legge til
logikk for å unngå å skru på blokkering hvis blokkering allerede er
på (og tilsvarende for å skru av blokkering).

Alternativt, hvis det er mulig å behandle lydkortet som en
fildeskriptor, kan du bruke select().

--
,------------------- Markus Bjartveit Krüger ---------------------.
' `
` E-mail: mar...@pvv.org WWW: http://www.pvv.org/~markusk/ '
)-------------------------------------------------------------------(

Frode Vatvedt Fjeld

unread,
Apr 1, 2005, 3:26:01 PM4/1/05
to
Asbjørn Sæbø <as...@stud.ntnu.no> writes:

> Mottakardelen har ei hovedløkke som, prinsipielt sett, går slik:
>
> while (do_continue) {
> nread = recvfrom(sockfd, packet->data, ...); /* Lese pakkedata frå socket */
> queueenqueuepacket( &queue, &packet ); /* Leggje pakke inn i kø */
> queuecopydata(&queue, &data, ...); /* Bygge buffer frå data i kø */
> snd_pcm_writei(soundcardh, data, ...); /* Spele av data i buffer */
> }
>
> Dersom eg lar socketen vere blokkerande får eg lav latens i
> overføringa, med ei total forseinking på vel ti millisekund. (Dette
> inkluderar A/D og D/A-omvandling, buffring i lydkort hos avsendar,
> nettverkslag i OS, transporttid i nettet og køa hos mottakaren.)

> P.g.a. at mottakar og avsendar ikkje har heilt synkrone klokker (på
> lydkorta) vil det før eller seinare oppstå overskot eller underskot på
> pakker hos mottakaren. Derfor kan eg ikkje bruke ei slik enkel
> blokkerande løkke som over. Derfor gjer eg socketen ikkje-blokkerande
> (set flagget O_NONBLOCK), og sjekkar om errno er EWOULDBLOCK etter
> kallet til recvfrom(). Men dermed går total-latensen opp frå titals
> millisekund til fleire hundre millisekund. Og ein så høg latens er er
> ikkje akseptabelt i denne samanhengen. (Programmet skal brukast til
> f.eks. musikalsk samspel over nettet.)

Hvordan foregår synkroniseringen når systemet starter opp? Hvis
socketen ikke er blokkerende og du ikke har noe lyddata å spille av
(hvilket du jo ikke har før du har mottat noe) så .. vel noe må du
bestemme deg for å gjøre for å synkronisere mottakeren med
senderen. Hvis denne initielle synkroniseringen er dårlig, ligger det
vel ikke i sakens natur at latensen vil forsvinne etterhvert. Mao. bør
det vel være en eller annen form for eksplisitt initiell
synkronisering der du venter (blokkerende) på data fra senderen.

--
Frode Vatvedt Fjeld

Asbjørn Sæbø

unread,
Apr 4, 2005, 3:03:19 AM4/4/05
to
Frode Vatvedt Fjeld <fro...@cs.uit.no> writes:

> Asbjørn Sæbø <as...@stud.ntnu.no> writes:
>
> > [Overføring av lyddata over UDP/IP, med lav latens, der latensen
> > går opp dersom socketen blir sett ikkje blokkerande.]

> Hvordan foregår synkroniseringen når systemet starter opp? Hvis
> socketen ikke er blokkerende og du ikke har noe lyddata å spille av
> (hvilket du jo ikke har før du har mottat noe) så .. vel noe må du
> bestemme deg for å gjøre for å synkronisere mottakeren med
> senderen. Hvis denne initielle synkroniseringen er dårlig, ligger
> det vel ikke i sakens natur at latensen vil forsvinne
> etterhvert. Mao. bør det vel være en eller annen form for eksplisitt
> initiell synkronisering der du venter (blokkerende) på data fra
> senderen.

Det er synkronisering, både ved oppstart og undervegs.
(Synkronisering undervegs må det vere for å sikre seg mot at det ikkje
byggjer seg oppp ekstra latens p.g.a. "for rask" sendar.)

Eg ser på det som skal overførast som ein straum av data. Straumen er
oppdelt i pakker, der sendaren utstyrer kvar pakke med eit
sekvensnummer. Mottakaren har ein "avspelingsposisjon", gitt som
sekvensnummer/del av sekvensnummer i straumen, som eit mål på kvar i
straumen vi er. (Denne posisjonen blir naturlegvis oppdatert kvar
gong data blir overført frå køa til lydkortet.)

Synkronisering skjer ved at mottakaren ser på sekvensnummeret til
innkommande pakker. Dersom avspelingsposisjonen er "ute av synk" i
forhold til innkommande data blir han justert tilsvarande. Formålet
med resynkroniseringa er å halde (den lavpassfiltrerte) kølengda på
ein lav verdi. Resynkroniseringa _kan_ føre til at mottekne pakker
blir kasta, det er viktigare å ha lav latens.

Dersom mottakaren har data (f.eks. fordi sendaren ikkje er starta,
eller fordi pakker har gått tapt) blir det spela av dummy-data så
lenge. Så snart det er gyldige data tilgjengelege att blir avspelinga
resynkronisert.


Så langt eg kan sjå ligg ikkje feilen i synkroniseringa. I begge
tilfella (blokkerande/ikkje-blokkerande) brukar data like lang tid
"gjennom" mottakaren (det har eg kontrollert). Og sidan eg får
EWOULDBLOCK for ekstra lesingar frå socketen skal det heller ikkje
liggje data på vent der, så langt eg har forstått det.

Asbjørn Sæbø

unread,
Apr 4, 2005, 3:52:50 AM4/4/05
to
mar...@pvv.org (Markus B. Krüger) writes:

> Asbjørn Sæbø <as...@stud.ntnu.no> writes:
>
> > Hallvard B Furuseth <h.b.fu...@usit.uio.no> writes:
> >
> > > Asbjørn Sæbø <as...@stud.ntnu.no> writes:
> > >
> > > > (...) Derfor gjer eg socketen ikkje-blokkerande (set flagget
> > > > O_NONBLOCK), og sjekkar om errno er EWOULDBLOCK etter kallet til
> > > > recvfrom(). Men dermed går total-latensen opp frå titals
> > > > millisekund til fleire hundre millisekund.
> > >
> > > Med en ikke-blokkerende løkke som det hopper vel loaden på
> > > maskinen og blir konstant rundt 1, og da går alt tregere.
> >
> > Der var ein ting eg hadde gløymt å nemne, ja.
> >
> > Kallet til snd_pcm_writei() blokkerar til lydkortet kan ta mot data.
> > Slik blir det lydkortet som styrer tempoet i heile greia, som det må
> > vere.
>

> Hva med å la socketen blokkere hvis og bare hvis køen er tom? [...]


> Da slipper du at lydkortet venter på en blokkert socket hvis det er
> data i køen, samtidig som du får fatt i data så fort som mulig hvis
> køen er tom.

Tja, kanskje. Men det er ikkje meininga at køa skal gå tom, den
fungerar som ein buffer for å regulere for jitter og drift. Så ved
normal operasjon vil socketen vere ikkje-blokkerande, med den høge
latensen eg ikkje kan ha.

Markus B. Krüger

unread,
Apr 4, 2005, 12:05:20 PM4/4/05
to
Asbjørn Sæbø <as...@stud.ntnu.no> writes:

> Dersom mottakaren har data (f.eks. fordi sendaren ikkje er starta,
> eller fordi pakker har gått tapt) blir det spela av dummy-data så
> lenge. Så snart det er gyldige data tilgjengelege att blir
> avspelinga resynkronisert.

Antar det mangler et "ikke" i første setning i det siterte avsnittet?
Har du noen målinger på hvor ofte det blir spilt av dummy-data med og
uten blokkerende socket? Det kunne være en grei indikasjon på om det
hender at køen går tom.

Asbjørn Sæbø

unread,
Apr 5, 2005, 2:26:51 AM4/5/05
to
mar...@pvv.org (Markus B. Krüger) writes:

> Asbjørn Sæbø <as...@stud.ntnu.no> writes:
>
> > Dersom mottakaren har data (f.eks. fordi sendaren ikkje er starta,
> > eller fordi pakker har gått tapt) blir det spela av dummy-data så
> > lenge. Så snart det er gyldige data tilgjengelege att blir
> > avspelinga resynkronisert.
>
> Antar det mangler et "ikke" i første setning i det siterte avsnittet?

Det stemmer. Det skal vere "Dersom mottakaren ikkje har data [...]".

> Har du noen målinger på hvor ofte det blir spilt av dummy-data med og
> uten blokkerende socket? Det kunne være en grei indikasjon på om det
> hender at køen går tom.

Eg har ingen eksplisitt rapport på det. Men eg har temmeleg god
kontroll på det ut frå rapportane om kva pakker som kjem inn og kva
pakker som blir spela av, og ut frå kvaliteten på på musikken som blir
øverført. Dummy-data blir stort sett berre avspela ved oppstart og
dersom eg f.eks. pausar sendaren. På dette området ser eg ingen
forskjell mellom blokkerande og ikkje-blokkerande.

0 new messages