c#中类的内存对齐?

时间:2009-12-23 07:57:16

标签: c# memory

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

一些更新:

  • 这绝对是对齐问题

  • 有时对齐(无论出于什么原因?)是如此糟糕,以至于对双倍的访问速度比其最快访问速度慢50倍。

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

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


有没有办法在c#中记忆对齐类的分配?

以下演示(我认为!)没有正确对齐的双重错误。它对存储在类中的double执行一些简单的数学计算,对每次运行进行计时,在变量上运行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();
        }
    }
}

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

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

谢谢, 保罗。

6 个答案:

答案 0 :(得分:6)

为了证明.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();
        }

    }
}

答案 1 :(得分:4)

也许StructLayoutAttribute正是您要找的?

答案 2 :(得分:4)

有趣地看着运行机器的齿轮。我有一个问题,解释为什么当一个double只能以两种方式对齐时,有多个不同的值(我得到4)。我认为与CPU缓存行的对齐也起到了作用,尽管这只会增加3个可能的时间。

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

如果它很关键,那么你可以用Marshal.AllocCoTaskMem()分配非托管内存,并使用一个你可以正确对齐的不安全指针。如果为使用SIMD指令的代码分配内存,则必须执行相同的操作,它们需要16字节对齐。考虑一下这是绝望的举动。

答案 3 :(得分:0)

使用struct而不是class,使时间不变。还考虑使用StructLayoutAttribute。它有助于指定结构的精确内存布局。对于CLASSES,我认为你没有任何保证他们如何在记忆中安排。

答案 4 :(得分:0)

您无法控制.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

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

答案 5 :(得分:0)

它将正确对齐,否则您将在x64上获得对齐异常。我不知道你的代码片段显示了什么,但我不会说任何与它对齐的内容。