Aalmada “Los Baños de Doña María de Padilla”

Kuinka käyttää Span ja Memory

(Päivitetty .NET Core 2.1: n viralliseen julkaisuversioon)

esittely

Span ja Muisti ovat uusia ominaisuuksia .NET Core 2.1: ssä, jotka sallivat vierekkäisen muistin vahvan tyyppisen hallinnan riippumatta siitä, kuinka se on allokoitu. Nämä antavat koodin ylläpidon helpommaksi ja parantavat sovellusten suorituskykyä huomattavasti vähentämällä tarvittavien muistin allokointien ja kopioiden määrää.

Syistä, joita muut voivat selittää paljon paremmin, Span voi esiintyä vain pinossa (toisin kuin kasassa). Tämä tarkoittaa, että se ei voi olla luokan kenttä tai mikään ”laatikkokelpoinen” rakenne (muunnettavissa vertailutyypiksi). Span hyödyntää ref # structia, uutta ominaisuutta C # 7.2: ssä, jolloin kääntäjä valvoo tätä sääntöä.

Seuraavaksi aion luoda muutaman käyttökehityksen, jotta ymmärrät paremmin, milloin ja miten näitä uusia ominaisuuksia käytetään.

Paikalliset muuttujat

Kuvittelemme, että meillä on palvelu, joka palauttaa kokoelman Foo-tyyppisiä esineitä. Kokoelma tulee jostakin syrjäisestä sijainnista, joten se tulee tavuvirtaan. Tämä tarkoittaa, että joudumme hankkimaan palasen tavujen paikalliselle puskurille, muuntamaan ne esineiksi ja toistamaan tämän prosessin kokoelman loppuun asti.

Seuraava esimerkki toistuu kokoelman läpi ja laskee kunkin kohteen Kokonaisluku-kentässä olevien arvojen summan.

Huomaa rivillä 5, että puskuri on allokoitu taulukkona , mutta tallennettu paikallisena muuttujana, jonka tyyppi on Span . Kehys sisältää implisiittiset operaattorit, jotka sallivat tämän muuntamisen.

Paikalliset muuttujat tavanomaisissa menetelmissä (joissa ei käytetä asyncia, satoa tai lambdaa) allokoidaan pinoon. Tämä tarkoittaa, että Span -toiminnolla ei ole mitään ongelmia. Muuttuja on pysyvä niin kauan kuin oma laajuus, tässä tapauksessa itse toiminto.

Jos tarkistat Stream.Read () -menetelmän allekirjoituksen, huomaat, että se hyväksyy Span -tyyppisen argumentin. Tämä tarkoittaa yleensä, että meidän olisi kopioitava muisti. Ei Span kanssa. Niin kauan kuin T on arvotyyppi, mikä on tapaus, voit käyttää MemoryMarshal.Cast () -menetelmää, joka peittää puskurin toiseksi tyypiksi ilman kopioita. Siirrä Span Stream.Read () (rivit 8 ja 15), mutta lue sen sisältö Foo-kokoelmana Span (rivi 13) avulla. Voit käyttää Span -sovellusta hakasulke-operaattorilla tai etusilmukalla.

Koska lueteltujen kappaleiden lukumäärä voi luettelon lopussa olla pienempi kuin puskurin koko, iteroimme alkuperäisen puskurin viipaleella. Slice () on menetelmä, joka palauttaa toisen Span samalle puskurille, mutta eri rajoituksilla.

Huomaa, että Stream.Read (): n lisäksi muistikopioita ei ole. Vain saman puskurin peittäminen. Tämä johtaa merkittäviin suorituskyvyn parannuksiin verrattuna muistihallintoihin, joita meillä oli aiemmin. Kaikki tämä tyyppiturvalla ja helppohoitoisella koodilla.

stackalloc

Huomaa edellisestä esimerkistä, että span on asuttava pinoon, mutta ei sen sisältöön. Matriisi allokoidaan kasaan.

Koska tässä tapauksessa puskurin ei tarvitse olla vanhempi kuin funktio ja se on suhteellisen pieni, voimme allokoida puskurin pinossa stackalloc-avainsanan avulla.

Puskurivarauksen lisäksi kaikki koodi pysyy muuttumattomana. Tämä on vielä yksi etu, kun käytetään Span -sovellusta. Se tiivistää kuinka vierekkäinen muisti allokoitiin.

ref rakenne

Nämä aikaisemmat esimerkit toimivat hyvin, mutta entä jos haluat suorittaa useita toimintoja kokoelman yli? Sinun on toistettava tämä koodi monissa muissa paikoissa luomalla ylläpito painajainen. Entä jos voisimme käyttää foreach-silmukkaa? Meidän on vain luotava pinoon allokoitu rakenne, joka toteuttaa IEnumerator .

Valitettavasti kaikki rajapinnan toteuttavat arvotyypit ovat ”laatikkokykyisiä”, mikä tarkoittaa, että ne voidaan muuntaa referenssityypeiksi.

Onneksi foreach ei oikeastaan ​​edellytä rajapintojen käyttöönottoa. Se vaatii vain menetelmän GetEnumerator (), joka palauttaa objektin esiintymän, joka toteuttaa vain luku -ominaisuus Nykyinen, ja menetelmän MoveNext (). Näin oikeastaan ​​Span -laskenta toteutetaan. Voimme tehdä samoin kokoelmallemme.

Huomaa riveillä 17 ja 18, että jännevälit eivät ole paikallisia muuttujia, vaan Enumerator-rakenteen kentät. Varmista, että tämä objekti luodaan vain pinossa, ilmoittamalla riville 12, että se julistetaan ref-rakenteeksi. Ilman tätä kääntäjä näyttäisi virheen.

