Async operaties gebruiken op MemoryStream in server-app ?

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:10
Wij zijn bezig met het maken van een server-applicatie die verantwoordelijk is voor messaging.

De applicatie moet berichten encrypten, compressen, signen etc... Er wordt veelvuldig gebruik gemaakt van Streams. Dit kunnen FileStreams, MemoryStreams, etc... zijn.

Initieel dachten we dat we best zoveel mogelijk gebruik moesten maken van async methods (CopyToAsync, ReadAsync, WriteAsync, etc...) indien we operaties uitvoeren op een Stream. Op die manier zou onze applicatie beter moeten functioneren onder heavy load.

Na wat onderzoek echter blijkt dat Async operaties veel trager zijn dan ik initieel dacht. Ik wist dat er een overhead was, maar het kopieëren van een FileStream naar een andere FileStream gaat via CopyToAsync een factor 5 tot 6x trager dan een reguliere CopyTo. (Blijkt wel dat ik dit kan versnellen door eerst de Length van de target-stream te zetten, maar dat kan ik niet altijd doen: bij het encrypten en compressen bv weet ik niet wat de uiteindelijke size van de target-stream zal zijn).

Nu lees ik ook dat MemoryStream bv geen echte async operaties ondersteunt. De operatie wordt gewoon synchroon uitgevoerd; dit voegt dus niet meer toe dan wat additionele overhead en zal imo dus ook niet bijdragen tot het beter functioneren van de applicatie onder heavy load.

Is het dus nuttig om in dit geval gebruik te maken van de async methods die binnen het .NET framework beschikbaar zijn ? Kan ik niet beter gewoon overal de synchrone varianten gaan gebruiken ?

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 13-05 09:12
It depends, waarschijnlijk. Synchroon zal *altijd* sneller zijn, maar vanaf een "bepaalde" lengte wordt het wachten er op vervelend voor je applicatie. Op dat moment zou je async moeten gaan werken.

Verwacht ik. :P

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.


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:10
Het is een server app.
Dus, je doet er een request naar met een bepaalde payload.

Die payload moet gedecrypt, gedecompressed, etc... worden. Dit gebeurd allemaal async, zodanig dat threads vrijkomen indien men met I/O bezig is (bv het kopiëren van data naar een GZipStream. Echter, ik kan me voorstellen dat hier ook wel wat CPU werk bij te pas komt. Die stream wordt nl. niet zomaar gedecompressed).

Als ik dat decompressen sync doe, dan gaat dat 30% sneller dan async. Dus, vraag ik me af of ik hier wel goed aan doe om dit async te doen.

Hetzelfde als het gaat om het persisten van de data (van een memorystream naar een filestream). Ook dit gaat async trager dan sync (wat ik wel verwacht), maar hier zie ik eigenlijk wel het nut van in om het async te doen.

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • supreme tweaker
  • Registratie: December 2002
  • Laatst online: 04-05 01:58
Inderdaad, het compressen en decompressen (streamprocessing) van een GZipStream zal niet vanzelf gaan. Er zal een onderliggende stream in gaan, welke verwerkt wordt (verwacht ik). Is dit een MemoryStream en dus heeft asynchroon minder effect omdat je niet op IO zit te wachten? Of is dit een FileStream, waarbij je wellicht de storage gewoon aan het werk kan zetten? Als dit wisselt, wat is de verhouding? Is het het je waard om op basis daarvan een andere strategie te kiezen?

Ook rijst bij mij de vraag hoe je deze processen aanstuurt (workers threads?) en of de aanroepende partij van deze server app ook wacht op het resultaat.

En daarna uiteraard de vragen; Hoe belangrijk is optimalisatie in dit geval? Hoeveel complexiteit is het je waard om toe te voegen? Hoe schaars zijn resources en kun je schalen? Hoeveel concurrent operaties verwacht je?

Burp


Acties:
  • 0 Henk 'm!

  • Hydra
  • Registratie: September 2000
  • Laatst online: 12-05 15:52
Kun je eens een simpel voorbeeld tonen van hoe je async vs. sync gebruikt? Want 30% verschil is wel heel erg extreem.

https://niels.nu


Acties:
  • 0 Henk 'm!

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
whoami schreef op dinsdag 14 november 2017 @ 11:34:
Wij zijn bezig met het maken van een server-applicatie die verantwoordelijk is voor messaging.

De applicatie moet berichten encrypten, compressen, signen etc... Er wordt veelvuldig gebruik gemaakt van Streams. Dit kunnen FileStreams, MemoryStreams, etc... zijn.
Vanwege het feit dat het een server-app is zou ik 1 groot onderscheid maken : Je hebt edge streams (die kunnen async zijn) en je hebt interne streams (die zou ik niet async maken maar gewoon zelf met multithreading afhandelen etc omdat je wmb dan beter resourcemanagement kan doen)

