Vi ved alle, at CPU og hukommelse er de to vigtigste målinger for et program, så hvor mange har egentlig tænkt over spørgsmålet: Hvor mange bytes optager en instans af en type (værditype eller referencetype) i hukommelsen? Mange af os kan ikke svare. C# tilbyder nogle operatorer og API'er til at beregne størrelser, men ingen af dem løser helt det problem, jeg lige spurgte om. Denne artikel giver en metode til at beregne antallet af hukommelsesbytes, der er optaget af instanser af værdityper og referencetyper. Kildekoden downloades herfra.
1. størrelsen på operatoren 2. Marshal. SizeOf-metoden 3. Usikker. StørrelseOf-metoden > 4. Kan det beregnes ud fra typen af feltmedlem? 5. Layout af værdityper og applikationstyper 6. LDFLDA-direktivet 7. Beregn antallet af bytes af værditypen 8. Tæl antallet af bytes af citationstypen 9. Fuldstændig beregning
1. størrelsen på operatoren
Operationen sizeofs bruges til at bestemme antallet af bytes, der optages af en instans af en type, men den kan kun anvendes på uadministrerede typer. Den såkaldte Unmanaged type er begrænset til:
Primitive typer: Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double og Single) Decimaltype Opgørelsestype Pegertype Structs, der kun indeholder datamedlemmer af typen Unmanaged Som navnet antyder, er en Unmanaged type en værditype, og den tilsvarende instans kan ikke indeholde nogen reference til det administrerede objekt. Hvis vi definerer en generisk metode som denne til at kalde sizeof-operatoren, skal den generiske parameter T tilføje en umananget begrænsning og et unsafe-tag til metoden.
Kun native og enum-typer kan bruge sizeof-operatoren direkte, som skal tilføjes, hvis den anvendes på andre typer (pointere og brugerdefinerede strukturer)./unsafesamlingstags og skal også placeres iUsikreI kontekst.
Da den følgende struct Foobar ikke er en Unmanaged-type, vil programmet have en kompileringsfejl.
2. Marshal. SizeOf-metoden
Statiske typer Marshal definerer en række API'er, der hjælper os med at allokere og kopiere uadministreret hukommelse, konvertere mellem administrerede og uadministrerede typer samt udføre en række andre operationer på uadministreret hukommelse (Marshal i beregningsvidenskab refererer til operationen med at konvertere hukommelsesobjekter til det tilsvarende format til datalagring eller overførsel). Static, som inkluderer følgende 4 SizeOf-metoder, overloader for at bestemme antallet af bytes af en given type eller objekt.
Marshal.SizeOf-metoden har ikke en begrænsning på den specificerede type for Unmanaged-typen, men kræver stadig, at en sådan er angivetVærditype。 Hvis det indkommende objekt er et objekt, skal det også være en boks for en værditype.
Da følgende Foobar defineres som:slags, så kald til begge SizeOf-metoder vil kaste en ArgumentException-undtagelse og prompt: Type 'Foobar' kan ikke marshales som en unmanaged struktur; Ingen meningsfuld størrelse eller offset kan beregnes.
Marshal. SizeOf-metodenGenerika understøttes ikke, men har også krav til strukturens layout, som understøtter støtteSekventielogEksplicitLayout-tilstand. Da Foobar-strukturen nedenfor anvender Auto-layout-tilstanden (Auto, som ikke understøtter "dynamisk planlægning" af hukommelseslayout baseret på feltmedlemmer på grund af strengere krav til hukommelseslayout i uadministrerede miljøer), vil kald til SizeOf-metoden stadig kaste den samme ArgumentException-undtagelse som ovenfor.
3. Usikker.SizeOf-metoden
Static Unsafe giver flere lavniveauoperationer for uadministreret hukommelse, og lignende SizeIOf-metoder er også defineret i denne type. Metoden har ingen begrænsninger på den angivne type, men hvis du angiver en referencetype, returnerer denAntal pointerbytes"(IntPtr.Størrelse)。
4. Kan det beregnes ud fra typen af feltmedlem?
Vi ved, at både værdi- og referencetyper er afbildet som et kontinuert fragment (eller gemt direkte i et register). Formålet med en type er at specificere hukommelseslayoutet for et objekt, og instanser af samme type har samme layout, og antallet af bytes er naturligt det samme (for felter af referencetype gemmer den kun den refererede adresse i denne byte-sekvens). Da bytelængden bestemmes af typen, hvis vi kan bestemme typen af hvert feltmedlem, ville vi så ikke kunne beregne antallet af bytes svarende til den type? Faktisk er det ikke muligt.
For eksempel ved vi, at bytes for byte, short, int og long er 1, 2, 4 og 8, så antallet af bytes for en byte-binær er 2, men for en typekombination af byte + short, byte + int, og byte + long er de tilsvarende bytes ikke 3, 5 og 9, men 3, 8 og 16. Fordi dette involverer spørgsmålet om hukommelsesjustering.
5. Layout af værdityper og referencetyper
Antallet af bytes, der optages af instanser af referencetypen og undertypen, er også forskelligt for præcis det samme datamedlem. Som vist på det følgende billede, byte-sekvensen for værditype-instansenAlle er feltmedlemmer, der bruges til at opbevare det。 For instanser af referencetyper gemmes adressen til den type, der svarer til den tilsvarende metodetabell, også foran feltbytesekvensen. Metodetabellen leverer næsten alle metadata, der beskriver typen, og vi bruger denne reference til at afgøre, hvilken type instansen tilhører. Helt forrest er der også ekstra bytes, som vi kalderObjektoverskriftDen bruges ikke kun til at gemme objektets låste tilstand, men hashværdien kan også caches her. Når vi opretter en referencetypevariabel, er denne variabelDen peger ikke på den første byte hukommelse, som instansen optager, men på det sted, hvor metodetabeladressen er gemt。
6. LDFLDA-direktivet
Som vi har introduceret ovenfor, kan operatoren sizeof og SizeOf-metoden, som den statiske type Marshal/Unsafe giver, reelt ikke løse beregningen af bytelængden, som instanserne optager. Så vidt jeg ved, kan dette problem ikke løses alene i C#-feltet, men det leveres på IL-niveauLDFLDAInstruktioner kan hjælpe os med at løse dette problem. Som navnet antyder, står Ldflda for Load Field Address, som hjælper os med at få adressen på et felt i instansen. Da denne IL-instruktion ikke har noget tilsvarende API i C#, kan vi kun bruge den i følgende form ved hjælp af IL Emit.
Som vist i kodeudsnittet ovenfor har vi en GenerateFieldAddressAccessor-metode i typen SizeCalculator, som genererer en delegeret af typen Func<object?, long[]> baseret på listen over felter af den angivne type, hvilket hjælper os med at returnere hukommelsesadressen for det angivne objekt og alle dets felter. Med adressen på selve objektet og adressen på hvert felt kan vi naturligt få offset for hvert felt og derefter nemt beregne antallet af bytes hukommelse, som hele instansen optager.
7. Beregn antallet af bytes af værditypen
Da værdityper og referencetyper har forskellige layouts i hukommelsen, er vi også nødt til at bruge forskellige beregninger. Da byten af strukturen er indholdet af alle felterne i hukommelsen, bruger vi en smart måde at beregne den på. Antag, at vi skal fastsætte antallet af bytes for en struct af typen T, så opretter vi en ValueTuple<T,T> tuple, og offsetet af dens andet felt Item2 er antallet af bytes af struct T. Den specifikke beregningsmetode afspejles i følgende CalculateValueTypeInstance-metode.
Som vist i kodeudsnittet ovenfor, antager vi, at den struct-type, vi skal beregne, er T, og kalder GetDefaultAsObject-metoden for at få default(T)-objektet i form af en refleksion, og opretter derefter en ValueTuple<T,T>tuple. Efter at have kaldt GenerateFieldAddressAccessor-metoden for at hente Func<object?, long[]> delegat til at beregne instansen og dens feltadresser, kalder vi denne delegat som et argument. For de tre hukommelsesadresser, vi får, kodetupleen og adresserne på felterne 1 og 2 er de samme, vi bruger den tredje adresse, der repræsenterer Element2 minus den første adresse, og vi får det ønskede resultat.
8. Tæl antallet af bytes af citationstypen
Byteberegning for referencetyper er mere kompliceret med denne idé: efter vi har fået adressen på selve instansen og hvert felt, sorterer vi adresserne for at få offset fra det sidste felt. Lad os tilføje denne offset til antallet af bytes i det sidste felt selv, og derefter tilføje de nødvendige "første og sidste bytes" til det ønskede resultat, som afspejles i følgende CalculateReferneceTypeInstance-metode.
Som vist i kodeudsnittet ovenfor, hvis den specificerede type ikke har nogen felter defineret, returnerer CalculateReferneceTypeInstance det mindste antal bytes for referencetypeinstansen: 3 gange antallet af adressepegerbytes. For x86-arkitekturer optager et applikationstypeobjekt mindst 12 bytes, inklusive ObjectHeader (4 bytes), metodetabelpegere (bytes) og mindst 4 bytes feltindhold (disse 4 bytes er nødvendige, selvom ingen type er defineret uden felter). I tilfælde af x64-arkitektur vil dette minimumsantal bytes være 24, fordi metodetabellens peger og minimumsfeltindhold vil blive 8 bytes, selvom det gyldige indhold i ObjectHeader kun optager 4 bytes, men 4 bytes udfyldning vil blive tilføjet foran.
Afregningen af bytes, der er optaget af det sidste felt, er også meget enkel: hvis typen er en værditype, kaldes CalculateValueTypeInstance-metoden, der blev defineret tidligere, til at beregne; hvis det er en referencetype, er indholdet i feltet kun hukommelsesadressen for målobjektet, så længden er IntPtr.Size. Da referencetypeinstanser som standard er justeret med IntPtr.Size i hukommelsen, gøres dette også her. Endelig må du ikke glemme, at referencen for referencetype-instansen ikke peger på den første byte hukommelse, men på den byte, der gemmer metodetabel-pointeren, så du skal tilføje antallet af bytes i ObjecthHeader (IntPtr.Size).
9. Fuldstændig beregning
De to metoder, der bruges til at beregne antallet af bytes af værditype- og referencetypeinstanser, anvendes i følgende SizeOf-metode. Da kaldet af Ldflda-instruktionen skal angive en tilsvarende instans, giver denne metode en delegeret til at hente den tilsvarende instans ud over at angive måltypen. Parametrene, der svarer til denne delegerede, kan være standardindstillinger, og vi vil bruge standardværdien for værditypen. For referencetyper vil vi også forsøge at oprette målobjektet ved hjælp af standardkonstruktøren. Hvis dette delegerede objekt ikke er tilgængeligt, og målinstansen ikke kan oprettes, kaster SizeOf-metoden en undtagelse. Selvom vi skal angive målinstansen, er det beregnede resultat kun relateret til typen, så vi cacher det beregnede resultat. For nemheds skyld tilbyder vi også en anden generisk <T>SizeOf-metode.
I kodeudsnittet nedenfor bruger vi det til at outputte antallet af bytes for to strukturer og typer med samme feltdefinition. I den næste artikel vil vi yderligere få det fulde binære indhold af instansen i hukommelsen baseret på det beregnede antal bytes, så følg med.
Originalt link:Hyperlink-login er synlig. |