Știm cu toții că CPU-ul și memoria sunt cele mai importante două metrici pentru un program, așa că câți oameni s-au gândit cu adevărat la întrebarea: Câți octeți ocupă o instanță a unui tip (tip de valoare sau tip de referință) în memorie? Mulți dintre noi nu putem răspunde. C# oferă câțiva operatori și API-uri pentru calcularea dimensiunilor, dar niciunul nu rezolvă complet problema pe care tocmai am întrebat-o. Acest articol oferă o metodă pentru calcularea numărului de octeți de memorie ocupați de instanțe de tipuri de valoare și tipuri de referință. Codul sursă este descărcat de aici.
1. Dimensiunea operatorului 2. Metoda Marshal. Dimensiunea 3. Nesigur. Dimensiunea metodei > 4. Se poate calcula pe baza tipului de membru al câmpului? 5. Structura tipurilor de valori și a tipurilor de aplicații 6. Directiva LDFLDA 7. Calcularea numărului de octeți ai tipului de valoare 8. Numără numărul de octeți ai tipului de citare 9. Calcul complet
1. Dimensiunea operatorului
Operația de dimensiune este folosită pentru a determina numărul de octeți ocupați de o instanță a unui tip, dar poate fi aplicată doar tipurilor Negestionate. Așa-numitul tip Negestionat este limitat la:
Tipuri primitive: Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double și Single) Tip zecimal Tip de enumerare Tip pointer Structuri care conțin doar membri de date de tip Negestionat După cum sugerează și numele, un tip Negestionat este un tip de valoare, iar instanța corespunzătoare nu poate conține nicio referință la obiectul administrat. Dacă definim o metodă generică ca aceasta pentru a numi operatorul sizeof, parametrul generic T trebuie să adauge o constrângere negestionată și o etichetă nesigură la metodă.
Doar tipurile native și enum pot folosi direct operatorul sizeof, care trebuie adăugat dacă este aplicat altor tipuri (pointere și structuri personalizate)./unsafeși trebuie de asemenea plasate înnesigurîn context.
Deoarece structura următoare Foobar nu este de tip Negestionat, programul va avea o eroare de compilare.
2. Metoda Marshal. Dimensiunea
Tipuri statice Marshal definește o serie de API-uri care ne ajută să alocăm și să copiem memorie neadministrată, să convertim între tipuri gestionate și negestionate și să efectuăm o serie de alte operații pe memoria negestionată (Marshal în știința computațională se referă la operația de conversie a obiectelor de memorie în formatul corespunzător pentru stocarea sau transferul datelor). Static, care include următoarele 4 suprasarcini de metodă SizeOf pentru a determina numărul de octeți dintr-un anumit tip sau obiect.
Metoda Marshal.SizeOf nu are o restricție privind tipul specificat pentru tipul Negestionat, dar tot necesită ca una să fie specificatăTip de valoare。 Dacă obiectul care intră este un obiect, trebuie să fie și o cutie pentru un tip de valoare.
Deoarece următorul Foobar este definit astfel:fel, deci apelurile către ambele metode SizeOf vor genera o excepție ArgumentException și un prompt: Tipul 'Foobar' nu poate fi marshalat ca o structură negestionată; nu se poate calcula nicio dimensiune sau offset semnificativ.
Metoda Marshal.SizeOfGenericele nu sunt suportate, dar are și cerințe pentru configurația structurii, care susține suportulSecvenţialșiExplicitModul de layout. Deoarece structura Foobar prezentată mai jos adoptă modul Auto layout (Auto, care nu suportă "planificarea dinamică" a layout-ului memoriei bazată pe membrii câmpului din cauza cerințelor mai stricte de aranjare a memoriei în mediile negestionate), apelurile către metoda SizeOf vor genera în continuare aceeași excepție ArgumentException ca mai sus.
3. Metoda nesigură. Dimensiunea
Static Unsafe oferă mai multe operații de nivel scăzut pentru memoria negestionată, iar metode similare SizeIOf sunt de asemenea definite în acest tip. Metoda nu are restricții privind tipul specificat, dar dacă specifici un tip de referință, returneazăNumărul de octeți de pointer"(IntPtr.Size)。
4. Se poate calcula pe baza tipului de membru al câmpului?
Știm că atât tipurile de valoare, cât și cele de referință sunt mapate ca un fragment continuu (sau stocate direct într-un registru). Scopul unui tip este de a specifica aranjamentul memoriei unui obiect, iar instanțele de același tip au aceeași dispunere și numărul de octeți este în mod natural același (pentru câmpurile de tip de referință, stochează doar adresa referențiată în această secvență de octeți). Deoarece lungimea octetului este determinată de tip, dacă putem determina tipul fiecărui membru al câmpului, nu am putea calcula numărul de octeți corespunzători acelui tip? De fapt, nu este posibil.
De exemplu, știm că octeții octetului scurt, int și lung sunt 1, 2, 4 și 8, deci numărul de octeți pentru un binar de octeți este 2, dar pentru o combinație de tip octet + scurt, octet + int și octet + lung, octeții corespunzători nu sunt 3, 5 și 9, ci 3, 8 și 16. Pentru că asta implică problema alinierii memoriei.
5. Structura tipurilor de valori și a tipurilor de referință
Numărul de octeți ocupați de instanțele tipului de referință și subtipului este, de asemenea, diferit pentru exact același membru de date. Așa cum se arată în imaginea următoare, secvența de octeți a instanței tipului de valoareToți sunt membri de teren folosiți pentru a o stoca。 Pentru instanțele tipurilor de referință, adresa tabelului de metode corespunzător este stocată și în fața secvenței de octeți de câmp. Tabelul de metode oferă aproape toate metadatele care descriu tipul, iar noi folosim această referință pentru a determina ce tip aparține instanța. Chiar în față, există și octeți suplimentari, pe care îi vom numiAntet obiectNu este folosit doar pentru a stoca starea blocată a obiectului, ci și valoarea hash poate fi stocată în cache aici. Când creăm o variabilă de tip referință, această variabilăNu indică către primul octet de memorie ocupat de instanță, ci către locul unde este stocată adresa tabelului de metodă。
6. Directiva LDFLDA
Așa cum am introdus mai sus, operatorul sizeof și metoda SizeOf furnizată de tipul static Marshal/Unsafe nu pot rezolva cu adevărat calculul lungimii octetului ocupat de instanțe. Din câte știu, această problemă nu poate fi rezolvată doar în câmpul C#, dar este oferită la nivel ILLDFLDAInstrucțiunile ne pot ajuta să rezolvăm această problemă. După cum sugerează și numele, Ldflda înseamnă Load Field Address, ceea ce ne ajută să obținem adresa unui câmp în instanță. Deoarece această instrucțiune IL nu are un API corespunzător în C#, o putem folosi doar în forma următoare folosind IL Emit.
Așa cum se arată în fragmentul de cod de mai sus, avem o metodă GenerateFieldAddressAccessor în tipul SizeCalculator, care generează un delegat de tip Func<object?, long[]> bazat pe lista câmpurilor de tipul specificat, ceea ce ne ajută să returnăm adresa de memorie a obiectului specificat și a tuturor câmpurilor sale. Cu adresa obiectului în sine și adresa fiecărui câmp, putem obține în mod natural offset-ul fiecărui câmp și apoi putem calcula ușor numărul de octeți de memorie ocupați de întreaga instanță.
7. Calcularea numărului de octeți ai tipului de valoare
Deoarece tipurile de valori și cele de referință au aranjamente diferite în memorie, trebuie să folosim și calcule diferite. Deoarece octetul structurii este conținutul tuturor câmpurilor din memorie, folosim o metodă ingenioasă de a-l calcula. Să presupunem că trebuie să stabilim numărul de octeți ai unei structuri de tip T, atunci creăm o tuple-uri ValueTuple<T,T>, iar offset-ul celui de-al doilea câmp Item2 este numărul de octeți al structurii T. Metoda specifică de calcul este reflectată în următoarea metodă CalculateValueTypeInstance.
Așa cum se arată în fragmentul de cod de mai sus, presupunând că tipul struct pe care trebuie să-l calculăm este T, apelăm metoda GetDefaultAsObject pentru a obține obiectul default(T) sub forma unei reflexii, apoi creăm un ValueTuple<T,T>tuple. După ce apelăm metoda GenerateFieldAddressAccessor pentru a obține delegatul Func<object?, long[]> pentru calcularea instanței și a adreselor sale de câmp, numim acest delegat ca argument. Pentru cele trei adrese de memorie pe care le obținem, tupla-ul de cod și adresele câmpurilor 1 și 2 sunt aceleași, folosim a treia adresă care reprezintă Item2 minus prima adresă și obținem rezultatul dorit.
8. Numără numărul de octeți ai tipului de citare
Calculul de octeți pentru tipurile de referință este mai complicat, folosind această idee: după ce obținem adresa instanței în sine și a fiecărui câmp, sortăm adresele pentru a obține offset-ul ultimului câmp. Să adăugăm acest offset la numărul de octeți ai ultimului câmp propriu-zis, apoi să adăugăm "primul și ultimul octet" necesar rezultatului dorit, ceea ce se reflectă în următoarea metodă CalculateReferneceTypeInstance.
Așa cum se arată în fragmentul de cod de mai sus, dacă tipul specificat nu are niciun câmp definit, CalculateReferneceTypeInstance returnează numărul minim de octeți al instanței de tip de referință: 3 ori numărul de octeți de adresă. Pentru arhitecturile x86, un obiect de tip aplicație ocupă cel puțin 12 octeți, inclusiv ObjectHeader (4 octeți), pointerii tabelului de metode (octeți) și cel puțin 4 octeți de conținut al câmpului (acești 4 octeți sunt necesari chiar dacă niciun tip nu este definit fără câmpuri). În cazul arhitecturii x64, acest număr minim de octeți va fi 24, deoarece pointerul tabelului de metode și conținutul minim al câmpului vor deveni 8 octeți, deși conținutul valid al ObjectHeader ocupă doar 4 octeți, dar 4 octeți de umplutură vor fi adăugați în față.
Stabilirea octeților ocupați de ultimul câmp este, de asemenea, foarte simplă: dacă tipul este un tip de valoare, atunci metoda CalculateValueTypeInstance definită anterior este chemată pentru a calcula; dacă este un tip de referință, conținutul stocat în câmp este doar adresa de memorie a obiectului țintă, deci lungimea este IntPtr.Size. Deoarece instanțele de tip referință sunt aliniate implicit cu IntPtr.Size în memorie, acest lucru se face și aici. În final, nu uita că referința instanței de tip referință nu indică către primul octet de memorie, ci către octetul care stochează indicatorul tabelului de metode, așa că trebuie să adaugi numărul de octeți din ObjecthHeader (IntPtr.Size).
9. Calcul complet
Cele două metode folosite pentru a calcula numărul de octeți ale instanțelor de tip valoare și tip de referință sunt utilizate în următoarea metodă SizeOf. Deoarece apelul instrucției Ldflda trebuie să furnizeze o instanță corespunzătoare, această metodă oferă un delegat pentru a obține instanța corespunzătoare, pe lângă tipul țintă. Parametrii corespunzători acestui delegat pot fi impliciti, iar noi vom folosi valoarea implicită pentru tipul de valoare. Pentru tipurile de referință, vom încerca și să creăm obiectul țintă folosind constructorul implicit. Dacă acest obiect delegat nu este furnizat și instanța țintă nu poate fi creată, metoda SizeOf aruncă o excepție. Deși trebuie să furnizăm instanța țintă, rezultatul calculat este legat doar de tipul, așa că stocăm în cache rezultatul calculat. Pentru ușurința apelului, oferim și o <T>altă metodă generică SizeOf.
În fragmentul de cod de mai jos, îl folosim pentru a genera numărul de octeți a două structuri și tipuri cu aceeași definiție a câmpului. În următorul articol, vom obține conținutul binar complet al instanței în memorie, pe baza numărului calculat de octeți, așa că rămâneți pe fază.
Link original:Autentificarea cu hyperlink este vizibilă. |