Všetci vieme, že CPU a pamäť sú dve najdôležitejšie metriky pre program, takže koľko ľudí naozaj premýšľalo nad otázkou: Koľko bajtov zaberá inštancia typu (hodnota alebo typ referencie) v pamäti? Mnohí z nás nevedia odpovedať. C# poskytuje niektoré operátory a API na výpočet veľkostí, ale žiadny z nich úplne nevyrieši problém, ktorý som práve položil. Tento článok poskytuje metódu na výpočet počtu pamäťových bajtov obsadených inštanciami typov hodnôt a typov referencií. Zdrojový kód sa sťahuje odtiaľto.
1. Veľkosť operátora 2. Metóda Marshal.SizeOf 3. Metóda Unsafe.SizeOf > 4. Dá sa vypočítať na základe typu terénneho člena? 5. Rozloženie typov hodnôt a typov aplikácií 6. Smernica LDFLDA 7. Vypočítajte počet bajtov hodnotového typu 8. Počítajte počet bajtov citačného typu 9. Kompletný výpočet
1. Veľkosť operátora
Veľkosť operácie sa používa na určenie počtu bajtov obsadených inštanciou typu, ale môže sa aplikovať iba na nespravované typy. Takzvaný Unmanaged typ je obmedzený na:
Primitívne typy: Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double a Single) Desatinný typ Typ enumerácie Typ ukazovateľa Štruktúry, ktoré obsahujú iba dátové členy typu Unmanaged Ako názov napovedá, Unmanaged typ je typ hodnoty a zodpovedajúca inštancia nemôže obsahovať žiadnu referenciu na spravovaný objekt. Ak definujeme generickú metódu ako túto na volanie operátora veľkosti, generický parameter T musí k metóde pridať nemanované obmedzenie a nebezpečný tag.
Iba natívne a enum typy môžu priamo používať operátor veľkosti, ktorý musí byť pridaný, ak sa aplikuje na iné typy (ukazovatele a vlastné štruktúry)./unsafekompilačné tagy, a tiež ich treba umiestniť doNebezpečnév kontexte.
Keďže nasledujúca štruktúra Foobar nie je Unmanaged typ, program bude mať chybu kompilácie.
2. Metóda Marshal.SizeOf
Statické typy Marshal definuje sériu API, ktoré nám pomáhajú alokovať a kopírovať nespravovanú pamäť, konvertovať medzi spravovanými a nespravovanými typmi a vykonávať sériu ďalších operácií na nespravovanej pamäti (Marshal vo výpočtovej vede označuje operáciu konverzie pamäťových objektov do zodpovedajúceho formátu pre ukladanie alebo prenos dát). Statické, ktoré zahŕňajú nasledujúce 4 preťaženia metódy SizeOf na určenie počtu bajtov daného typu alebo objektu.
Metóda Marshal.SizeOf nemá obmedzenie na špecifikovaný typ pre Unmanaged typ, ale stále vyžaduje, aby bol špecifikovanýTyp hodnoty。 Ak je prichádzajúci objekt objekt, musí to byť aj rámček pre typ hodnoty.
Keďže nasledujúci Foobar je definovaný ako:láskavý, takže volania oboch metód SizeOf vyhodia výnimku ArgumentException a výzvu: Typ 'Foobar' nemôže byť marshaled ako nespravovaná štruktúra; Nie je možné vypočítať žiadnu významnú veľkosť ani posun.
Metóda Marshal.SizeOfGeneriká nie sú podporované, ale má aj požiadavky na usporiadanie konštrukcie, ktorá podporujeSekvenčnýaExplicitnýRežim rozloženia. Keďže štruktúra Foobar zobrazená nižšie používa režim Auto layout (Auto, ktorý nepodporuje "dynamické plánovanie" rozloženia pamäte na základe členov poľa kvôli prísnejším požiadavkám na rozloženie pamäte v nespravovaných prostrediach), volania metódy SizeOf stále vyhodia rovnakú výnimku ArgumentException ako vyššie.
3. Metóda Unsafe.SizeOf
Static Unsafe poskytuje viac nízkoúrovňových operácií pre nespravovanú pamäť a podobné metódy SizeIOf sú tiež definované v tomto type. Metóda nemá žiadne obmedzenia na špecifikovaný typ, ale ak špecifikujete typ referencie, vrátiPočet bajtov ukazovateľa"(IntPtr.Size)。
4. Dá sa vypočítať na základe typu terénneho člena?
Vieme, že hodnoty aj referenčné typy sú mapované ako spojitý fragment (alebo uložené priamo v registri). Účelom typu je špecifikovať rozloženie pamäte objektu, pričom inštancie rovnakého typu majú rovnaké rozloženie a počet bajtov je prirodzene rovnaký (pre polia referenčného typu sa v tejto sekvencii bajtov uchováva iba referenčná adresa). Keďže dĺžka bajtu je určená typom, ak dokážeme určiť typ každého člena poľa, neboli by sme schopní vypočítať počet bajtov zodpovedajúcich danému typu? V skutočnosti to nie je možné.
Napríklad vieme, že bajty bajtov, short, int a long sú 1, 2, 4 a 8, takže počet bajtov pre bajtovú binárku je 2, ale pre typovú kombináciu bajt + short, bajt + int a bajt + long nie sú zodpovedajúce bajty 3, 5 a 9, ale 3, 8 a 16. Pretože to súvisí s otázkou zarovnania pamäte.
5. Rozloženie hodnôt a referenčných typov
Počet bajtov obsadených inštanciami referenčného typu a podtypu sa tiež líši pre presne toho istého dátového člena. Ako je znázornené na nasledujúcom obrázku, sekvencia bajtov inštancie typu hodnotyVšetci sú terénni pracovníci používaní na jeho skladovanie。 Pre inštancie referenčných typov je adresa tabuľky metód zodpovedajúcej typovej tiež uložená pred sekvenciou bajtov poľa. Tabuľka metód poskytuje takmer všetky metadáta popisujúce typ a túto referenciu používame na určenie, ku ktorému typu inštancia patrí. Na úplnom začiatku sú aj extra bajty, ktoré budeme nazývaťObjektová hlavičkaNepoužíva sa len na uloženie uzamknutého stavu objektu, ale hash hodnota môže byť tiež uložená v cache. Keď vytvoríme premennú typu referencie, táto premennáNeukazuje na prvý bajt pamäte obsadený inštanciou, ale na miesto, kde je uložená adresa tabuľky metód。
6. Smernica LDFLDA
Ako sme uviedli vyššie, operátor sizeof a metóda SizeOf poskytovaná statickým typom Marshal/Unsafe nemôžu skutočne vyriešiť výpočet dĺžky bajtu obsadeného inštanciami. Pokiaľ viem, tento problém sa nedá vyriešiť len v oblasti C#, ale je poskytovaný na úrovni ILLdfldaInštrukcie nám môžu pomôcť tento problém vyriešiť. Ako názov napovedá, Ldflda znamená Load Field Address, čo nám pomáha získať adresu poľa v danej inštancii. Keďže táto inštrukcia IL nemá v C# zodpovedajúce API, môžeme ju použiť iba v nasledujúcej forme pomocou IL Emit.
Ako je ukázané v úryvku kódu vyššie, máme metódu GenerateFieldAddressAccessor v type SizeCalculator, ktorá generuje delegáta typu Func<object?, long[]> na základe zoznamu polí daného typu, čo nám pomáha vrátiť pamäťovú adresu špecifikovaného objektu a všetkých jeho polí. S adresou samotného objektu a adresou každého poľa môžeme prirodzene získať posun každého poľa a potom ľahko vypočítať počet bajtov pamäte zaberaných celou inštanciou.
7. Vypočítajte počet bajtov hodnotového typu
Keďže typy hodnôt a typy referencií majú v pamäti odlišné usporiadanie, musíme použiť aj odlišné výpočty. Keďže bajt štruktúry je obsahom všetkých polí v pamäti, používame šikovný spôsob jeho výpočtu. Predpokladajme, že potrebujeme vyrovnať počet bajtov štruktúry typu T, vytvoríme n-ticu ValueTuple<T,T> a offset jej druhého poľa Item2 je počet bajtov štruktúry T. Konkrétna metóda výpočtu sa odráža v nasledujúcej metóde CalculateValueTypeInstance.
Ako je ukázané v úryvku kódu vyššie, za predpokladu, že typ štruktúry, ktorý potrebujeme vypočítať, je T, zavoláme metódu GetDefaultAsObject, aby sme získali predvolený(T) objekt vo forme reflexie, a potom vytvoríme ValueTuple<T,T>ticu. Po zavolaní metódy GenerateFieldAddressAccessor na získanie delegáta Func<object?, long[]> na výpočet inštancie a jej adries polí, nazývame tohto delegáta ako argument. Pre tri pamäťové adresy, ktoré dostaneme, sú kódová n-tica a adresy polí 1 a 2 rovnaké, použijeme tretiu adresu, ktorá reprezentuje Položku2 mínus prvú adresu, a dostaneme požadovaný výsledok.
8. Počítajte počet bajtov citačného typu
Výpočet bajtov pre typy referencií je zložitejší, využívajúc túto myšlienku: po získaní adresy samotnej inštancie a každého poľa zoradíme adresy, aby sme získali posun posledného poľa. Pridajme tento offset k počtu bajtov posledného poľa a potom pridajme potrebné "prvé a posledné bajty" k požadovanému výsledku, čo sa odráža v nasledujúcej metóde CalculateReferneceTypeInstance.
Ako je ukázané v úryvku kódu vyššie, ak špecifikovaný typ nemá definované žiadne polia, CalculateReferneceTypeInstance vráti minimálny počet bajtov inštancie referenčného typu: 3-násobok počtu bajtov ukazovateľa na adresu. Pre architektúry x86 zaberá objekt typu aplikácie aspoň 12 bajtov, vrátane ObjectHeader (4 bajty), ukazovateľov na tabuľku metód (bajtov) a aspoň 4 bajtov obsahu poľa (tieto 4 bajty sú potrebné aj v prípade, že žiadny typ nie je definovaný bez polí). V prípade architektúry x64 bude tento minimálny počet bajtov 24, pretože ukazovateľ tabuľky metód a minimálny obsah poľa budú mať 8 bajtov, hoci platný obsah ObjectHeader zaberá iba 4 bajty, ale pred nimi sa pridajú 4 bajty výplne.
Usporiadanie bajtov obsadených posledným poľom je tiež veľmi jednoduché: ak je typ hodnotový typ, potom sa volá metóda CalculateValueTypeInstance definovaná skôr na výpočt, ak ide o referenčný typ, obsah uložený v poli je iba pamäťová adresa cieľového objektu, takže dĺžka je IntPtr.Size. Keďže inštancie referenčných typov sú v pamäti predvolene zarovnané s IntPtr.Size, toto sa robí aj tu. Nakoniec nezabudnite, že referencia inštancie typu referencie neukazuje na prvý bajt pamäte, ale na bajt, ktorý uchováva ukazovateľ tabuľky metód, takže musíte pridať počet bajtov ObjecthHeader (IntPtr.Size).
9. Kompletný výpočet
Dve metódy použité na výpočet počtu bajtov inštancií typu hodnoty a referenčného typu sa používajú v nasledujúcej metóde SizeOf. Keďže volanie inštrukcie Ldflda musí poskytnúť zodpovedajúcu inštanciu, táto metóda poskytuje delegáta na získanie zodpovedajúcej inštancie okrem poskytnutia cieľového typu. Parametre zodpovedajúce tomuto delegátovi môžu byť predvolené a použijeme predvolenú hodnotu pre typ hodnoty. Pre typy referencií sa tiež pokúsime vytvoriť cieľový objekt pomocou predvoleného konštruktora. Ak tento objekt delegátu nie je poskytnutý a cieľová inštancia nemôže byť vytvorená, metóda SizeOf vyhodí výnimku. Aj keď potrebujeme poskytnúť cieľovú inštanciu, vypočítaný výsledok súvisí iba s typom, takže vypočítaný výsledok ukladame do vyrovnávacej pamäte. Pre jednoduchšie volanie poskytujeme aj ďalšiu generickú <T>metódu SizeOf.
V kódovom úryvku nižšie ho používame na výstup počtu bajtov dvoch štruktúr a typov s rovnakou definíciou poľa. V ďalšom článku sa dozvieme kompletný binárny obsah inštancie v pamäti na základe vypočítaného počtu bajtov, takže zostaňte naladení.
Pôvodný odkaz:Prihlásenie na hypertextový odkaz je viditeľné. |