Ця стаття є дзеркальною статтею машинного перекладу, будь ласка, натисніть тут, щоб перейти до оригінальної статті.

Вид: 537|Відповідь: 1

[Джерело] [Поворот]. Як NET/C# розраховує, скільки пам'яті займає екземпляр?

[Копіювати посилання]
Опубліковано 27.08.2025 09:00:22 | | | |
Ми всі знаємо, що процесор і пам'ять — це дві найважливіші метрики для програми, тож скільки людей справді замислювалися над питанням: скільки байтів у пам'яті займає екземпляр типу (значення типу або типу посилання)? Багато хто з нас не може відповісти. C# надає деякі оператори та API для розрахунку розмірів, але жоден із них повністю не вирішує проблему, яку я щойно поставив. У цій статті наведено метод обчислення кількості байтів пам'яті, зайнятих екземплярами типів значень і типів посилання. Вихідний код можна завантажити звідси.

1. розмір оператора
2. Метод Marshal.SizeOf
3. Небезпечно. Розмір методу >
4. Чи можна його розрахувати на основі типу учасника поля?
5. Розташування типів значень і типів додатків
6. Директива LDFLDA
7. Обчисліть кількість байтів типу значення
8. Порахуйте кількість байтів типу цитування
9. Повний розрахунок

1. розмір оператора

Розмір операції використовується для визначення кількості байтів, зайнятих екземпляром типу, але його можна застосувати лише до некерованих типів. Так званий некерований тип обмежений:

Примітивні типи: булевий, байт, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double та Single)
Десятковий тип
Тип перепису
Тип вказівника
Структури, які містять лише елементи даних типу Unmanaged
Як випливає з назви, тип Unmanaged є типом значення, і відповідний екземпляр не може містити жодного посилання на керований об'єкт. Якщо визначити загальний метод для виклику оператора sizeof, загальний параметр T повинен додати до методу необмежене обмеження та небезпечний тег.

Лише нативні та enum типи можуть безпосередньо використовувати оператор розміру, який потрібно додавати, якщо застосовується до інших типів (вказівників і власних структур)./unsafeтеги компіляції, а також потрібно розміщувати уНебезпечниху контексті.

Оскільки наступна структура Foobar не є некерованим типом, програма матиме помилку компіляції.

2. Метод Marshal.SizeOf

Статичні типи Маршал визначає серію API, які допомагають нам виділяти та копіювати некеровану пам'ять, конвертувати між керованими та некерованими типами, а також виконувати низку інших операцій з некерованою пам'яттю (у обчислювальній науці Маршал означає операцію конвертації об'єктів пам'яті у відповідний формат для зберігання або передачі даних). Статичний, який включає наступні 4 перевантаження методу SizeOf для визначення кількості байтів певного типу або об'єкта.

Метод Marshal.SizeOf не має обмежень щодо вказаного типу для Unmanage, але все одно вимагає його вказівкиТип вартості。 Якщо вхідний об'єкт є об'єктом, він також повинен бути коробкою для типу значення.

Оскільки наступний Фубар визначається як:вид, тому виклики обох методів SizeOf видають виключення ArgumentException і запит: Набір 'Foobar' не може бути використаний як некерована структура; Не можна обчислити значущий розмір чи зсув.

Метод Marshal.SizeOfДженерики не підтримуються, але також має вимоги до планування конструкції, яка підтримує підтримкуПослідовнихіЯвніРежим розкладки. Оскільки структура Foobar, показана нижче, приймає режим Auto (Auto, який не підтримує «динамічне планування» розкладки пам'яті на основі елементів поля через суворіші вимоги до компонування пам'яті в некерованих середовищах), виклики методу SizeOf все одно видають той самий виняток ArgumentException, що й вище.

3. Небезпечний.Розмір методу

Static Unsafe забезпечує більше низькорівневих операцій для некерованої пам'яті, і подібні методи SizeIOf також визначені в цьому типі. Метод не має обмежень щодо вказаного типу, але якщо вказати тип посилання, він повертаєКількість байтів вказівників"(IntPtr.Size)。

4. Чи можна його розрахувати на основі типу учасника поля?

