2016-08-02 2 views
1

Я пытался получить размер структуры DateTimeOffset в моем коде, чтобы я мог вычислить размер родительского объекта. Проблема в том, что ни оператор sizeof, ни функция Marshal.SizeOf не работают для этой цели.Каков средний размер DateTimeOffset на x64-разрядной машине Azure?

sizeof не работает, потому что я должен скомпилировать с небезопасным флагом, и этой функции недостаточно для обоснования этого. Marshal.SizeOf бросает исключение:

Тип «System.DateTimeOffset» не может быть выстраивали как неуправляемый структуры; никакие значимые размеры или смещение не могут быть вычислены.

Я отказался от попыток вычислить размер такой структуры, потому что каждая строка сообщения об атаке/блоге возникает с той или иной ошибкой.

Может ли кто-нибудь сказать мне, что средний размер DateTimeOffset находится на 64-битном веб-сервере Azure?

ответ

9

Принятый ответ неправильный, он игнорирует выравнивание. Размер структуры - это не просто сумма ее членов. Может потребоваться дополнительное пространство между полями и концом структуры, чтобы помочь процессору эффективно считывать поле и реализовывать гарантии атомарности, предоставляемые моделью памяти .NET.

Это сложная функция для DateTimeOffset, программа Microsoft, которая написала структуру DateTimeOffset, сделала большую ошибку, начав код путем копирования/вставки структуры DateTime. Которая имеет историческую ошибку, которая имеет большое значение для DateTimeOffset, поскольку она имеет два поля вместо одного. Он использует LayoutKind.Auto вместо Sequential. Легко видны в Reference Source

Это дает свободу действий CLR, чтобы организовать поля оптимальным в любом режиме он работает. В 32-битном режиме он не выравнивать Int64 8 байт, как это обычно , но до 4 байтов. Это добавляет меньше полей между полями, размер которых составляет 12 байтов.

Аналогично, в 64-битном режиме ему нравится выравнивать поля до 8. Это создает больше полей между полями.

Единственный хороший способ увидеть это с помощью отладчика. Выполнить этот бит кода:

static void Main(string[] args) { 
     var arr = new DateTimeOffset[] { 
      new DateTimeOffset(0x123456789abcdef0, TimeSpan.FromMinutes(60)), 
      new DateTimeOffset(0x123456789abcdef0, TimeSpan.FromMinutes(60)), 
     }; 
     System.Diagnostics.Debugger.Break(); 
    } 

Когда хиты точки останова, используйте Debug> Windows> Память> Memory1 и тип &arr[0] в поле Адрес, чтобы посмотреть на содержимое массива. Вы увидите что-то похожее на:

0x00000115DC4FBA30 3c 00 00 00 00 00 00 00 f0 76 f8 38 70 56 34 12 <.......ðvø8pV4. 
0x00000115DC4FBA40 3c 00 00 00 00 00 00 00 f0 76 f8 38 70 56 34 12 <.......ðvø8pV4. 
0x00000115DC4FBA50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 

Вы можете легко увидеть Offset поле (60 = 0x003c) и поле DateTime.Я изменил размер окна, сделав 2 элемента очевидными, он не будет выглядеть так чисто на вашей машине :) Но просто пересчитайте байты до тех пор, пока они не повторятся, DateTimeOffset принимает 16 байтов в 64-битном режиме.

Тот факт, что размер отличается между 32-битным и 64-битным режимами, должен в целом беспокоить вас. Эта ошибка в противном случае никогда не требовалась для исправления, нет разумной истории взаимодействия для DateTimeOffset, она никогда не соответствует эквивалентному неуправляемому типу. Это назначенный тип взаимодействия в WinRT (иначе UWP), но языковая проекция, встроенная в CLR, скрывает проблему.

1

Он занимает 10 байт.

Рассматривая source, он имеет Int16 и DateTime; DateTime имеет номер UInt64.

Я понятия не имею, почему Marshal.SizeOf не может его измерить.

1

Это не совсем достоверно (и предполагает, что накладные расходы массива постоянны, что, вероятно, верно при нормальных условиях), но ниже это простой способ ответить на этот вопрос эмпирически. Если по какой-то причине вы хотите проверить на окружающую среду, которая испытывает недостаток в отладчик, этот подход может быть хорошо для проверки вменяемости:

void Main() 
{ 
    PrintSize<DateTimeOffset>(); 
} 

public static void PrintSize<T>() 
{ 
    GC.Collect(); 
    long gc1 = GC.GetTotalMemory(true); 
    const int sz = 100000; 
    T[] foo = new T[sz]; 
    long gc2 = GC.GetTotalMemory(true); 
    GC.KeepAlive(foo); 
    Console.WriteLine($"{(gc2 - gc1)/(double)sz} bytes per {typeof(T)}"); 
} 

Когда я тестировал в 32 бита, напечатанные 12 (+/- 0,001).
Когда я протестировал 64-разрядную версию, она напечатала 16 (+/- 0,001).

Обратите внимание, что этот тест лучше всего выполнять как отдельное приложение; более сложное приложение, скорее всего, будет иметь непредсказуемые изменения в использовании ОЗУ.

+0

+1 Спасибо за решение моей оригинальной проблемы. Я думал об этом в своем веб-приложении, но я не мог гарантировать, что он был потокобезопасным, поскольку могут возникать многочисленные запросы. –

+0

В какой-то степени вы можете уменьшить чувствительность этого трюка к другим влияниям за счет увеличения sz. Однако этот код временно сжигает кусок памяти, поэтому он не идеален для использования в производстве. Однако вы можете просто развернуть новый экземпляр Azure, запустить код, а затем отбросить экземпляр. Во всяком случае, подход Ганса намного лучше, когда вам нужны надежные, точные цифры. Тем не менее, это подход, который я обычно использую, когда хочу оценить влияние новой функции приложения на память (в основном, для прогнозирования потребления памяти по порядку величины). – Brian

Смежные вопросы