Vi vet alla att CPU och minne är de två viktigaste måtten för ett program, så hur många har egentligen funderat på frågan: Hur många bytes upptar en instans av en typ (värdetyp eller referenstyp) i minnet? Många av oss kan inte svara. C# tillhandahåller några operatorer och API:er för att beräkna storlekar, men ingen av dem löser helt problemet jag just frågade. Den här artikeln ger en metod för att beräkna antalet minnesbyte som upptas av instanser av värdetyper och referenstyper. Källkoden laddas ner härifrån.
1. operatorstorleken 2. Marshal. SizeOf-metoden 3. Osäker. Storlek av metoden > 4. Kan det beräknas utifrån typen av fältmedlem? 5. Layout av värdetyper och applikationstyper 6. LDFLDA-direktivet 7. Beräkna antalet byte av värdetypen 8. Räkna antalet bytes av citeringstypen 9. Fullständig beräkning
1. operatorstorleken
Operationen sizeofs används för att bestämma antalet byte som upptas av en instans av en typ, men den kan endast tillämpas på ohanterade typer. Den så kallade Ohanterade typen är begränsad till:
Primitiva typer: Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double och Single) Decimaltyp Uppräkningstyp Pekartyp Structs som endast innehåller datamedlemmar av typen Utan hantering Som namnet antyder är en Unmanaged-typ en värdetyp, och motsvarande instans kan inte innehålla någon referens till det hanterade objektet. Om vi definierar en generisk metod som denna för att anropa sizeof-operatorn, måste den generiska parametern T lägga till en omanangad begränsning och en unsafe-tagg till metoden.
Endast native- och enum-typer kan använda operatorn sizeof direkt, vilket måste läggas till om det tillämpas på andra typer (pekare och egna strukturer)./unsafesammanställningstaggar, och måste också placeras iosäkerI sitt sammanhang.
Eftersom följande struct Foobar inte är en Unmanaged-typ kommer programmet att ha ett kompileringsfel.
2. Marshal. SizeOf-metoden
Statiska typer Marshal definierar en serie API:er som hjälper oss att allokera och kopiera ohanterat minne, konvertera mellan hanterade och ohanterade typer samt utföra en serie andra operationer på ohanterat minne (Marshal inom beräkningsvetenskap syftar på att konvertera minnesobjekt till motsvarande format för datalagring eller överföring). Statisk, som inkluderar följande 4 SizeOf-metod, överbelastar för att bestämma antalet byte av en given typ eller objekt.
Marshal.SizeOf-metoden har ingen begränsning på den specificerade typen för Unmanaged-typen, men kräver ändå att en sådan specificerasVärdetyp。 Om det inkommande objektet är ett objekt måste det också vara en ruta för en värdetyp.
Eftersom följande Foobar definieras som:sort, så anrop till båda SizeOf-metoderna kommer att kasta ett ArgumentException-undantag och prompt: Typen 'Foobar' kan inte marshalas som en ohanterad struktur; ingen meningsfull storlek eller offset kan beräknas.
Marshal.SizeOf-metodenGeneriska läkemedel stöds inte, men har också krav på strukturens utformning, vilket stödjer stödSekventiellaochExplicitLayoutläge. Eftersom Foobar-strukturen nedan antar Auto-layoutläget (Auto, som inte stöder "dynamisk planering" av minneslayout baserat på fältmedlemmar på grund av striktare minneslayoutkrav i ohanterade miljöer), kommer anrop till SizeOf-metoden fortfarande att kasta samma ArgumentException-undantag som ovan.
3. Osäker.SizeOf-metoden
Static Unsafe ger fler lågnivåoperationer för ohanterat minne, och liknande SizeIOf-metoder definieras också i denna typ. Metoden har inga begränsningar på den angivna typen, men om du anger en referenstyp returnerar denAntal pekarbytes"(IntPtr.Size)。
4. Kan det beräknas utifrån typen av fältmedlem?
Vi vet att både värde- och referenstyper mappas som ett kontinuerligt fragment (eller lagras direkt i ett register). Syftet med en typ är att specificera minneslayouten för ett objekt, och instanser av samma typ har samma layout och antalet bytes är naturligt detsamma (för referenstypfält lagrar den endast den refererade adressen i denna bytesekvens). Eftersom bytelängd bestäms av typ, om vi kan bestämma typen för varje fältmedlem, skulle vi då inte kunna räkna ut antalet byte som motsvarar den typen? Faktum är att det inte är möjligt.
Till exempel vet vi att bytena av byte, short, int och long är 1, 2, 4 och 8, så antalet bytes för en bytebinär är 2, men för en typkombination av byte + short, byte + int, och byte + long är motsvarande byte inte 3, 5 och 9, utan 3, 8 och 16. För detta handlar om minnesjustering.
5. Layout av värdetyper och referenstyper
Antalet byte som upptas av instanser av referenstypen och subtypen skiljer sig också för exakt samma datamedlem. Som visas i följande bild är bytesekvensen för värdetypinstansenAlla är fältmedlemmar som används för att lagra den。 För instanser av referenstyper lagras adressen till tabellen motsvarande metod framför fältbytesekvensen. Metodtabellen tillhandahåller nästan all metadata som beskriver typen, och vi använder denna referens för att avgöra vilken typ instansen tillhör. Längst fram finns också extra bytes, som vi kommer att kallaObjekthuvudDen används inte bara för att lagra objektets låsta tillstånd, utan hashvärdet kan också cachelagras här. När vi skapar en referenstypvariabel, är denna variabelDen pekar inte på den första byte minne som upptas av instansen, utan på platsen där adressen till metodtabellen lagras。
6. LDFLDA-direktivet
Som vi har introducerat ovan kan operatorn sizeof och SizeOf-metoden som tillhandahålls av den statiska typen Marshal/Unsafe egentligen inte lösa beräkningen av bytelängd som upptas av instanser. Såvitt jag vet kan detta problem inte lösas enbart i C#-fältet, men det tillhandahålls på IL-nivåLdfldaInstruktioner kan hjälpa oss att lösa detta problem. Som namnet antyder står Ldflda för Load Field Address, vilket hjälper oss att få adressen till ett fält i instansen. Eftersom denna IL-instruktion inte har något motsvarande API i C# kan vi endast använda den i följande form med IL Emit.
Som visas i kodbiten ovan har vi en GenerateFieldAddressAccessor-metod i typen SizeCalculator, som genererar en delegat av typen Func<object?, long[]> baserat på listan av fält av den angivna typen, vilket hjälper oss att returnera minnesadressen till det angivna objektet och alla dess fält. Med objektets adress och adressen till varje fält kan vi naturligt få offsetet för varje fält, och sedan enkelt beräkna antalet bytes minne som hela instansen upptar.
7. Beräkna antalet byte av värdetypen
Eftersom värdetyper och referenstyper har olika layouter i minnet behöver vi också använda olika beräkningar. Eftersom bytet i strukturen är innehållet i alla fält i minnet använder vi ett smart sätt att beräkna det. Antag att vi behöver fastställa antalet byte för en struktur av typen T, sedan skapar vi en ValueTuple<T,T> tuple, och offsetet för dess andra fält Item2 är antalet bytes för struktur T. Den specifika beräkningsmetoden återspeglas i följande CalculateValueTypeInstance-metod.
Som visas i kodutsnittet ovan, förutsatt att strukturtypen vi behöver beräkna är T, anropar vi metoden GetDefaultAsObject för att hämta default(T)-objektet i form av en reflektion, och skapar sedan en ValueTuple<T,T>tuple. Efter att ha anropat metoden GenerateFieldAddressAccessor för att få Func<object?, long[]> delegat för att beräkna instansen och dess fältadresser, kallar vi denna delegat som ett argument. För de tre minnesadresserna vi får, kodtuplen och adresserna till fält 1 och 2 är desamma, vi använder den tredje adressen som representerar Item2 minus den första adressen, och vi får det resultat vi vill ha.
8. Räkna antalet bytes av citeringstypen
Byteberäkning för referenstyper är mer komplicerad, med denna idé: efter att vi fått adressen till själva instansen och varje fält, sorterar vi adresserna för att få offsetet för det senaste fältet. Låt oss lägga till denna offset till antalet bytes i det sista fältet självt, och sedan lägga till nödvändiga "första och sista bytes" till det resultat vi vill ha, vilket återspeglas i följande CalculateReferneceTypeInstance-metod.
Som visas i kodutsnittet ovan, om den angivna typen inte har några definierade fält, returnerar CalculateReferneceTypeInstance det minsta antalet bytes för referenstypinstansen: 3 gånger antalet adresspekarbytes. För x86-arkitekturer tar ett applikationstypobjekt upp minst 12 byte, inklusive ObjectHeader (4 byte), metodtabellpekare (byte) och minst 4 byte fältinnehåll (dessa 4 byte krävs även om ingen typ definieras utan fält). I fallet med x64-arkitekturen kommer detta minsta antal byte att vara 24, eftersom metodtabellpekaren och minsta fältinnehåll blir 8 byte, även om det giltiga innehållet i ObjectHeader bara upptar 4 byte, men 4 byte utfyllnad läggs till framför.
Fördelningen av bytes som upptas av det sista fältet är också mycket enkel: om typen är en värdetyp, så anropas CalculateValueTypeInstance-metoden som definierades tidigare för att beräkna, om det är en referenstyp är innehållet som lagras i fältet endast minnesadressen till målobjektet, så längden är IntPtr.Size. Eftersom referenstypinstanser är justerade med IntPtr.Size i minnet som standard, görs detta också här. Slutligen, glöm inte att referensen för referenstypinstansen inte pekar på den första minnesbytet, utan på byten som lagrar metodtabellpekaren, så du måste lägga till antalet byte i ObjecthHeader (IntPtr.Size).
9. Fullständig beräkning
De två metoderna som används för att beräkna antalet bytes av värdetyp- och referenstypinstanser används i följande SizeOf-metod. Eftersom anropet av Ldflda-instruktionen måste tillhandahålla en motsvarande instans, tillhandahåller denna metod en delegat för att erhålla motsvarande instans utöver att tillhandahålla måltypen. Parametrarna som motsvarar denna delegat kan vara standardjusterade, och vi kommer att använda standardvärdet för värdetypen. För referenstyper försöker vi också skapa målobjektet med standardkonstruktorn. Om detta delegatobjekt inte tillhandahålls och målinstansen inte kan skapas, kastar SizeOf-metoden ett undantag. Även om vi behöver tillhandahålla målinstansen, är det beräknade resultatet endast relaterat till typen, så vi cachar det beräknade resultatet. För enkelhetens uppringning erbjuder vi även en annan generell <T>SizeOf-metod.
I kodutsnittet nedan använder vi det för att ge ut antalet byte för två strukturer och typer med samma fältdefinition. I nästa artikel kommer vi att få hela binärt innehåll av instansen i minnet baserat på det beräknade antalet bytes, så håll utkik.
Originallänk:Inloggningen med hyperlänken är synlig. |