Dit artikel is een spiegelartikel van machinevertaling, klik hier om naar het oorspronkelijke artikel te gaan.

Bekijken: 537|Antwoord: 1

[Bron] [Draai]. Hoe berekent NET/C# hoeveel geheugen een instantie in beslag neemt?

[Link kopiëren]
Geplaatst op 27-8-2025 09:00:22 | | | |
We weten allemaal dat CPU en geheugen de twee belangrijkste metrics voor een programma zijn, dus hoeveel mensen hebben er eigenlijk over nagedacht: Hoeveel bytes neemt een instantie van een type (waardetype of referentietype) in het geheugen in? Velen van ons kunnen daar geen antwoord op geven. C# biedt enkele operatoren en API's om de grootte te berekenen, maar geen van hen lost het probleem dat ik net stelde volledig op. Dit artikel biedt een methode om het aantal geheugenbytes te berekenen dat wordt ingenomen door instanties van waardetypes en referentietypes. De broncode wordt hier gedownload.

1. grootte van operator
2. Marshal.SizeOf-methode
3. Onveilig. SizeOf-methode >
4. Kan het worden berekend op basis van het type veldlid?
5. Indeling van waardetypes en applicatietypen
6. LDFLDA-richtlijn
7. Bereken het aantal bytes van het waardetype
8. Tel het aantal bytes van het citatietype
9. Volledige berekening

1. grootte van operator

De sizeof-operatie wordt gebruikt om het aantal bytes te bepalen dat door een instantie van een type wordt ingenomen, maar kan alleen worden toegepast op Unmanaged types. Het zogenaamde Unmanaged type is beperkt tot:

Primitieve types: Booleaan, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double en Single)
Decimale letters
Enumeratietype
Pointertype
Structs die alleen dataleden van het type Onbeheerd bevatten
Zoals de naam al aangeeft, is een Unmanaged type een waardetype, en de bijbehorende instantie kan geen enkele verwijzing naar het beheerde object bevatten. Als we een generieke methode als deze definiëren om de sizeof-operator aan te roepen, moet de generieke parameter T een onbeheerde constraint en een unsafe-tag aan de methode toevoegen.

Alleen native en enum-types kunnen de sizeof-operator direct gebruiken, die moet worden toegevoegd als deze wordt toegepast op andere types (pointers en custom structs)./unsafecompilatietags, en moeten ook worden geplaatst inonveiligIn de context.

Aangezien de volgende struct Foobar geen Unmanaged type is, zal het programma een compilatiefout hebben.

2. Marshal.SizeOf-methode

Statische types Marshal definieert een reeks API's die ons helpen onbeheerd geheugen toe te wijzen en te kopiëren, om te zetten tussen beheerde en onbeheerde types, en een reeks andere bewerkingen uit te voeren op onbeheerd geheugen (Marshal in de computationele wetenschap verwijst naar de operatie van het omzetten van geheugenobjecten naar het bijbehorende formaat voor gegevensopslag of -overdracht). Static, dat de volgende 4 SizeOf-methode omvat, overlaadt om het aantal bytes van een bepaald type of object te bepalen.

De Marshal.SizeOf-methode heeft geen beperking op het opgegeven type voor het Unmanaged type, maar vereist wel dat er een wordt gespecificeerdWaardetype。 Als het binnenkomende object een object is, moet het ook een box zijn voor een waardetype.

Aangezien de volgende Foobar wordt gedefinieerd als:soort, dus aanroepen van beide SizeOf-methoden zullen een ArgumentException-uitzondering en prompt geven: Type 'Foobar' kan niet worden gemarshaled als een niet-beheerde structuur; er kan geen betekenisvolle grootte of offset worden berekend.

Marshal.SizeOf-methodeGenerieke middelen worden niet ondersteund, maar heeft ook eisen voor de indeling van de constructie, die ondersteuning ondersteuntSequentieelenUitdrukkelijkLayoutmodus. Aangezien de hieronder getoonde Foobar-structuur de Auto-layoutmodus aanneemt (Auto, die geen "dynamische planning" van geheugenindeling ondersteunt op basis van veldleden vanwege de strengere geheugenindelingseisen in onbeheerde omgevingen), zullen aanroepen naar de SizeOf-methode nog steeds dezelfde ArgumentException-uitzondering als hierboven gooien.

3. Onveilige. SizeOf-methode

Static Unsafe biedt meer laagniveau-operaties voor onbeheerd geheugen, en vergelijkbare SizeIOf-methoden zijn ook gedefinieerd in dit type. De methode heeft geen beperkingen op het gespecificeerde type, maar als je een referentietype opgeeft, geeft hij deAantal pointerbytes"(IntPtr.Size)。