Відомо, що і типи значення, і посилання відображаються як неперервний фрагмент (або зберігаються безпосередньо в регістрі). Мета типу — вказати розташування пам'яті об'єкта, і екземпляри того ж типу мають однакове розташування, а кількість байтів природно однакова (для полів типу посилання зберігається лише згадана адреса в цій послідовності байтів). Оскільки довжина байта визначається типом, якщо ми можемо визначити тип кожного члена поля, хіба ми не зможемо обчислити кількість байтів, що відповідають цьому типу? Насправді це неможливо.

Наприклад, ми знаємо, що байти байтів, коротких, int і long дорівнюють 1, 2, 4 і 8, тому кількість байтів для бінарного байта дорівнює 2, але для комбінації типів байт + короткий, байт + int і байт + довгий відповідні байти не 3, 5 і 9, а 3, 8 і 16. Адже це стосується питання вирівнювання пам'яті.

5. Розташування типів значень і еталонних типів

Кількість байтів, зайнятих екземплярами типу посилання та підтипу, також відрізняється для того самого елемента даних. Як показано на наступному зображенні, послідовність байтів екземпляра типу значенняУсі вони є польовими членами, які використовуються для зберігання。 Для прикладів типів посилання адреса відповідної таблиці методу типу також зберігається перед послідовністю байтів поля. Таблиця методів містить майже всі метадані, що описують тип, і ми використовуємо це посилання, щоб визначити, до якого типу належить екземпляр. На самому початку також є додаткові байти, які ми назвемоЗаголовок об'єктаВін використовується не лише для зберігання заблокованого стану об'єкта, а й для кешування тут можна кешувати хеш-значення. Коли ми створюємо змінну типу посилання, ця зміннаВін не вказує на перший байт пам'яті, зайнятий екземпляром, а на місце, де зберігається адреса таблиці методу



6. Директива LDFLDA

Як ми зазначили вище, розмір оператора та метод SizeOf, який надає статичний тип Marshal/Unsafe, насправді не можуть розв'язати обчислення довжини байтів, зайнятих екземплярами. Наскільки мені відомо, цю проблему не можна вирішити лише в галузі C#, але вона надається на рівні ILLdfldaІнструкції можуть допомогти нам розв'язати цю проблему. Як випливає з назви, Ldflda означає Load Field Address, що допомагає нам отримати адресу поля в екземплярі. Оскільки ця інструкція IL не має відповідного API в C#, ми можемо використовувати її лише у такій формі за допомогою IL Emit.

Як показано у наведеному вище фрагменті коду, у нас є метод GenerateFieldAddressAccessor у типі SizeCalculator, який генерує делегата типу Func<object?, long[]> на основі списку полів зазначеного типу, що допомагає повернути адресу пам'яті вказаного об'єкта та всіх його полів. З адресою самого об'єкта та адресою кожного поля ми природно можемо отримати зсув кожного поля, а потім легко обчислити кількість байтів пам'яті, зайнятих усім екземпляром.

7. Обчисліть кількість байтів типу значення

Оскільки типи значень і еталонні типи мають різні структури в пам'яті, нам також потрібно використовувати різні обчислення. Оскільки байт структури — це вміст усіх полів у пам'яті, ми використовуємо хитрий спосіб його обчислення. Припустимо, нам потрібно визначити кількість байтів структури типу T, тоді створюємо кортеж ValueTuple<T,T>, і зсув його другого поля Item2 дорівнює кількості байтів структури T. Конкретний метод обчислення відображається у наступному методі CalculateValueTypeInstance.

Як показано у наведеному вище фрагменті коду, припускаючи, що тип структури, який нам потрібно обчислити, є T, ми викликаємо метод GetDefaultAsObject, щоб отримати об'єкт default(T) у вигляді відображення, а потім створюємо ValueTuple<T,T>кортеж. Після виклику методу GenerateFieldAddressAccessor для отримання делегата Func<object?, long[]> для обчислення екземпляра та його адрес полів, ми викликаємо цей делегат як аргумент. Для трьох адрес пам'яті, які ми отримуємо, кортеж коду і адреси полів 1 і 2 збігаються, ми використовуємо третю адресу, що представляє елемент 2, мінус першу, і отримуємо бажаний результат.

8. Порахуйте кількість байтів типу цитування

