У 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
, або an Int64
і a 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
натомість використовуєте не те, що має змінити.