4. Kan het worden berekend op basis van het type veldlid?

We weten dat zowel waarde- als referentietypen worden gemapt als een continu fragment (of direct opgeslagen in een register). Het doel van een type is om de geheugenindeling van een object te specificeren, en instanties van hetzelfde type hebben dezelfde lay-out en het aantal bytes is natuurlijk gelijk (voor velden van referentietype slaat het alleen het gerefereerde adres op in deze bytereeks). Aangezien bytelengte wordt bepaald door type, als we het type van elk veldlid kunnen bepalen, zouden we dan niet het aantal bytes kunnen berekenen dat bij dat type hoort? In feite is het niet mogelijk.

We weten bijvoorbeeld dat de bytes byte, short, int en long 1, 2, 4 en 8 zijn, dus het aantal bytes voor een bytebinaire is 2, maar voor een typecombinatie van byte + short, byte + int, en byte + long zijn de overeenkomstige bytes niet 3, 5 en 9, maar 3, 8 en 16. Omdat dit het probleem van geheugenuitlijning betreft.

5. Indeling van waardetypes en referentietypen

Het aantal bytes dat wordt ingenomen door instanties van het referentietype en subtype verschilt ook voor exact hetzelfde datalid. Zoals getoond in de volgende afbeelding, is de bytereeks van de waardetype-instantieAllen zijn veldleden die worden gebruikt om het op te slaan。 Voor instanties van referentietypen wordt het adres van de type overeenkomstige methodetabel ook opgeslagen vóór de veldbytereeks. De methodetabel bevat bijna alle metadata die het type beschrijft, en we gebruiken deze referentie om te bepalen tot welk type de instantie behoort. Helemaal vooraan staan ook extra bytes, die we zullen noemenObjectkopHet wordt niet alleen gebruikt om de vergrendelde toestand van het object op te slaan, maar de hashwaarde kan hier ook worden gecachet. Wanneer we een referentietypevariabele creëren, wordt deze variabeleHet wijst niet naar de eerste byte geheugen die door de instantie wordt gebruikt, maar naar de plek waar het adres van de methodetabel is opgeslagen



6. LDFLDA-richtlijn

Zoals we hierboven hebben geïntroduceerd, kunnen de sizeof-operator en de SizeOf-methode die door het statische type Marshal/Unsafe worden geleverd, de berekening van bytelengte die door instanties wordt ingenomen, niet echt oplossen. Voor zover ik weet kan dit probleem niet alleen in het C#-veld worden opgelost, maar het wordt wel op IL-niveau aangebodenLDFLDAInstructies kunnen ons helpen dit probleem op te lossen. Zoals de naam al doet vermoeden, staat Ldflda voor Load Field Address, wat ons helpt het adres van een veld in de instantie te krijgen. Aangezien deze IL-instructie geen bijbehorende API in C# heeft, kunnen we deze alleen in de volgende vorm gebruiken met IL Emit.

Zoals te zien is in het codefragment hierboven, hebben we een GenerateFieldAddressAccessor-methode in het type SizeCalculator, die een gedelegeerde van het type Func<object?, long[]>genereert, gebaseerd op de lijst van velden van het opgegeven type, wat ons helpt het geheugenadres van het opgegeven object en alle velden terug te geven. Met het adres van het object zelf en het adres van elk veld kunnen we natuurlijk de offset van elk veld krijgen en vervolgens eenvoudig het aantal bytes geheugen berekenen dat door de hele instantie wordt ingenomen.

7. Bereken het aantal bytes van het waardetype

Omdat waardetypes en referentietypes verschillende indelingen in het geheugen hebben, moeten we ook verschillende berekeningen gebruiken. Aangezien de byte van de struct de inhoud is van alle velden in het geheugen, gebruiken we een slimme manier om het te berekenen. Stel dat we het aantal bytes van een struct van type T moeten vaststellen, dan maken we een ValueTuple<T,T> tuple, en de offset van het tweede veld Item2 is het aantal bytes van struct T. De specifieke berekeningsmethode wordt weerspiegeld in de volgende CalculateValueTypeInstance-methode.

Zoals te zien is in het codefragment hierboven, ervan uitgaande dat het structtype dat we moeten berekenen T is, roepen we de GetDefaultAsObject-methode aan om het default(T)-object in de vorm van een reflectie te krijgen, en maken we vervolgens een ValueTuple<T,T>tuple. Na het aanroepen van de GenerateFieldAddressAccessor-methode om de Func<object?, long[]> delegate te krijgen voor het berekenen van de instantie en de veldadressen, roepen we deze delegate als een argument aan. Voor de drie geheugenadressen die we krijgen, zijn de codetuple en de adressen van velden 1 en 2 hetzelfde; we gebruiken het derde adres dat Item2 vertegenwoordigt minus het eerste adres, en krijgen we het gewenste resultaat.