Обчислення байтів для типів посилань складніше, використовуючи таку ідею: після отримання адреси самого екземпляра та кожного поля ми сортуємо адреси, щоб отримати зсув останнього поля. Додамо цей зсув до кількості байтів останнього поля, а потім додамо необхідні «перші та останні байти» до потрібного результату, що відображається у наступному методі CalculateReferneceTypeInstance.

Як показано у наведеному вище фрагменті коду, якщо вказаний тип не має визначених полів, CalculateReferneceTypeInstance повертає мінімальну кількість байтів екземпляра типу посилання: у 3 рази більше байтів вказівника адреси. Для архітектур x86 об'єкт типу додатка займає щонайменше 12 байтів, включно з ObjectHeader (4 байти), вказівниками таблиці методів (байтами) та щонайменше 4 байтами вмісту поля (ці 4 байти потрібні, навіть якщо жоден тип не визначений без полів). У випадку архітектури x64 ця мінімальна кількість байтів буде 24, оскільки вказівник таблиці методів і мінімальний вміст поля становлять 8 байтів, хоча дійсний вміст ObjectHeader займає лише 4 байти, але 4 байти заповнення додаються спереду.

Розв'язання байтів, зайнятих останнім полем, також дуже просте: якщо тип є типом значення, то для обчислення викликається раніше визначений метод CalculateValueTypeInstance; якщо це тип посилання, то вміст, що зберігається в полі, є лише адресою пам'яті цільового об'єкта, тому довжина дорівнює IntPtr.Size. Оскільки екземпляри типів посилання за замовчуванням вирівнюються з IntPtr.Size у пам'яті, це також робиться тут. Нарешті, не забувайте, що посилання на тип посилання не вказує на перший байт пам'яті, а на байт, що зберігає вказівник таблиці методів, тому потрібно додати кількість байтів ObjecthHeader (IntPtr.Size).

9. Повний розрахунок

Два методи, що використовуються для обчислення кількості байтів екземплярів типів значення та типу посилання, застосовуються у наступному методі SizeOf. Оскільки виклик інструкції Ldflda має надати відповідний екземпляр, цей метод надає делегат для отримання відповідного екземпляра, окрім наведення цільового типу. Параметри, що відповідають цьому делегату, можуть бути встановлені за замовчуванням, і ми використаємо значення за замовчуванням для типу значення. Для типів референсу ми також спробуємо створити цільовий об'єкт за стандартним конструктором. Якщо цей об'єкт делегата не надано і цільовий екземпляр не можна створити, метод SizeOf викидає виняток. Хоча нам потрібно надати цільовий екземпляр, обчислений результат пов'язаний лише з типом, тому ми кешуємо обчислений результат. Для зручності виклику ми також пропонуємо ще один <T>універсальний метод SizeOf.

У наведеному нижче фрагменті коду ми використовуємо його для виведення кількості байтів двох структур і типів з однаковим визначенням поля. У наступній статті ми детальніше отримаємо повний бінарний вміст екземпляра в пам'яті на основі обчисленої кількості байтів, тож слідкуйте за оновленнями.

Оригінальне посилання:Вхід за гіперпосиланням видно.




Попередній:Фронтенд-фреймворк вивчає проект відкритого коду Component-Party
Наступний:Зберігання MinIO (iii) Копіювання-завантаження (міграція) локальних файлів у minio bucket
 Орендодавець| Опубліковано 2025-8-27 09:33:22 |
Колекція C# вставляє 10 000 фрагментів даних, які займають пам'ять




Код:


Застереження:
Усе програмне забезпечення, програмні матеріали або статті, опубліковані Code Farmer Network, призначені лише для навчання та досліджень; Вищезазначений контент не повинен використовуватися в комерційних чи незаконних цілях, інакше користувачі несуть усі наслідки. Інформація на цьому сайті надходить з Інтернету, і спори щодо авторських прав не мають до цього сайту. Ви повинні повністю видалити вищезазначений контент зі свого комп'ютера протягом 24 годин після завантаження. Якщо вам подобається програма, будь ласка, підтримуйте справжнє програмне забезпечення, купуйте реєстрацію та отримайте кращі справжні послуги. Якщо є будь-яке порушення, будь ласка, зв'яжіться з нами електронною поштою.

Mail To:help@itsvse.com