Všichni víme, že CPU a paměť jsou dvě nejdůležitější metriky pro program, takže kolik lidí opravdu přemýšlelo nad otázkou: Kolik bajtů zabírá instance typu (hodnota, typ nebo typ reference) v paměti? Mnozí z nás na to nedokážou odpovědět. C# poskytuje některé operátory a API pro výpočet velikostí, ale žádný z nich úplně nevyřeší problém, na který jsem se právě ptal. Tento článek poskytuje metodu pro výpočet počtu paměťových bajtů obsazených instancemi typů hodnot a typů referencí. Zdrojový kód je zde stažen.
1. Velikost operátora 2. Metoda Marshal.SizeOf 3. Metoda Unsafe.SizeOf > 4. Lze ji vypočítat na základě typu polního člena? 5. Rozložení typů hodnot a typů aplikací 6. Směrnice LDFLDA 7. Spočítejte počet bajtů typu hodnoty 8. Spočítejte počet bajtů citačního typu 9. Kompletní výpočet
1. Velikost operátora
Velikost operace se používá k určení počtu bajtů obsazených instancí typu, ale může být aplikována pouze na nespravované typy. Takzvaný Unmanaged typ je omezen na:
Primitivní typy: Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double a Single) Desetinný typ Typ enumerace Typ ukazovátka Struktury, které obsahují pouze datové členy typu Unmanaged Jak název napovídá, Unmanaged typ je typ hodnoty a odpovídající instance nemůže obsahovat žádnou referenci na spravovaný objekt. Pokud definujeme generickou metodu jako tuto pro volání operátoru velikosti, generický parametr T musí k metodě přidat nemanagované omezení a nebezpečný tag.
Pouze nativní a enum typy mohou přímo používat operátor velikosti, který je třeba přidat, pokud je aplikován na jiné typy (ukazatele a vlastní struktury)./unsafekompilačních tagů, a také je potřeba je umístit donebezpečnýv kontextu.
Protože následující struktura Foobar není typu Unmanaged, program bude mít chybu při kompilaci.
2. Metoda Marshal.SizeOf
Statické typy Marshal definuje řadu API, která nám pomáhají alokovat a kopírovat nespravovanou paměť, převádět mezi spravovanými a nespravovanými typy a provádět řadu dalších operací na nespravované paměti (Marshal v výpočetní vědě označuje operaci převodu paměťových objektů do odpovídajícího formátu pro ukládání nebo přenos dat). Statické, které zahrnuje následující 4 přetížení metody SizeOf pro určení počtu bajtů daného typu nebo objektu.
Metoda Marshal.SizeOf nemá omezení na specifikovaný typ pro Unmanaged typ, ale stále vyžaduje, aby byl jeden specifikovánTyp hodnoty。 Pokud je příchozí objekt objektem, musí být také rámečkem pro typ hodnoty.
Protože následující Foobar je definován jako:laskavý, takže volání obou metod SizeOf vyhodí výjimku ArgumentException a výzvu: Typ 'Foobar' nelze marshalovat jako nespravovanou strukturu; nelze vypočítat žádnou smysluplnou velikost ani posun.
Metoda Marshal.SizeOfGenerika nejsou podporována, ale má také požadavky na uspořádání konstrukce, která podporuje podporuSekvenčníaExplicitníRežim rozvržení. Protože struktura Foobar zobrazená níže používá režim Auto layout (Auto, který nepodporuje "dynamické plánování" rozložení paměti na základě členů pole kvůli přísnějším požadavkům na rozložení paměti v nespravovaných prostředích), volání metody SizeOf stále vyhodí stejnou výjimku ArgumentException jako výše.
3. Metoda Unsafe.SizeOf
Static Unsafe poskytuje více nízkoúrovňových operací pro nespravovanou paměť a podobné metody SizeIOf jsou také definovány v tomto typu. Metoda nemá žádná omezení na specifikovaný typ, ale pokud zadáte referenční typ, vrátíPočet ukazatelových bajtů"(IntPtr.Size)。
4. Lze ji vypočítat na základě typu polního člena?
Víme, že jak hodnotové, tak referenční typy jsou mapovány jako spojitý fragment (nebo uloženy přímo v registru). Účelem typu je specifikovat rozložení paměti objektu a instance stejného typu mají stejné rozložení a počet bajtů je přirozeně stejný (u polí referenčního typu se v této posloupnosti bajtů ukládá pouze odkazovaná adresa). Protože délka bajtu je určena typem, pokud dokážeme určit typ každého člena pole, nebyli bychom schopni vypočítat počet bajtů odpovídajících tomuto typu? Ve skutečnosti to není možné.
Například víme, že bajty bajtů, short, int a long jsou 1, 2, 4 a 8, takže počet bajtů pro bajtovou binární jednotku je 2, ale pro typovou kombinaci bajt + short, bajt + int a bajt + long nejsou odpovídající bajty 3, 5 a 9, ale 3, 8 a 16. Protože to zahrnuje otázku zarovnání paměti.
5. Rozvržení typů hodnot a typů referencí
Počet bajtů obsazených instancemi referenčního typu a podtypu se také liší u přesně stejného datového člena. Jak je ukázáno na následujícím obrázku, sekvence bajtů instance typu hodnotyVšichni jsou členové terénu používaní k jeho skladování。 Pro instance referenčních typů je adresa tabulky metod odpovídající typu také uložena před sekvencí bajtů polí. Tabulka metod poskytuje téměř všechna metadata popisující typ a používáme tuto referenci k určení, ke kterému typu instance patří. Na úplném začátku jsou také extra bajty, které nazvemeObjektová hlavičkaPoužívá se nejen k ukládání zamčeného stavu objektu, ale hash hodnota může být také uložena zde. Když vytvoříme proměnnou typu reference, tato proměnnáNeukazuje na první bajt paměti obsazený instancí, ale na místo, kde je uložena adresa tabulky metod。
6. Směrnice LDFLDA
Jak jsme uvedli výše, operátor sizeof a metoda SizeOf poskytovaná statickým typem Marshal/Unsafe nemohou skutečně vyřešit výpočet délky bajtu obsazeného instancemi. Pokud vím, tento problém nelze vyřešit pouze v oblasti C#, ale je poskytován na úrovni ILLDFLDAInstrukce nám mohou pomoci tento problém vyřešit. Jak název napovídá, Ldflda znamená Load Field Address, což nám pomáhá získat adresu pole v instanci. Protože tato instrukce IL nemá odpovídající API v C#, můžeme ji použít pouze v následující podobě pomocí IL Emit.
Jak je ukázáno v úryvku kódu výše, máme metodu GenerateFieldAddressAccessor v typu SizeCalculator, která generuje delegáta typu Func<object?, long[]> založeného na seznamu polí daného typu, což nám pomáhá vracet paměťovou adresu zadaného objektu a všech jeho polí. S adresou samotného objektu a adresou každého pole můžeme přirozeně získat offset každého pole a pak snadno vypočítat počet bajtů paměti zabíraných celou instancí.
7. Spočítejte počet bajtů typu hodnoty
Protože typy hodnot a typů referencí mají v paměti různé rozložení, musíme také použít různé výpočty. Protože bajt struktury je obsah všech polí v paměti, používáme chytrý způsob jeho výpočtu. Předpokládejme, že potřebujeme ustálit počet bajtů struktury typu T, pak vytvoříme n-tici ValueTuple<T,T> a offset jejího druhého pole Item2 je počet bajtů struktury T. Konkrétní metoda výpočtu je odražena v následující metodě CalculateValueTypeInstance.
Jak je ukázáno v úryvku kódu výše, za předpokladu, že typ struktury, který potřebujeme vypočítat, je T, voláme metodu GetDefaultAsObject, abychom získali výchozí(T) objekt ve formě odrazu, a poté vytvoříme ValueTuple<T,T>tici. Po zavolání metody GenerateFieldAddressAccessor pro získání delegáta Func<object?, long[]> pro výpočet instance a jejích adres polí, nazýváme tohoto delegáta argumentem. Pro tři paměťové adresy, které dostaneme, jsou kódová n-tice a adresy polí 1 a 2 stejné, použijeme třetí adresu reprezentující položku 2 minus první adresu a dostaneme požadovaný výsledek.
8. Spočítejte počet bajtů citačního typu
Výpočet bajtů pro typy referencí je složitější, protože používáme tuto myšlenku: poté, co získáme adresu samotné instance a každého pole, třídíme adresy, abychom získali offset posledního pole. Přičtěme tento offset k počtu bajtů samotného posledního pole a poté přidejme potřebné "první a poslední bajty" k požadovanému výsledku, což se odráží v následující metodě CalculateReferneceTypeInstance.
Jak je ukázáno v úryvku kódu výše, pokud specifikovaný typ nemá žádná definovaná pole, CalculateReferneceTypeInstance vrátí minimální počet bajtů instance referenčního typu: 3krát více bajtů adresního ukazatele. Pro architektury x86 zabírá objekt typu aplikace alespoň 12 bajtů, včetně ObjectHeader (4 bajty), ukazatelů tabulky metod (bajtů) a alespoň 4 bajtů obsahu pole (těchto 4 bajtů je potřeba i v případě, že žádný typ není definován bez pole). V případě architektury x64 bude tento minimální počet bajtů 24, protože ukazatel tabulky metod a minimální obsah pole budou 8 bajtů, i když platný obsah ObjectHeaderu zabírá pouze 4 bajty, ale před něj budou přidány 4 bajty vyplňování.
Uspořádání bajtů obsazených posledním polem je také velmi jednoduché: pokud je typ hodnotovým typem, pak se k výpočtu volá metoda CalculateValueTypeInstance definovaná dříve, pokud je to referenční typ, obsah uložený v poli je pouze paměťová adresa cílového objektu, takže délka je IntPtr.Size. Protože instance referenčních typů jsou ve výchozím nastavení zarovnány s IntPtr.Size v paměti, toto se provádí i zde. Nakonec nezapomeňte, že reference instance referenčního typu neukazuje na první bajt paměti, ale na bajt, který uchovává ukazatel tabulky metod, takže musíte přidat počet bajtů ObjecthHeader (IntPtr.Size).
9. Kompletní výpočet
Dvě metody používané k výpočtu počtu bajtů typu hodnoty a referenčního typu jsou použity v následující metodě SizeOf. Protože volání instrukce Ldflda musí poskytnout odpovídající instanci, tato metoda poskytuje delegáta k získání odpovídající instance kromě zadání cílového typu. Parametry odpovídající tomuto delegátovi mohou být výchozí a použijeme výchozí hodnotu pro typ hodnoty. Pro typy referencí se také pokusíme vytvořit cílový objekt pomocí výchozího konstruktoru. Pokud tento objekt delegáta není poskytnut a cílová instance nemůže být vytvořena, metoda SizeOf vyhodí výjimku. Ačkoliv musíme poskytnout cílovou instanci, vypočítaný výsledek souvisí pouze s typem, takže vypočtený výsledek ukládáme do mezipaměti. Pro snadnější volání nabízíme také další <T>obecnou metodu SizeOf.
V níže uvedeném úryvku kódu jej používáme k výstupu počtu bajtů dvou struktur a typů se stejnou definicí pole. V dalším článku se dozvíme, že v paměti se dozvíme kompletní binární obsah instance na základě vypočteného počtu bajtů, takže zůstaňte naladěni.
Původní odkaz:Přihlášení k hypertextovému odkazu je viditelné. |