Jatkojen luominen on nyt Enumerator-rakentajassa, mutta hyvin samanlainen kuin ensimmäinen esimerkki (parhaan tietoni mukaan tässä tapauksessa ei voida käyttää pinoa). Luettelo on nyt jaettu Nykyiseen ja Siirrä seuraavaan (). foreach kutsuu menetelmää MoveNext () siirtyäksesi kokoelmien seuraavaan kohtaan ja kutsuu sitten Nykyinen hankkimaan sen.

Huomaa, että nykyinen palauttaa vain luku -tyyppisen viittauksen tyypiksi Foo. Tämä tarkoittaa, että se käyttää kohdetta kopioimatta sitä. Tämä on myös C # 7.2: n ominaisuus, jota voidaan käyttää parantamaan sovellusten suorituskykyä huomattavasti.

Arvotyypit

Aikaisempi koodi sallii foreachin käytön, mutta jos haluat myös sallia LINQ: n käytön, rajapintojen toteuttamisesta ei päästä pois.

Huomaa rivillä 19, että puskuri on edelleen tallennettu kentäksi, mutta nyt tyyppiä Memory .

Muisti on Span -tehdas, joka voi sijaita kasassa. Sillä on Span-ominaisuus, joka luo uuden Span -ilmentymän, joka on voimassa kutsutussa laajuudessa. Sitä käytetään linjoilla 33 ja 45.

Enumerator ei voi nyt olla korjausrakenne, koska se toteuttaa käyttöliittymän. Jätän sen rakenteena, koska se toimii tässä tapauksessa paremmin. Liittymämenetelmälle soittaminen on suoritussakko, koska se on virtuaalipuhelu. Radat eivät salli perintöä, joten .NET pystyy optimoimaan nämä puhelut tekemällä niistä hieman nopeampia.

Ominaisuus Nykyinen palaa nyt Foo viitteen sijasta, mikä tarkoittaa, että muistikopio on olemassa. Voit lisätä ylikuormituksen, joka palauttaa viitteen, mutta jokainen puhelu, joka käyttää IEnumerator , käyttää nimenomaisesti toista.

Esitys

Kuinka nämä esimerkit toimivat? BenchmarkDotNet tekee kaikkien näiden skenaarioiden suorituskyvyn vertaamisen erittäin helpoksi.

Näiden vertailuarvojen koodi löytyy osoitteesta https://github.com/aalmada/SpanSample/blob/master/SpanSample/EnumerationBenchmarks.cs

Vertailuarvojen osalta laajensin ensimmäisen esimerkin kolmeen iteraation vaihtoehtoon puskurin Span <> avulla: käyttämällä ennakointia, käyttämällä GetEnumerator (): ta ja käyttämällä for for -piiriä indeksointioperaattorin kanssa. Mielenkiintoista nähdä, että esitelmillä on sama suorituskyky kuin nyt, mutta GetEnumeratorin () käyttö on kaksinkertainen.

For-silmukan käyttö puskurilla allokoidulla stackallocilla on tehokkainta. Se vie vähiten aikaa (1,6 ms) eikä varaa muistia kasaan. Se on asetettu vertailukohteeksi vertailun helpottamiseksi.

Ref struct -laskurin käyttö on hitaampaa 4,2 ms: lla (2,67 kertaa hitaampaa kuin raa'an stackalloc -laskennan laskeminen), mutta uudelleenkäytettävän ja helpommin ylläpidettävän koodin kanssa. Tämä on rangaistus siitä, että luetelulogiikka on jaettu kahteen funktioon.

IEnumerator -sovelluksen käyttö tekee siitä huomattavasti hitaamman 24,0 ms: lla (15,11 kertaa hitaampi kuin raa'an pinoarvon laskeminen). Tässä tapauksessa on sama rangaistus kuin aikaisemmin, plus rajapintojen käyttö, joka ei palauta arvoa vertailuna ja jolla ei ole yhtä span <> koko luettelossa.

Vaikka näitä ei ole esitetty tässä, mikä tahansa näistä ratkaisuista on paljon nopeampaa kuin ilman Span -sovellusta. Nämä arvot osoittavat, että sinun tulee harkita useita luetelma skenaarioita sovelluksissasi riippuen siitä, suositaanko joustavuutta tai suorituskykyä. Jos olet sovellusliittymäkehittäjä, sinun tulee paljastaa kaikki nämä, jotta käyttäjä voi tehdä oman valintansa.

johtopäätös

Span ja Muisti ovat uusia ominaisuuksia, jotka voivat vähentää dramaattisesti .NET-sovellusten muistikopioita mahdollistaen suorituskyvyn parantamisen tinkimättä tyyppiturvasta ja koodin luettavuudesta.

Voit ladata tämän artikkelin lähdekoodin ja suorittaa vertailukohdat omassa järjestelmässä.

Lisätietoja

Aion kirjoittaa vielä muutamia artikkeleita aiheesta, mutta löydät paljon lisätietoja näistä linkeistä:

  • Tervetuloa Mads Torgersenin julkaisuun C # 7.2 ja Span
  • C # - Kaikki Span: Tutustu uuteen .NET-tukikohtaan, kirjoittanut Stephen Toub
  • Span kirjoittanut Adam Sitnik
  • C # 7.2: Jared Parsonsin ymmärtämä span
  • Krzysztof Cwalina et al
  • Lisää alkuperäiset span / puskuripohjaiset sovellusliittymät corefx: iin, kirjoittanut Stephen Toub et ai