В 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, что я хотел бы, чтобы поля были упакованы без указания постоянного смещения поля?