В Noda Time v2 мы переходим к наносекундному разрешению. Это означает, что мы больше не можем использовать 8-байтовое целое число для представления всего интересующего нас диапазона времени. Это побудило меня исследовать использование памяти (многими) структурами Noda Time, что, в свою очередь, привело меня к чтобы выявить небольшую странность в решении CLR о выравнивании.
Во - первых, я понимаю , что это является решение реализации, и что поведение по умолчанию может измениться в любой момент. Я понимаю, что могу изменить его с помощью [StructLayout]
и [FieldOffset]
, но я бы предпочел найти решение, которое не требует этого, если это возможно.
Мой основной сценарий состоит в том, что у меня есть struct
поле, содержащее поле ссылочного типа и два других поля типа значения, для которых эти поля являются простыми оболочками int
. Я надеялся , что это будет представлено как 16 байтов в 64-битной среде CLR (8 для справки и 4 для каждого из остальных), но по какой-то причине он использует 24 байта. Между прочим, я измеряю пространство с помощью массивов - я понимаю, что макет может отличаться в разных ситуациях, но это было разумной отправной точкой.
Вот пример программы, демонстрирующей проблему:
using System;
using System.Runtime.InteropServices;
#pragma warning disable 0169
struct Int32Wrapper
{
int x;
}
struct TwoInt32s
{
int x, y;
}
struct TwoInt32Wrappers
{
Int32Wrapper x, y;
}
struct RefAndTwoInt32s
{
string text;
int x, y;
}
struct RefAndTwoInt32Wrappers
{
string text;
Int32Wrapper x, y;
}
class Test
{
static void Main()
{
Console.WriteLine("Environment: CLR {0} on {1} ({2})",
Environment.Version,
Environment.OSVersion,
Environment.Is64BitProcess ? "64 bit" : "32 bit");
ShowSize<Int32Wrapper>();
ShowSize<TwoInt32s>();
ShowSize<TwoInt32Wrappers>();
ShowSize<RefAndTwoInt32s>();
ShowSize<RefAndTwoInt32Wrappers>();
}
static void ShowSize<T>()
{
long before = GC.GetTotalMemory(true);
T[] array = new T[100000];
long after = GC.GetTotalMemory(true);
Console.WriteLine("{0}: {1}", typeof(T),
(after - before) / array.Length);
}
}
И сборка и вывод на моем ноутбуке:
c:\Users\Jon\Test>csc /debug- /o+ ShowMemory.cs
Microsoft (R) Visual C# Compiler version 12.0.30501.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.
c:\Users\Jon\Test>ShowMemory.exe
Environment: CLR 4.0.30319.34014 on Microsoft Windows NT 6.2.9200.0 (64 bit)
Int32Wrapper: 4
TwoInt32s: 8
TwoInt32Wrappers: 8
RefAndTwoInt32s: 16
RefAndTwoInt32Wrappers: 24
Так:
- Если у вас нет поля ссылочного типа, среда CLR с радостью упакует
Int32Wrapper
поля вместе (TwoInt32Wrappers
имеет размер 8). - Даже с полем ссылочного типа среда CLR по-прежнему может упаковывать
int
поля вместе (RefAndTwoInt32s
имеет размер 16). - Комбинируя эти два, каждое
Int32Wrapper
поле кажется дополненным / выровненным до 8 байтов. (RefAndTwoInt32Wrappers
имеет размер 24.) - Выполнение того же кода в отладчике (но все еще сборка выпуска) показывает размер 12.
Еще несколько экспериментов дали аналогичные результаты:
- Помещение поля ссылочного типа после полей типа значения не помогает
- Использование
object
вместоstring
не помогает (я ожидаю, что это "любой ссылочный тип") - Использование другой структуры в качестве «оболочки» вокруг ссылки не помогает
- Использование общей структуры в качестве оболочки вокруг ссылки не помогает
- Если я продолжу добавлять поля (попарно для простоты),
int
поля все равно будут считаться 4 байтами, аInt32Wrapper
поля - 8 байтами. - Добавление
[StructLayout(LayoutKind.Sequential, Pack = 4)]
к каждой видимой структуре не меняет результатов
Есть ли у кого-нибудь объяснение этого (в идеале со справочной документацией) или предложение о том, как я могу намекнуть CLR, что я бы хотел, чтобы поля были упакованы без указания постоянного смещения поля?
TwoInt32Wrappers
, или Int64
и а TwoInt32Wrappers
? Как насчет того, чтобы создать общий шаблон, Pair<T1,T2> {public T1 f1; public T2 f2;}
а затем создать Pair<string,Pair<int,int>>
и Pair<string,Pair<Int32Wrapper,Int32Wrapper>>
? Какие комбинации вынуждают JITter сглаживать вещи?
Pair<string, TwoInt32Wrappers>
это даст только 16 байт, так что бы решить эту проблему. Захватывающий.
Marshal.SizeOf
вернет размер структуры, которая будет передана в машинный код, который не обязательно должен иметь какое-либо отношение к размеру структуры в .NET-коде.
Ref<T>
а используетеstring
вместо этого, не то чтобы это должно иметь значение.