【问题标题】:Performance of DeepClone (using binary serialization) versus manually setting propertiesDeepClone(使用二进制序列化)与手动设置属性的性能
【发布时间】:2012-03-13 13:19:19
【问题描述】:

我有一个对象集合,我正在尝试克隆这个集合并尝试了解不同方法的性能影响。

集合中的对象有大约 20 个属性,所有字符串、整数、浮点数(该对象内部没有任何嵌套对象)。这两种方法是:

  1. 创建 DeepClone() 方法:

    public static class ExtensionMethods
    {
        public static T DeepClone<T>(this T a)
        {
           using (var stream = new MemoryStream())
           {
               var formatter = new BinaryFormatter();
               formatter.Serialize(stream, a);
               stream.Position = 0;
              return (T)formatter.Deserialize(stream);
           }
       }
    

    }

  2. 手动编写“复制”代码,在其中循环遍历集合并“新建”一个新对象,然后手动设置所有 20 个属性。像这样的

     public MyObject Copy(MyObject myObj)
    {
     var obj = new MyObject();
     obj.Prop1 = myObj.Prop1;
     obj.Prop2 = myObj.Prop2;
     return obj;
    

    }

我得到的结果非常不一致,所以我想得到人们的反馈:

  1. 一个应该比另一个快得多吗?我会想到选择二,但我的测试似乎不支持这一点,所以我想弄清楚我是否做错了什么。

  2. 有什么方法可以更快地做到这一点?

【问题讨论】:

  • 当您只想执行浅拷贝时,我不确定您为什么要标记此 DeepClone()。由于字符串是不可变的,因此字符串的浅拷贝等同于深拷贝。此外,由于 BinaryFormatter 使用反射来获取属性名称、setter、构造函数等,因此手动设置必须快得多。
  • @Gelno - 如果对象内部有嵌套对象,此方法将深度克隆对象(因此得名)

标签: c# performance clone


【解决方案1】:

首先,BinaryFormatter 路由肯定要慢一些,因为它使用反射来获取/设置属性。最常见的方法是结合使用 IClonable 接口和复制构造函数。

class A : ICloneable
{
    private readonly int _member;

    public A(int member)
    {
        _member = member;
    }

    public A(A a)
    {
        _member = a._member;
    }

    public object Clone()
    {
        return new A(this);
    }
}

当然严格来说你只需要复制构造函数,这应该是最快的方法。如果您的对象很简单,您应该尝试使用内置的MemberwiseClone 函数。

class A : ICloneable
{
    private readonly int _member;

    public A(int member)
    {
        _member = member;
    }

    public object Clone()
    {
        return MemberwiseClone();
    }
}

同时,我写了some test code 来查看 MemberwiseClone() 是比使用复制构造函数快得多还是慢得多。你可以找到它here。我发现 MemberwiseClone 实际上比 CopyConstructor 慢得多,至少在小类上是这样。请注意,使用 BinaryFormatter 非常慢。

【讨论】:

  • MemberwiseClone 不使用反射吗?或者它只是一个 memcpy,GC 不需要任何进一步的帮助?
  • 我认为它使用了一些 CLR 钩子来获得性能,所以基本上是 memcpy 和一些调整。
  • 在 MemberwiseClone 的帮助中提到它会进行浅拷贝,是否与使用 BinaryFormatter 相同?我很怀疑,因为那个函数被命名为 DeepCopy,我猜这个名字是不正确的。我的观点是深度克隆也应该克隆引用的对象???
  • @Sam - 这个方法是通用的并且在其他地方使用但是如果有嵌套对象它也会克隆它们
  • @Sam,OP在标题中指定了DeepCopy,但后来提到他只有浅对象要复制,所以MemberwiseClone()比BinaryFormatter效率高很多。
【解决方案2】:

在我之前的角色中,我们研究了这个问题,因为我们正在缓存对象并希望在将它们从缓存中分发出来之前克隆它们。

我们做了一些详细的基准测试,发现属性设置总是至少比 BinaryFormatter 方法快一个数量级,尽管显然需要手动实现而不是更简单的方法BinaryFormatter 方法。对于深度对象图,IIRC 的差异变得更加明显。

最后,我们决定采用三管齐下的“克隆”方法:

  • 如果类型是不可变的(我们将使用标记接口IImutable 表示,但您同样可以使用属性或类似的东西),我们将通过返回原始实例来“克隆”。因为我们知道没有人可以改变它,所以继续返回同一个实例是安全的。显然这是最快的“克隆”类型,尽管显然根本不是真正的克隆。
  • 如果该类型实现了我们自己的IDeepCloneable&lt;T&gt; 接口(类似于您的第二个示例 - 但通用),我们将使用它。 [此泛型接口将继承自非泛型等价物IDeepCloneable]
  • 否则,我们将退回到您的第一个示例,BinaryFormatter

我提到“不可变”的方法是因为根据您正在做的事情,有时最好的方法是重新设计您需要克隆的类,这样它们就根本不需要被克隆。如果它们在创建后基本上是只读的,这很容易;但即使不是,有时 builder/immutable-type 方法也是有用的(参见框架中的 UriUriBuilder。前者本质上是不可变的,而后者可用于构建和/或改变前)。

【讨论】:

    【解决方案3】:

    为此使用序列化是一个非常聪明的想法。但是,逐个设置属性应该一样快,并且大多数时候(对象非常小时除外)必须更快 - 您所做的工作更少,并且不使用反射。

    您能否重现使用序列化比“手动”复制属性更快的情况?

    【讨论】:

      【解决方案4】:

      序列化肯定比属性分配要慢。

      二进制序列化更可取,因为代码易于维护。

      相反,属性分配更好,因为可能在克隆期间,您不想克隆对象的所有属性,但希望能够顺便序列化它们。简而言之,您可以更好地控制流量。

      【讨论】:

        猜你喜欢
        • 2010-11-08
        • 1970-01-01
        • 1970-01-01
        • 2017-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-02
        • 2013-11-04
        相关资源
        最近更新 更多