Мы все знаем, что процессор и память — это два самых важных показателя для программы, поэтому сколько людей действительно задумывались над вопросом: сколько байт в памяти занимает экземпляр типа (тип значения или тип ссылки)? Многие из нас не могут ответить. C# предоставляет некоторые операторы и API для вычисления размеров, но ни один из них не решает полностью ту проблему, которую я только что задал. В этой статье представлен метод вычисления количества байтов памяти, занятых экземплярами типов значений и типов ссылок. Исходный код можно скачать отсюда.
1. размер оператора 2. Метод Marshal.SizeOf 3. Небезопасно. РазмерМетод > 4. Можно ли рассчитывать на основе типа участника поля? 5. Расположение типов значений и типов приложений 6. Директива LDFLDA 7. Вычислить количество байт типа значения 8. Подсчитайте количество байт типа цитирования 9. Полный расчёт
1. размер оператора
Размер операции используется для определения количества байт, занятых экземпляром типа, но может применяться только к неуправляемым типам. Так называемый неуправляемый тип ограничен следующим образом:
Примитивные типы: Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double и Single) Десятичный тип Тип переписи Тип указателя Структуры, содержащие только элементы данных типа Unmanaged Как следует из названия, неуправляемый тип — это тип значения, и соответствующий экземпляр не может содержать ссылки на управляемый объект. Если определить такой универсальный метод для вызова оператора sizeof, то общий параметр T должен добавить неуправляемое ограничение и небезопасный тег к методу.
Только нативные и enum типы могут использовать оператор sizeof, который необходимо добавлять, если применяется к другим типам (указателям и пользовательским структурам)./unsafeкомпиляционные теги, а также должны быть размещены вопасныйВ контексте.
Поскольку следующая структура Foobar не является типом Unmanage, программа будет иметь ошибку компиляции.
2. Метод Marshal.SizeOf
Статические типы Маршал определяет серию API, которые помогают нам выделять и копировать неуправляемую память, преобразовывать между управляемыми и неуправляемыми типами, а также выполнять ряд других операций с неуправляемой памятью (в вычислительной науке Marshal означает преобразование объектов памяти в соответствующий формат для хранения или передачи данных). Статический, включающий следующие 4 метода SizeOf, перегружает для определения количества байт заданного типа или объекта.
Метод Marshal.SizeOf не имеет ограничения на указанный тип для типа Unmanage, но всё равно требует указания такого типаТип стоимости。 Если входящий объект является объектом, он также должен быть блоком для типа значения.
Поскольку следующий Фубар определяется как:добрый, поэтому вызовы обоих методов SizeOf будут выдают исключение ArgumentException и подсказку: Type 'Foobar' нельзя использовать как неуправляемую структуру; Нельзя вычислить значимый размер или смещение.
Метод Marshal.SizeOfГенерики не поддерживаются, но также есть требования к планировке конструкции, которая поддерживает поддержкуПоследовательныйиЯвныйРежим раскладки. Поскольку структура Foobar, показанная ниже, использует режим Auto layout (Auto, который не поддерживает «динамическое планирование» раскладки памяти на основе членов поля из-за более строгих требований к раскладке памяти в неуправляемых средах), вызовы метода SizeOf всё равно будут выдавать то же исключение ArgumentException, что и выше.
3. Небезопасный. Размер метода
Статический небезопасный обеспечивает больше низкоуровневых операций для неуправляемой памяти, и аналогичные методы SizeIOf также определены в этом типе. Метод не имеет ограничений на указанный тип, но если вы указываете тип ссылки, он возвращаетКоличество байт-указателей«(IntPtr.Size)。
4. Можно ли рассчитывать на основе типа участника поля?
Известно, что и типы значений, и ссылки отображаются как непрерывный фрагмент (или хранятся непосредственно в регистре). Цель типа — задать расположение памяти объекта, и экземпляры того же типа имеют одинаковую структуру, а количество байт естественно одинаковое (для полей эталонного типа он хранит только указанный адрес в этой байтовой последовательности). Поскольку длина байта определяется типом, если мы можем определить тип каждого члена поля, разве мы не сможем вычислить количество байт, соответствующих этому типу? На самом деле, это невозможно.
Например, мы знаем, что байты байт, short, int и long равны 1, 2, 4 и 8, поэтому количество байтов для бинарного байта равно 2, но для типовой комбинации байт + short, байт + int и байт + long соответствующие байты не 3, 5 и 9, а 3, 8 и 16. Потому что речь идёт о проблеме выравнивания памяти.
5. Расположение типов значений и типов отсылок
Количество байт, занятых экземплярами типа и подтипа ссылки, также различается для одного и того же элемента данных. Как показано на следующем изображении, последовательность байтов экземпляра типа значенияВсе они — полевые сотрудники, используемые для хранения。 Для примеров типов ссылок адрес соответствующей таблицы методов также хранится перед последовательностью байтов поля. Таблица методов содержит почти все метаданные, описывающие тип, и мы используем эту ссылку, чтобы определить, к какому типу принадлежит экземпляр. В самом начале также есть дополнительные байты, которые мы назовёмЗаголовок объектаОн используется не только для хранения заблокированного состояния объекта, но и здесь может кэшировать хеш-значение. Когда мы создаём переменную типа ссылок, эта переменнаяОн указывает не на первый байт памяти, занятый экземпляром, а на место, где хранится адрес таблицы методов。
6. Директива LDFLDA
Как мы уже представили выше, оператор sizeof и метод 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.
В приведённом ниже фрагменте кода мы используем его для вывода количества байт двух структур и типов с одинаковым определением поля. В следующей статье мы подробнее узнаем полное двоичное содержимое экземпляра в памяти на основе рассчитанного количества байт, так что следите за новостями.
Оригинальная ссылка:Вход по гиперссылке виден. |