У меня есть две структуры с массивами байтов и логических значений:
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct struct1
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public byte[] values;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct struct2
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public bool[] values;
}
И следующий код:
class main
{
public static void Main()
{
Console.WriteLine("sizeof array of bytes: "+Marshal.SizeOf(typeof(struct1)));
Console.WriteLine("sizeof array of bools: " + Marshal.SizeOf(typeof(struct2)));
Console.ReadKey();
}
}
Это дает мне следующий результат:
sizeof array of bytes: 3
sizeof array of bools: 12
Кажется, что a boolean
занимает 4 байта памяти. В идеале a boolean
будет занимать только один бит ( false
или true
, 0
или 1
и т. Д.).
Что здесь происходит? Неужели boolean
типаж такой неэффективный?
Ответы:
Тип bool имеет изменчивую историю с множеством несовместимых вариантов языковых сред. Это началось с исторического выбора дизайна, сделанного Деннисом Ричи, парнем, который изобрел язык C. У него не было типа bool , альтернативой была int, где значение 0 представляло ложь, а любое другое значение считалось истинным .
Этот выбор был перенесен в Winapi, основная причина использования pinvoke, у него есть typedef, для
BOOL
которого является псевдонимом ключевого слова int компилятора C. Если вы не применяете явный атрибут [MarshalAs], то логическое значение C # преобразуется в BOOL, создавая, таким образом, поле длиной 4 байта.Что бы вы ни делали, объявление вашей структуры должно соответствовать выбору среды выполнения, сделанному на языке, с которым вы взаимодействуете. Как уже отмечалось, BOOL для winapi, но большинство реализаций C ++ выбирают байт , большинство взаимодействия COM Automation используют VARIANT_BOOL, что является коротким .
Фактический размер C #
bool
составляет один байт. Сильная цель разработки CLR - это то, что вы не можете узнать. Макет - это деталь реализации, которая слишком сильно зависит от процессора. Процессоры очень придирчивы к типам переменных и их выравниванию, неправильный выбор может существенно повлиять на производительность и вызвать ошибки времени выполнения. Делая макет недоступным для обнаружения, .NET может предоставить универсальную систему типов, не зависящую от фактической реализации среды выполнения.Другими словами, вы всегда должны маршалировать структуру во время выполнения, чтобы закрепить макет. На этом этапе выполняется преобразование из внутреннего макета в макет взаимодействия. Это может быть очень быстро, если макет идентичен, и медленным, когда поля необходимо переупорядочить, поскольку для этого всегда требуется создание копии структуры. Технический термин для этого - непреобразуемый , передача непреобразуемой структуры в собственный код происходит быстро, потому что маршаллер pinvoke может просто передать указатель.
Производительность также является основной причиной, по которой логическое значение не является единичным битом. Есть несколько процессоров, которые обеспечивают прямую адресацию битов, наименьшая единица - байт. Для извлечения бита из байта требуется дополнительная инструкция, которая предоставляется не бесплатно. И он никогда не бывает атомным.
В остальном компилятор C # не стесняется говорить вам, что он занимает 1 байт, используйте
sizeof(bool)
. Это все еще не фантастический предсказатель того, сколько байтов занимает поле во время выполнения, CLR также должна реализовывать модель памяти .NET, и она обещает, что простые обновления переменных являются атомарными . Это требует, чтобы переменные были правильно выровнены в памяти, чтобы процессор мог обновлять их за один цикл шины памяти. Довольно часто из-за этого для bool требуется 4 или 8 байтов в памяти. Дополнительный отступ, который был добавлен для обеспечения правильного выравнивания следующего члена.CLR фактически использует то, что макет не может быть обнаружен, он может оптимизировать макет класса и переупорядочивать поля, чтобы минимизировать отступы. Итак, скажем, если у вас есть класс с членом bool + int + bool, тогда он займет 1 + (3) + 4 + 1 + (3) байта памяти, (3) - это заполнение, всего 12 байтов. 50% отходов. Автоматическая компоновка изменяется на 1 + 1 + (2) + 4 = 8 байт. Только класс имеет автоматическую компоновку, структуры по умолчанию имеют последовательную компоновку.
Еще более мрачно то, что для bool может потребоваться до 32 байтов в программе C ++, скомпилированной с помощью современного компилятора C ++, который поддерживает набор инструкций AVX. Что требует 32-байтового выравнивания, переменная bool может содержать 31 байт заполнения. Также основная причина, по которой джиттер .NET не испускает инструкции SIMD, если явно не завернут, он не может получить гарантию выравнивания.
источник
Во-первых, это только размер для взаимодействия. Он не представляет размер в управляемом коде массива. Это 1 байт на
bool
- по крайней мере, на моей машине. Вы можете проверить это сами с помощью этого кода:using System; class Program { static void Main(string[] args) { int size = 10000000; object array = null; long before = GC.GetTotalMemory(true); array = new bool[size]; long after = GC.GetTotalMemory(true); double diff = after - before; Console.WriteLine("Per value: " + diff / size); // Stop the GC from messing up our measurements GC.KeepAlive(array); } }
Теперь, для сортировки массивов по значению, как и вы, в документации говорится:
Итак, мы смотрим на
ArraySubType
:Теперь, глядя на
UnmanagedType
, есть:Это значение по умолчанию для
bool
, и это 4 байта, потому что это соответствует типу Win32 BOOL - поэтому, если вы взаимодействуете с кодом, ожидающимBOOL
массив, он делает именно то, что вы хотите.Теперь вы можете указать
ArraySubType
asI1
вместо этого, что задокументировано как:Поэтому, если код, с которым вы взаимодействуете, ожидает 1 байт на значение, просто используйте:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.I1)] public bool[] values;
Затем ваш код покажет, что это занимает 1 байт на значение, как и ожидалось.
источник