x64 位 Azure 机器上 DateTimeOffset 的平均大小是多少?

What is the average size of a DateTimeOffset on a x64 bit Azure machine?

我一直在尝试获取用于此目的的 DateTimeOffset struct in my code so that I can compute the size of a parent object. The problem is that neither the sizeof operator nor the Marshal.SizeOf 函数的大小。

sizeof 将无法工作,因为我必须使用不安全标志进行编译,而此功能不足以作为这样做的理由。 Marshal.SizeOf 抛出异常:

Type 'System.DateTimeOffset' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed.

我已经放弃尝试像这样计算结构的大小,因为 attack/blog post 的每一行都会出现一个或另一个错误。

谁能告诉我 64 位 Azure Web 服务器上 DateTimeOffset 的平均大小是多少?

占用10个字节

查看 source,它有一个 Int16 和一个 DateTimeDateTime 有一个 UInt64.

我不知道为什么Marshal.SizeOf不能测量它。

接受的答案不正确,它忽略了对齐。结构的大小不仅仅是其成员的总和。可能需要在字段之间以及结构末尾添加额外的 space 以帮助处理器有效读取字段并实现 .NET 内存模型提供的原子性保证。

DateTimeOffset 更加复杂,编写 DateTimeOffset 结构的 Microsoft 程序员犯了一个大错误,开始使用 copy/pasting DateTime 结构进行编码。它有一个历史错误,对 DateTimeOffset 来说很重要,因为它有两个字段而不是一个。它使用 LayoutKind.Auto 而不是顺序。在 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 > Memory > 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  ................

您可以很容易地看到偏移字段(60 = 0x003c) 和日期时间字段。我调整了 window 的大小,使这 2 个元素变得明显,它在您的机器上看起来不会那么干净 :) 但只需计算字节直到它们重复,DateTimeOffset 需要 16 个字节 在 64 位模式下。

32 位和 64 位模式之间的大小不同这一事实通常会让您很担心。这个错误永远不需要修复,DateTimeOffset 没有合理的互操作故事,它永远不会匹配等效的非托管类型。它是 WinRT(又名 UWP)中指定的互操作类型,但 CLR 中内置的语言投影隐藏了这个问题。

这并不完全可靠(并且假设数组开销是恒定的,这在理智的条件下可能是正确的),但下面是根据经验回答这个问题的简单方法。如果出于某种原因您希望在缺少调试器的环境中进行测试,这种方法可能适用于健全性检查:

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)。

请注意,此类测试最好作为独立应用程序进行;更复杂的应用程序更有可能在 RAM 使用方面发生不可预测的变化。