8. Tel het aantal bytes van het citatietype

Byte-berekening voor referentietypen is ingewikkelder, met dit idee: nadat we het adres van de instantie zelf en elk veld hebben gekregen, sorteren we de adressen om de offset van het laatste veld te krijgen. Laten we deze offset toevoegen aan het aantal bytes van het laatste veld zelf, en vervolgens de benodigde "eerste en laatste bytes" toevoegen aan het gewenste resultaat, wat wordt weerspiegeld in de volgende CalculateReferneceTypeInstance-methode.

Zoals getoond in het codefragment hierboven, als het gespecificeerde type geen velden heeft gedefinieerd, geeft CalculateReferneceTypeInstance het minimale aantal bytes van de referentietype-instantie terug: 3 keer het aantal adrespointerbytes. Voor x86-architecturen neemt een applicatietype-object minstens 12 bytes in beslag, inclusief ObjectHeader (4 bytes), methodetabellenpointers (bytes) en minstens 4 bytes aan veldinhoud (deze 4 bytes zijn vereist, zelfs als er geen type zonder velden is gedefinieerd). In het geval van x64-architectuur zal dit minimale aantal bytes 24 zijn, omdat de methode-tabel pointer en minimale veldinhoud 8 bytes worden, hoewel de geldige inhoud van de ObjectHeader slechts 4 bytes beslaat, maar 4 bytes aan opvulling eraan worden toegevoegd.

De verdeling van bytes die door het laatste veld worden ingenomen is ook heel eenvoudig: als het type een waardetype is, wordt de eerder gedefinieerde CalculateValueTypeInstance-methode aangeroepen om te berekenen; als het een referentietype is, is de inhoud in het veld alleen het geheugenadres van het doelobject, dus de lengte is IntPtr.Size. Omdat referentietype-instanties standaard in het geheugen zijn uitgelijnd met IntPtr.Size, wordt dit hier ook gedaan. Vergeet ten slotte niet dat de referentie van het referentietype exemplaar niet naar de eerste byte geheugen wijst, maar naar de byte die de methodetafelpointer opslaat, dus je moet het aantal bytes van ObjecthHeader (IntPtr.Size) optellen.

9. Volledige berekening

De twee methoden die worden gebruikt om het aantal bytes van waardetype- en referentietype-instanties te berekenen, worden gebruikt in de volgende SizeOf-methode. Aangezien de aanroep van de Ldflda-instructie een bijbehorende instantie moet leveren, biedt deze methode een gedelegeerde om naast het doeltype ook de bijbehorende instantie te verkrijgen. De parameters die bij deze delegat horen, kunnen standaard worden ingesteld, en we gebruiken de standaardwaarde voor het waardetype. Voor referentietypes proberen we ook het doelobject te maken met de standaard constructor. Als dit delegate-object niet wordt verstrekt en de doelinstantie niet kan worden aangemaakt, gooit de SizeOf-methode een uitzondering. Hoewel we de doelinstantie moeten aangeven, is het berekende resultaat alleen gerelateerd aan het type, dus cachen we het berekende resultaat. Voor het gemak bieden we ook een andere generieke SizeOf-methode aan<T>.

In het onderstaande codefragment gebruiken we het om het aantal bytes van twee structs en types met dezelfde velddefinitie uit te geven. In het volgende artikel krijgen we verder de volledige binaire inhoud van de instantie in het geheugen op basis van het berekende aantal bytes, dus blijf op de hoogte.

Originele link:De hyperlink-login is zichtbaar.




Vorig:Het front-end framework leert het Component-Party open source project
Volgend:MinIO-opslag (iii) Kopieer-upload (migreer) lokale bestanden naar de minio-bucket
 Huisbaas| Geplaatst op 27-8-2025 09:33:22 |
De C#-collectie voegt 10.000 gegevensstukken in, die het geheugen in beslag nemen




Code:


Disclaimer:
Alle software, programmeermaterialen of artikelen die door Code Farmer Network worden gepubliceerd, zijn uitsluitend bedoeld voor leer- en onderzoeksdoeleinden; De bovenstaande inhoud mag niet worden gebruikt voor commerciële of illegale doeleinden, anders dragen gebruikers alle gevolgen. De informatie op deze site komt van het internet, en auteursrechtconflicten hebben niets met deze site te maken. Je moet bovenstaande inhoud volledig van je computer verwijderen binnen 24 uur na het downloaden. Als je het programma leuk vindt, steun dan de echte software, koop registratie en krijg betere echte diensten. Als er sprake is van een inbreuk, neem dan contact met ons op via e-mail.

Mail To:help@itsvse.com