Når er structs svaret?

stemmer
30

Jeg gjør en raytracer hobbyprosjekt, og opprinnelig var jeg bruker structs for min Vector og Ray objekter, og jeg trodde en raytracer var den perfekte situasjonen til å bruke dem: du skape millioner av dem, de lever ikke lenger enn en enkelt metode, de er lette. Men ved å endre 'struct' til 'klasse' på Vector og Ray, fikk jeg en meget betydelig ytelsesforbedring.

Hva gir? De er både små (3 flyter for Vector, 2 vektorer for en Ray), ikke bli kopiert rundt overdrevet. Jeg passerer dem til metoder når det trengs selvfølgelig, men det er uunngåelig. Så hva er de vanligste fallgruvene som dreper ytelse når du bruker structs? Jeg har lest denne MSDN-artikkel som sier følgende:

Når du kjører dette eksempelet, vil du se at struct loop er størrelsesordener raskere. Det er imidlertid viktig å være oppmerksom på å bruke ValueTypes når du behandler dem som gjenstander. Dette gir ekstra boksing og unboxing overhead til programmet, og kan ende opp med å koste deg mer enn det ville gjort hvis du hadde stukket med objekter! For å vise dette i praksis, modifisere koden over til å bruke en oppstilling av foos og barer. Du vil finne at ytelsen er mer eller mindre like.

Det er imidlertid ganske gamle (2001) og hele å sette dem i en rekke årsaker boksing / unboxing slo meg som merkelig. Er det sant? Men jeg gjorde pre-beregne primære stråler og legg dem i en matrise, så jeg tok opp på denne artikkelen, og beregnet den primære ray når jeg trengte det, og aldri lagt dem til en matrise, men det gjorde ikke endre noe: med klasser, var det fortsatt 1,5 ganger raskere.

Jeg kjører .NET 3.5 SP1 som jeg tror løst et problem der struct metoder ikke var noen gang i foret, slik at ikke kan være det heller.

Så i utgangspunktet: noen tips, hva du bør tenke og hva du skal unngå?

EDIT: Som antydet i noen svar, har jeg satt opp et testprosjekt der jeg har prøvd bestått structs som ref. Metodene for å legge til to vektorer

