【问题标题】:MemberwiseClone equivalent to an existing object?MemberwiseClone 等效于现有对象?
【发布时间】:2013-08-07 20:37:33
【问题描述】:

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

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

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

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

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

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

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

没有 cmets 询问这是否真的有必要或谈论过早的优化。

感谢您的帮助。

回答

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

【问题讨论】:

  • 你的是类还是结构?它包含什么?只有原始类型或对象?
  • 您假设 MemberwiseClone() 做的不是“抓取整个内存块”。它没有。问题是你只是创造了太多的垃圾。大量克隆肯定会做到这一点。
  • @HansPassant 这正是我假设 MemberwiseClone 所做的。这就是我希望能够在不创建克隆到新对象所创建的垃圾的情况下做的事情。

标签: c# performance memory-management


【解决方案1】:

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

【讨论】:

  • 他可以让类的属性“代理”结构体的字段,只使用结构体直接复制到一个新的类中。
  • 看来值得一试
  • @xanatos:这是基本思想。不过需要注意的一点是,访问嵌套结构 fields 是有效的,无论结构有多大,但访问结构 properties 的效率可能非常低。例如,如果Quad<T1> 是具有四个T1 类型的公开字段的公开字段结构,并且vQuad<Quad<Quad<Quad<Int32>>>> 类型的可变变量(保存256 个整数),那么int temp = v.v1.v3.v4.v1 将是一个整数加载,但如果 v 是属性或 readonly 变量,则该表达式将...
  • ...必须复制v(1024 字节),然后复制v.v1(256 字节),然后复制v.v1.v3(64 字节),然后v1.v3.v4 的副本(16 字节),然后才能最终读取所需的整数。如果仔细使用大型结构可能非常有效,但如果让系统插入隐式复制操作则效率非常低。
  • @WillCalderwood:也许人们在提到基准时可以使用一些首字母缩略词,以表明他们知道它们在调试模式下毫无用处。 >:*3 “可变结构是邪恶的”口头禅忽略了这样一个事实,即暴露字段结构通常可以让人们编写比任何替代方案都更快、更清晰的代码,尽管我希望有一种简单的方法来表明一个类型应该将封装结构的成员视为自己的 [无需手动转发每个成员]。
【解决方案2】:

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

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

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

从那里打开一个新世界......您可以直接逐字节复制您的课程内容,其中包含一些不安全的内容(作为unsafe 键盘和 unsafe 作为“正在运行- with-scissors") 代码。

但要使其正常工作,您的课程必须是 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);
        }
    }
}

【讨论】:

  • 我认为您误解了这个问题。我正在尝试重用对象,但我需要一种快速的方法将属性从一个对象复制到另一个对象。
  • @WillCalderwood 已更改回复
  • 谢谢。不幸的是,我在课堂上确实有非 blittable 类型。 :-(
猜你喜欢
  • 2014-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-23
  • 2016-03-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多