Je edge streams die moeten in principe over een 2400 baud modem nog werken om een bestand in je app te krijgen, maar intern zou ik op voorspelbaarheid overgaan.
Na wat onderzoek echter blijkt dat Async operaties veel trager zijn dan ik initieel dacht.
Dit zit hem praktisch idd in de length van de target stream, als die niet bekend is moet die telkens bijgeschaafd worden en met async operaties zijn je buffers standaard kleiner ingesteld.
Oftewel je kan nog wat aan je buffers gaan schaven qua async instellingen zodat er minder geresized hoeft te worden. Alleen dat verzwaart wel weer je load.
Is het dus nuttig om in dit geval gebruik te maken van de async methods die binnen het .NET framework beschikbaar zijn ? Kan ik niet beter gewoon overal de synchrone varianten gaan gebruiken ?
Zoals ik al zeg, ik zou async aan de edges gebruiken en intern gewoon alles sync maken zodat je beter een loadverwachting kan geven.

Async is geen wondermiddel, het is (simplistisch gezegd) gewoon een nieuwe thread opstarten en dan daarin alles sync afhandelen met kleinere buffers etc.
Dat werkt leuk en makkelijk met 1 async actie, maar als jij echt 10000 async acties tegelijk wilt uitvoeren dan zitten al je hdd-buffers vol en gaat alles alleen maar traag. Dan liever handmatig 5000 threads in beheer houden en de rest in een queue gooien zodat je kan sturen op de max performance van je hdd's zonder ze te overladen.

Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 23:10
supreme tweaker schreef op woensdag 15 november 2017 @ 18:23:
Inderdaad, het compressen en decompressen (streamprocessing) van een GZipStream zal niet vanzelf gaan. Er zal een onderliggende stream in gaan, welke verwerkt wordt (verwacht ik). Is dit een MemoryStream en dus heeft asynchroon minder effect omdat je niet op IO zit te wachten? Of is dit een FileStream, waarbij je wellicht de storage gewoon aan het werk kan zetten? Als dit wisselt, wat is de verhouding? Is het het je waard om op basis daarvan een andere strategie te kiezen?
Dat is idd de werking van een GZipStream, die eigenlijk een decorater-stream is rond een andere stream.
Ook al is de stream een FileStream -en ik in principe baat kan hebben bij async I/O-, dan is async een 30% trager.
Ook rijst bij mij de vraag hoe je deze processen aanstuurt (workers threads?) en of de aanroepende partij van deze server app ook wacht op het resultaat.
Indien er een request binnenkomt, wordt er een Task gestart (Task.Run()) , waar dan de uiteindelijk verwerking van het bericht in gebeurd.
En ja, de aanroepende partij wacht op een antwoord. De server antwoord met en synchrone response, maar dit is eigenlijk allemaal niet zo relevant.
Hydra schreef op woensdag 15 november 2017 @ 18:37:
Kun je eens een simpel voorbeeld tonen van hoe je async vs. sync gebruikt? Want 30% verschil is wel heel erg extreem.
Dat vond ik ook, en vandaar dus ook dit topic. Ik had wel iets van verschil verwacht, maar nauwelijks significant.
In .NET kan je normaal gezien relatief makkelijk gebruik maken van async I/O dmv bv:

(Voorbeeldcode, kan dus zijn dat het niet compiled ofzo, heb dit hier nu even gewoon zo getyped)

C#:
1
2
3
4
5
6
7
8
9
10
private static Task<Stream> DecompressStream( Stream zippedStream )
{
      // -> de FileStream wordt geopend voor async io
      var resultStream = new FileStream("...", FileMode.Create, ..., isAsync: true);

      using( var unzipStream = new GZipStream(zippedStream, Mode.Decompress))
      {
              return await unzipStream.CopyToAsync(resultStream);
      }
}

synchroon is er syntactisch nauwelijks verschil:
C#:
1
2
3
4
5
6
7
8
9
private static Stream DecompressStream( Stream zippedStream )
{
      var resultStream = new FileStream("...", FileMode.Create);

      using( var unzipStream = new GZipStream(zippedStream, Mode.Decompress))
      {
              return unzipStream.CopyTo(resultStream);
      }
}
Gomez12 schreef op woensdag 15 november 2017 @ 19:34:
[...]

[...]