public static VectorStruct Add(VectorStruct v1, VectorStruct v2)
{
  return new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

public static VectorStruct Add(ref VectorStruct v1, ref VectorStruct v2)
{
  return new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

public static void Add(ref VectorStruct v1, ref VectorStruct v2, out VectorStruct v3)
{
  v3 = new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

For hvert fikk jeg en variant av følgende referansemetode:

VectorStruct StructTest()
{
  Stopwatch sw = new Stopwatch();
  sw.Start();
  var v2 = new VectorStruct(0, 0, 0);
  for (int i = 0; i < 100000000; i++)
  {
    var v0 = new VectorStruct(i, i, i);
    var v1 = new VectorStruct(i, i, i);
    v2 = VectorStruct.Add(ref v0, ref v1);
  }
  sw.Stop();
  Console.WriteLine(sw.Elapsed.ToString());
  return v2; // To make sure v2 doesn't get optimized away because it's unused. 
}

Alt ser ut til å utføre ganske mye identiske. Er det mulig at de blir optimalisert ved JIT til hva som er den optimale måten å passere denne struct?

EDIT2: Jeg må merke seg forresten at det å bruke structs i min testprosjekt er ca 50% raskere enn å bruke en klasse. Hvorfor dette er annerledes for min raytracer jeg vet ikke.

Publisert på 28/02/2009 klokken 01:05
kilden bruker
På andre språk...                            


12 svar

stemmer
24

En rekke structs ville være et enkelt, sammenhengende struktur i minnet, mens elementene i en matrise av objekter (tilfeller av referanse typer) må adresseres individuelt av en peker (dvs. en referanse til et objekt på søppel-oppsamlet haug). Derfor, hvis du adressere store samlinger av elementer på en gang, vil structs gi deg en ytelsesgevinst, siden de trenger færre indirections. I tillegg kan structs ikke arves, som kan la kompilatoren for å foreta ytterligere optimaliseringer (men det er bare en mulighet, og avhenger av kompilatoren).

Men structs har ganske forskjellige oppdrag semantikk og kan heller ikke være arvelig. Derfor ville jeg vanligvis unngå structs med unntak for de gitte ytelsesgrunner når det trengs.


struct

En rekke verdier v kodet for av en struct (verditype) ser ut som dette i minne:

vvvv

klasse

En rekke verdier v kodet for av en klasse (referansetype) ut som dette:

pppp

..v..v ... vv.

hvor p er denne pekere, eller referanser som peker på faktiske verdier v på haugen. Prikkene viser andre gjenstander som kan være ispedd på haugen. I tilfellet med referanse typer trenger å referere v via det tilsvarende p, i tilfelle av verdityper kan få verdien direkte via dens forskyvning i matrisen.

Svarte 28/02/2009 kl. 15:46
kilden bruker

stemmer
10

I anbefalingene for når man skal bruke en struct står det at det ikke skal være større enn 16 byte. Din Vector er 12 byte, som er nær grensen. Ray har to vektorer, setter den på 24 bytes, som er tydelig over den anbefalte grensen.

Når en struct blir større enn 16 byte det ikke lenger kan kopieres effektivt med et enkelt sett med instruksjoner, i stedet en loop brukes. Så, ved å sende denne "magiske" grensen, er du faktisk gjør mye mer arbeid når du passerer en struct enn når du passerer en referanse til et objekt. Dette er grunnen til at koden er raskere med klasser eventhough det er mer overhead ved tildeling av objektene.

Vector kan fortsatt være en struct, men Ray er rett og slett for stor til å fungere godt som en struct.

Svarte 28/02/2009 kl. 04:04
kilden bruker

stemmer
9

Alt som er skrevet om boksing / unboxing før NET generiske legemidler kan tas med noe av en klype salt. Generiske samling typer har fjernet behovet for boksing og unboxing av verdityper, noe som gjør bruker structs i disse situasjonene mer verdifulle.

Som for din nedgang - vi vil sannsynligvis trenger å se noen kode.

Svarte 28/02/2009 kl. 01:12
kilden bruker

stemmer
6

Det første jeg vil se på, er å sørge for at du eksplisitt har implementert lik og GetHashCode. Unnlatelse av å gjøre dette betyr at kjøretiden gjennomføringen av hver av disse har noen svært dyre operasjoner for å sammenligne to struct forekomster (internt den bruker refleksjon for å bestemme hver av de private felt og deretter checkes dem for likestilling, dette fører til en betydelig mengde av allokering) .

Vanligvis skjønt, er det beste du kan gjøre er å kjøre koden under et profiler og se hvor de langsomme delene er. Det kan være en tankevekkende opplevelse.

Svarte 28/02/2009 kl. 03:12
kilden bruker

stemmer
6

Jeg tror nøkkelen ligger i disse to utsagnene fra innlegget ditt:

du skape millioner av dem

og

Jeg passerer dem til metoder når det trengs selvfølgelig

Nå med mindre din struct er mindre enn eller lik 4 byte i størrelse (eller 8 bytes hvis du er på et 64-bit system) du kopierer mye mer på hver metode samtale så hvis du bare gått et objekt referanse.

Svarte 28/02/2009 kl. 01:17
kilden bruker

stemmer
6

I utgangspunktet, ikke gjør dem for stor, og sende dem rundt med dommeren når du kan. Jeg oppdaget dette på nøyaktig samme måte ... Ved å endre min Vector og Ray klasser å structs.

Med mer minne som er gått rundt, er det bundet til å forårsake cache juling.

Svarte 28/02/2009 kl. 01:12
kilden bruker

stemmer
4

Har du profilert søknaden? Profilering er den eneste sikre måten å se hvor den faktiske ytelsen problemet er. Det er operasjoner som er generelt bedre / verre på structs men med mindre du profilen du vil bare være gjette på hva problemet er.

Svarte 28/02/2009 kl. 01:40
kilden bruker

stemmer
2

Mens funksjonaliteten er lik, strukturer er vanligvis mer effektivt enn klasser. Du bør definere en struktur, snarere enn en klasse, hvis typen vil gi bedre resultater som en verditype enn en referansetype.

Nærmere bestemt skal strukturtyper oppfyller alle disse kriterier:

  • Logisk betegner en enkeltverdi
  • Har en forekomst størrelse mindre enn 16 byte
  • Vil ikke bli endret etter etableringen
  • Vil ikke bli kastet til en referansetype
Svarte 17/04/2009 kl. 15:00
kilden bruker

stemmer
0

Min egen ray tracer bruker også struct vektorer (men ikke Rays) og endre Vector til klassen synes ikke å ha noen innvirkning på ytelsen. Jeg bruker tre dobbeltrom for vektoren så det kan være større enn det burde være. En ting å merke seg om, og dette kan være opplagt, men det var ikke for meg, og det er å kjøre programmet utenfor Visual Studio. Selv om du setter den til optimalisert utgivelse build kan du få en massiv fartsøkning hvis du starter exe utenfor VS. Enhver benchmarking du bør ta hensyn til dette.

Svarte 17/04/2009 kl. 10:13
kilden bruker

stemmer
0

Jeg bruker structs utgangspunktet for parameter gjenstander, tilbake flere opplysninger fra en funksjon, og ... ingenting annet. Vet ikke om det er "riktig" eller "galt", men det er det jeg gjør.

Svarte 28/02/2009 kl. 06:05
kilden bruker

stemmer
-1

Dersom structs er små, og ikke altfor mange eksisterer samtidig, bør det være å plassere dem på stabelen (så lenge det er en lokal variabel, og ikke et medlem av en klasse) og ikke på haugen, betyr dette at GC spiller' t må påberopes og hukommelse tildeling / fradeling bør være nesten momentant.

Ved passering av et struct som en parameter for å funksjon, blir den struct kopieres, noe som ikke bare betyr flere tildelinger / deallocations (fra stabelen, som er nesten momentant, men likevel har overhead), men den overliggende i bare overføring av data mellom 2 kopier . Hvis du passerer via referanse, er dette en ikke-sak som du bare forteller det hvor du skal lese data fra, snarere enn å kopiere det.

Jeg er ikke 100% sikker på dette, men jeg mistenker at retur arrays via en 'ut' parameter kan også gi deg en fartsøkning, som minne på stakken er reservert for det og trenger ikke å bli kopiert som stakken er "viklet" på slutten av funksjonskall.

Svarte 28/02/2009 kl. 16:14
kilden bruker

stemmer
-5

Du kan også gjøre structs inn kan ha nullverdier stedene. Egendefinerte klasser vil ikke være i stand til laget

som

Nullable<MyCustomClass> xxx = new Nullable<MyCustomClass>

der med en struct kan ha nullverdier

Nullable<MyCustomStruct> xxx = new Nullable<MyCustomStruct>

Men du vil være (selvsagt) å miste alle dine arve funksjoner

Svarte 28/02/2009 kl. 01:26
kilden bruker

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more