【问题标题】:Memory alignment of classes in c#?c#中类的内存对齐?
【发布时间】:2010-12-29 09:46:27
【问题描述】:

(顺便说一句。这是指 32 位操作系统)

一些更新:

  • 这绝对是对齐问题

  • 有时对齐(无论出于何种原因?)非常糟糕,以致于访问 double 比其最快访问慢 50 倍以上。

  • 在 64 位机器上运行代码可以减少问题,但我认为它仍然在两个时间之间交替(通过在 32 位机器上将双精度更改为浮点数可以获得类似的结果)

  • 在单声道下运行代码没有问题——微软,你有机会从那些 Novell 家伙那里复制一些东西吗???


有没有办法在 c# 中对类的分配进行内存对齐?

以下演示(我认为!)没有正确对齐双打的坏处。它对存储在类中的双精度进行一些简单的数学运算,对每次运行进行计时,在变量上运行 5 次定时运行,然后再分配一个新的并重新执行。

基本上,结果看起来你要么有一个快速、中等或慢速的内存位置(在我的旧处理器上,这些最终每次运行大约 40、80 或 120 毫秒)

我尝试过使用 StructLayoutAttribute,但没有任何乐趣 - 也许还有其他事情发生?

class Sample
{
    class Variable { public double Value; }

    static void Main()
    {
        const int COUNT = 10000000;
        while (true)
        {
            var x = new Variable();
            for (int inner = 0; inner < 5; ++inner)
            {
                // move allocation here to allocate more often so more probably to get 50x slowdown problem
                var stopwatch = Stopwatch.StartNew();

                var total = 0.0;
                for (int i = 1; i <= COUNT; ++i)
                {
                    x.Value = i;
                    total += x.Value;
                }
                if (Math.Abs(total - 50000005000000.0) > 1)
                    throw new ApplicationException(total.ToString());

                Console.Write("{0}, ", stopwatch.ElapsedMilliseconds);
            }
            Console.WriteLine();
        }
    }
}

所以我看到很多关于互操作结构对齐的网页,那么类对齐呢?

(或者我的假设是错误的,上面还有另一个问题?)

谢谢, 保罗。

【问题讨论】:

  • 我无法复制您的问题(在带有框架 3.5 和 4b2 的 x64 上)。所有通道的速度都差不多,除了非常偶然的慢通道,大概是 GC 正在做的事情。
  • 这是在 32 位机器上 - 我尝试了几台不同的机器,结果相同。我会在 64 位机器上给它一个毛刺......
  • 你如何确定问题是对齐问题,而不是其他东西(如 gc 或类似)?
  • @nos - 证据是:相同的测试以相同的分配运行 5 次,并获得相同的时间结果。然后我分配一个新的并重复;有不同的时间。只是把钉子钉在棺材里(无论如何在我的脑海里)是下面发布的 AlignedNew 代码(它固定对象以便它可以通过这种方式获取地址)。
  • 无法在 VS2010、Win7 32 位上回购。始终在 50 毫秒左右。

标签: c# memory


【解决方案1】:

为了证明 .NET 中堆上对象未对齐的概念,您可以运行以下代码,您会发现现在它总是运行得很快。请不要拍我,它只是一个 PoC,但如果你真的关心性能,你可以考虑使用它;)

public static class AlignedNew
{
    public static T New<T>() where T : new()
    {
        LinkedList<T> candidates = new LinkedList<T>();
        IntPtr pointer = IntPtr.Zero;
        bool continue_ = true;

        int size = Marshal.SizeOf(typeof(T)) % 8;

        while( continue_ )
        {
            if (size == 0)
            {
                object gap = new object();
            }

            candidates.AddLast(new T());

            GCHandle handle = GCHandle.Alloc(candidates.Last.Value, GCHandleType.Pinned);
            pointer = handle.AddrOfPinnedObject();
            continue_ = (pointer.ToInt64() % 8) != 0 || (pointer.ToInt64() % 64) == 24;

            handle.Free();

            if (!continue_)
                return candidates.Last.Value;
        }

        return default(T);
    }
}

class Program
{

    [StructLayoutAttribute(LayoutKind.Sequential)]
    public class Variable
    {
        public double Value;
    }

    static void Main()
    {

        const int COUNT = 10000000;

        while (true)
        {

            var x = AlignedNew.New<Variable>();


            for (int inner = 0; inner < 5; ++inner)
            {

                var stopwatch = Stopwatch.StartNew();

                var total = 0.0;
                for (int i = 1; i <= COUNT; ++i)
                {
                    x.Value = i;
                    total += x.Value;
                }
                if (Math.Abs(total - 50000005000000.0) > 1)
                    throw new ApplicationException(total.ToString());


                Console.Write("{0}, ", stopwatch.ElapsedMilliseconds);
            }
            Console.WriteLine();
        }

    }
}

【讨论】:

  • :) 以这种方式创建新对象时会造成 100 倍的开销 :) 没那么邪恶。
  • 当然邪恶但很有创意。然而,这仅部分有效。如果 GC 决定稍后移动您的对象,那么您可能会看到性能下降。没有办法阻止这种情况。同样对于大型 T 对象或对象数组,这可能会导致严重的内存分配问题。
  • 它是固定的,所以它不会被GC重新定位
  • 不要忘记[FixedAddressValueType] 属性,它会将struct 浮动到 GC 堆中(通过装箱),然后在持续时间内将其固定。可能有用...
【解决方案2】:

有趣的看看运行机器的齿轮。当双精度只能以两种方式对齐时,我有一点问题可以解释为什么有多个不同的值(我得到了 4 个)。我认为与 CPU 缓存线的对齐也很重要,尽管这只加起来最多 3 个可能的时序。

好吧,您对此无能为力,CLR 仅承诺对齐 4 字节值,以保证 32 位机器上的原子更新。这不仅仅是托管代码的问题,C/C++ has this problem too。看起来芯片制造商需要解决这个问题。

如果它很关键,那么您可以使用 Marshal.AllocCoTaskMem() 分配非托管内存并使用可以正确对齐的不安全指针。如果您为使用 SIMD 指令的代码分配内存,则必须做同样的事情,它们需要 16 字节对齐。虽然这是一个绝望的举动。

【讨论】:

  • 听起来像是一些有趣的计划!明天我可能会采取绝望的行动;)
【解决方案3】:

也许StructLayoutAttribute 是您正在寻找的?

【讨论】:

  • 啊,我应该阅读文档!它确实说这也适用于课程的布局! (愚蠢的属性名称!)我会给它一个树瘤......
  • 玩这些字段似乎对结果没有影响?也许问题是由其他原因引起的?
  • 该属性只影响非托管代码使用的类型的布局。
【解决方案4】:

使用结构而不是类,使时间恒定。还可以考虑使用 StructLayoutAttribute。它有助于指定结构的确切内存布局。对于 CLASSES,我认为您无法保证它们在内存中的布局方式。

【讨论】:

  • 我在分配课程(在帖子中说过)。我将这些类嵌入到 lambda 函数中,我希望能够在其中更改值,而我不能用结构来做到这一点。
【解决方案5】:

它将正确对齐,否则您会在 x64 上得到对齐异常。我不知道你的 sn-p 显示了什么,但我不会说任何关于对齐的内容。

【讨论】:

  • 进一步调查证明这完全是对齐问题。
  • 我不相信这一点,除非我们对对齐有不同的定义。我对对齐的定义是,原语必须驻留在其大小的倍数的地址上(例如,32 位数字的地址必须能被 4 整除,等等)。我不敢相信对象并不总是正确对齐,如果您在 x86 上设置某些处理器标志,并且始终在 x64 上设置,如果您使用未对齐的值,则会出现异常。然后是缓存问题,但这是一个单独的问题。
  • 导致您的问题的一种更可能的原因是,有时您的线程会被抢占。
  • 我立即排除了缓存/线程,因为这是使用相同分配的对象运行计时 5 次的全部目的。你看过贴出来的 AlignNew 代码吗?它检查以查看在 mod 8 上对齐发生在哪个字节边界上(即每 64 位),并根据它落在哪里,我们得到描述的性能特征。
【解决方案6】:

您无法控制 .NET 如何在内存中布置您的类。

因为其他人已经说了structlayoutattribute可以用来强迫结构的特定的存储器布局请注意,这是针对c / c ++互操作的目的,而不是尝试微调.NET 应用程序的性能。

如果您担心内存对齐问题,那么 C# 可能是错误的语言选择。


编辑 - 破解 WinDbg 并查看在 32 位 Vista 和 .NET 2.0 上运行上述代码的堆。

注意:我没有得到上面所示的定时的变化。

0:003> !dumpheap -type Sample+Variable
 Address       MT     Size
01dc2fec 003f3c48       16     
01dc54a4 003f3c48       16     
01dc58b0 003f3c48       16     
01dc5cbc 003f3c48       16     
01dc60c8 003f3c48       16     
01dc64d4 003f3c48       16     
01dc68e0 003f3c48       16     
01dc6cd8 003f3c48       16     
01dc70e4 003f3c48       16     
01dc74f0 003f3c48       16     
01dc78e4 003f3c48       16     
01dc7cf0 003f3c48       16     
01dc80fc 003f3c48       16     
01dc8508 003f3c48       16     
01dc8914 003f3c48       16     
01dc8d20 003f3c48       16     
01dc912c 003f3c48       16     
01dc9538 003f3c48       16     
total 18 objects
Statistics:
      MT    Count    TotalSize Class Name
003f3c48       18          288 TestConsoleApplication.Sample+Variable
Total 18 objects
0:003> !do 01dc9538 
Name: TestConsoleApplication.Sample+Variable
MethodTable: 003f3c48
EEClass: 003f15d0
Size: 16(0x10) bytes
 (D:\testcode\TestConsoleApplication\bin\Debug\TestConsoleApplication.exe)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
6f5746e4  4000001        4        System.Double  1 instance 1655149.000000 Value

在我看来,除非我看错了,否则类的分配地址似乎是对齐的?

【讨论】:

  • 我认为这是一个非常悲伤的评论。我同意,如果你想要超级骗子的性能,我们都不会写 CUDA 或其他任何东西,但如果只是愚蠢的话,可以接受一个明显的次标准实现,即如何以正常方式处理浮点数。
  • 有趣。我一直在使用 .net 3,5 编译的 32 位 XP 上玩(在许多不同的机器上,从我的旧家用戴尔 D800 笔记本电脑到我目前相对较新的台式机正在工作)并且已经看到了这个问题他们都是。我将不得不在 vista 上给它一个树瘤??
  • +1 如果您担心内存对齐问题,那么 C# 可能是错误的语言选择。为工作使用正确的工具。 C++ 或 ASM。
猜你喜欢
  • 1970-01-01
  • 2017-06-02
  • 2012-11-13
  • 2011-10-16
  • 2015-09-24
  • 2013-02-02
  • 1970-01-01
  • 2018-07-22
  • 1970-01-01
相关资源
最近更新 更多