Async is geen wondermiddel, het is (simplistisch gezegd) gewoon een nieuwe thread opstarten en dan daarin alles sync afhandelen met kleinere buffers etc.
Bij async I/O ga je echt geen nieuwe thread gaan opstarten.
Als je een async I/O operatie doet (zoals CopytoAsync naar een FileStream), dan wordt er geen nieuwe thread gestart. De worker-thread waarop de actie gestart wordt, keert terug naar de thread-pool en is dus beschikbaar voor iets anders. Pas als de I/O actie afgelopen is, zal er weer een context switch naar een thread gebeuren. klik
Dit is natuurlijk anders voor cpu-bound async operaties; daar ge je typisch wel een andere thread starten.

Nu is het wel zo dat die GZipStream geen zuivere I/O zal doen, aangezien het compressen/decompressen ook nog moet gebeuren.

[ Voor 3% gewijzigd door whoami op 15-11-2017 22:38 ]

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • supreme tweaker
  • Registratie: December 2002
  • Laatst online: 04-05 01:58
Ik moet zeggen dat je nog niet zoveel van mijn vragen beantwoordt hebt. Zou je hier nog op terug willen komen?

Verder zou je de framework sources kunnen raadplegen om te kijken hoe deze operaties precies in hun werk. Mocht je .NET Core gebruiken, zou je zelfs een eigen versie kunnen compileren met wat timers erin.

Burp


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 13-05 09:12
whoami schreef op woensdag 15 november 2017 @ 22:12:
Bij async I/O ga je echt geen nieuwe thread gaan opstarten.
Als je een async I/O operatie doet (zoals CopytoAsync naar een FileStream), dan wordt er geen nieuwe thread gestart. De worker-thread waarop de actie gestart wordt, keert terug naar de thread-pool en is dus beschikbaar voor iets anders. Pas als de I/O actie afgelopen is, zal er weer een context switch naar een thread gebeuren. klik
Dit is natuurlijk anders voor cpu-bound async operaties; daar ge je typisch wel een andere thread starten.
Volgens hem is er geen thread.

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.


  • whoami
  • Registratie: December 2000
  • Laatst online: 23:10
Dat zeg ik toch ? Er wordt geen nieuwe thread aangemaakt om de I/O af te handelen.
De thread waarop de I/O actie gestart werd (want die is er natuurlijk wel), is beschikbaar om iets anders te doen.

https://fgheysels.github.io/


  • pedorus
  • Registratie: Januari 2008
  • Niet online
Gomez12 schreef op woensdag 15 november 2017 @ 19:34:
Dan liever handmatig 5000 threads in beheer houden en de rest in een queue gooien zodat je kan sturen op de max performance van je hdd's zonder ze te overladen.
5000 threads gaat je op dit moment vrijwel nooit helpen tenzij je wacht op externe services en zsm wil reageren. Als je schrijft naar disk heeft 5000 threads nooit zin denk ik, in het ergste geval gaan zelfs de koppen meer bewegen of zijn de streams niet sequentieel genoeg meer en wordt het schrijven trager. Ook zijn 5000 threads waarschijnlijk funest voor goede garbage collection latency. 50-500 zou kunnen.
whoami schreef op woensdag 15 november 2017 @ 22:12:
Nu is het wel zo dat die GZipStream geen zuivere I/O zal doen, aangezien het compressen/decompressen ook nog moet gebeuren.
In mijn ervaring hangt een GZipStream naar disk op 1 thread met 100% CPU en niet op disk. Daar wil je dus niet extra wisselingen en wachttijden in gaan bouwen, daarvan wil je dat 1 dedicated thread continu bezig is om te comprimeren. Gezien de opbouw van gzip (1 bit omslag kan rest stream volledig veranderen) zijn partities of lagere compressiefactors de enige manier om te versnellen. Of geen gzip gebruiken. Mocht je nu de thread waarin je je bevind niet bezet willen houden, dan is overhevelen naar een queue met dedicated workers natuurlijk een prima optie.

Verder is een goede profiler je vriend, ik ben zelf fan van visualvm geworden en dat is met c# natuurlijk geen optie. Ik gok dat er betere zijn dan die van Microsoft zelf, of wellicht is die inmiddels een stuk beter in het gedrag van threads analyseren.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 13-05 09:12
whoami schreef op donderdag 16 november 2017 @ 09:28:
Dat zeg ik toch ? Er wordt geen nieuwe thread aangemaakt om de I/O af te handelen.
De thread waarop de I/O actie gestart werd (want die is er natuurlijk wel), is beschikbaar om iets anders te doen.
Mijn excuses, ik had je reply niet aandachtig genoeg gelezen. :D

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.

Pagina: 1