2

这里有很多关于 MemberwiseClone 的问题,但我找不到确切的内容。

据我了解,MemberwiseClone 基本上只是复制一个对象的内存区域,将其转储到其他地方并将其称为该对象的新实例。显然非常快,对于大型对象,这是制作副本的最快方法。对于具有简单构造函数的小对象,创建实例并复制各个属性会更快。

现在,我有一个使用 MemberwiseClone 的紧密循环。由于 MemberwiseClone 总是创建一个新实例,这会导致大量内存分配和重新分配,这对性能不利。我为一个非常大的对象编写了一个自定义副本,它将所有属性单独复制到现有对象上,总体上比使用 MemberwiseClone 快一点,

我觉得如果我可以抓取整个内存块并将其转储到现有对象上,我将获得不错的性能提升,因为 GC 不必担心任何事情。希望我也能摆脱这条消息:

澄清一下,我想尽快将属性从一个现有对象复制到另一个对象。我只需要一个浅拷贝。

消息 2 DA0023:(平均)% GC 时间 = 19.30;% Time in GC 相对较高。这种垃圾收集开销过多的迹象可能会影响应用程序的响应能力。您可以收集 .NET 内存分配数据和对象生存期信息,以更好地了解应用程序使用的内存分配模式。

我对代码安全没有限制,速度是这里游戏的目标。

请不要评论询问这是否真的有必要或谈论过早的优化。

谢谢你的帮助。

回答

采取下面的建议,在对象中嵌入一个结构并将所有属性存储在其中。然后我可以获取该结构的副本以在单个分配中复制所有属性。与逐个字段复制相比,这产生了超过 50% 的速度提升,并且比使用 MemberwiseClone 每次创建新对象的速度提高了约 60%。

4

2 回答 2

4

如果您的类不可继承,则可以将类的整个状态存储在公开字段结构中(公开字段,而不是属性!)。这将要求您在所有字段名称前加上结构名称,这在源代码中会很丑陋,但访问该结构中的字段将与访问直接放置在封闭类中的字段一样快。但是,这样做将使您能够使用简单的赋值语句将结构从一个对象实例复制到另一个对象实例中。

于 2013-08-06T20:22:14.803 回答
2

一般来说,你不能比逐个字段地复制更快。

这个一般。有一个例外:如果您的类是 blittable (因此不包含对非 blittable 类的任何引用)并且它是用 [StructLayout(LayoutKind.Sequential)](或[StructLayout(LayoutKind.Explicit)],但测试更复杂)声明的,那么您可以固定它

GCHandle handle = GCHandle.Alloc(refToYourClass, GCHandleType.Pinned);

unsafe从那里打开一个新世界......您可以使用一些不安全的(作为键盘和不安全的“用剪刀运行”)代码直接逐字节复制课程的内容。

但要让这个工作,你的课程必须是 blittable !

最快的方法是UnsafeCopy8复制 8 字节的块(32 位和 64 位)。最快的 PInvoke 方法是memcpy.

[StructLayout(LayoutKind.Sequential)]
class MyClass
{
    public int A { get; set; }
    public int B { get; set; }
    public int C { get; set; }
    public int D { get; set; }
    public int E { get; set; }
    public int F { get; set; }
    public int G { get; set; }
    public byte H { get; set; }
}

class Program
{
    [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
    static extern IntPtr memcpy(IntPtr dest, IntPtr src, UIntPtr count);

    [DllImport("kernel32.dll", SetLastError = false)]
    static extern void CopyMemory(IntPtr dest, IntPtr src, UIntPtr count);

    static void Main()
    {
        MyClass mc = new MyClass { A = 1, B = 2, C = 3, D = 4, E = 5, F = 6, G = 7, H = 8 };
        MyClass mc2 = new MyClass();

        int size = Marshal.SizeOf(typeof(MyClass));

        var gc = GCHandle.Alloc(mc, GCHandleType.Pinned);
        var gc2 = GCHandle.Alloc(mc2, GCHandleType.Pinned);

        IntPtr dest = gc2.AddrOfPinnedObject();
        IntPtr src = gc.AddrOfPinnedObject();

        // Pre-caching
        memcpy(dest, src, (UIntPtr)size);
        CopyMemory(dest, src, (UIntPtr)size);
        UnsafeCopy(dest, src, size);
        UnsafeCopy8(dest, src, size);

        int cycles = 10000000;

        Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

        var sw1 = Stopwatch.StartNew();

        for (int i = 0; i < cycles; i++)
        {
            memcpy(dest, src, (UIntPtr)size);
        }

        sw1.Stop();

        var sw2 = Stopwatch.StartNew();

        for (int i = 0; i < cycles; i++)
        {
            CopyMemory(dest, src, (UIntPtr)size);
        }

        sw2.Stop();

        var sw3 = Stopwatch.StartNew();

        for (int i = 0; i < cycles; i++)
        {
            UnsafeCopy(dest, src, size);
        }

        sw3.Stop();

        var sw4 = Stopwatch.StartNew();

        for (int i = 0; i < cycles; i++)
        {
            UnsafeCopy8(dest, src, size);
        }

        sw4.Stop();

        gc.Free();
        gc2.Free();

        Console.WriteLine("IntPtr.Size: {0}", IntPtr.Size);
        Console.WriteLine("memcpy:      {0}", sw1.ElapsedTicks);
        Console.WriteLine("CopyMemory:  {0}", sw2.ElapsedTicks);
        Console.WriteLine("UnsafeCopy:  {0}", sw3.ElapsedTicks);
        Console.WriteLine("UnsafeCopy8: {0}", sw4.ElapsedTicks);
        Console.ReadKey();
    }

    static unsafe void UnsafeCopy(IntPtr dest, IntPtr src, int size)
    {
        while (size >= sizeof(int))
        {
            *((int*)dest) = *((int*)src);

            dest = (IntPtr)(((byte*)dest) + sizeof(int));
            src = (IntPtr)(((byte*)src) + sizeof(int));
            size -= sizeof(int);
        }

        if (size >= sizeof(short))
        {
            *((short*)dest) = *((short*)src);

            dest = (IntPtr)(((byte*)dest) + sizeof(short));
            src = (IntPtr)(((byte*)src) + sizeof(short));
            size -= sizeof(short);
        }

        if (size == sizeof(byte))
        {
            *((byte*)dest) = *((byte*)src);
        }
    }

    static unsafe void UnsafeCopy8(IntPtr dest, IntPtr src, int size)
    {
        while (size >= sizeof(long))
        {
            *((long*)dest) = *((long*)src);

            dest = (IntPtr)(((byte*)dest) + sizeof(long));
            src = (IntPtr)(((byte*)src) + sizeof(long));
            size -= sizeof(long);
        }

        if (size >= sizeof(int))
        {
            *((int*)dest) = *((int*)src);

            dest = (IntPtr)(((byte*)dest) + sizeof(int));
            src = (IntPtr)(((byte*)src) + sizeof(int));
            size -= sizeof(int);
        }

        if (size >= sizeof(short))
        {
            *((short*)dest) = *((short*)src);

            dest = (IntPtr)(((byte*)dest) + sizeof(short));
            src = (IntPtr)(((byte*)src) + sizeof(short));
            size -= sizeof(short);
        }

        if (size == sizeof(byte))
        {
            *((byte*)dest) = *((byte*)src);
        }
    }
}
于 2013-08-06T17:48:16.293 回答