更高效的克隆方法

14

我想创建深拷贝方法,并找到了三种执行方法

1-逐个传递每个属性进行深拷贝

2-使用反射

3-使用序列化

请问它们中哪一种在性能方面最好?

8个回答

16

首选项是手动深度复制您的值,这将具有远高于其他选项的性能。

反射会引入相当多的开销,因为访问数据相对较慢。

序列化会增加很大的成本,因为它将数据序列化到临时结构中,然后再反转该过程进行设置。这也是非常慢的。

选项2或3唯一的优点是它可能更容易实现,并可重用于多种类型。而第一种选项必须针对每种类型手动编写,但比选项3更快(并且在内存使用效率方面更高)。


1
可能是有人错过了箭头 :D - Lukasz Madon
@HABJAN:在那个时候,您不再使用反射 - 而是引入第四个选项 - 反射来处理代码生成以直接进行复制。然而,它仍然永远不会像直接复制一样快。 - Reed Copsey
@Reed Copsey:我同意这不会像直接复制那样快,但它会比标准反射更快,并且可以节省编码时间。 - HABJAN
@HABJAN:这取决于情况- 如果只有一个类,那么手动处理可能会更快。另外,如果您不经常执行此克隆操作,则运行时编译的开销可能比反射成本的节省更高。因为该选项具有非常大的一次性费用,但后续使用费用较低,所以会涉及许多问题。 - Reed Copsey
@HABJAN:IL生成基本上是创建一个委托来执行手动复制的操作。初始生成会比较慢,但一旦计算并缓存,它就可以非常快(几乎和手动复制一样快,但不完全相同)。如果你要做成千上万次这样的操作,额外的开销最终会被抵消。(那里的时间有些问题,顺便说一下,因为它们忽略了JIT问题,这就是为什么第一个结果是误导性的原因...如果OP把“手动”放在最后,它仍然会更快。) - Reed Copsey
显示剩余3条评论

16

我制作了一个图表,比较了三种方法以及一种表达式树的方法。

在此输入图片描述

对于大量对象,反射速度快5倍,而手动编写代码和表达式树速度快20倍,优化性能最好的是手动编写代码和表达式树。

用于克隆的代码链接(2.-4. 是扩展方法):

  1. 手动编写代码:手工编写,没有链接。
  2. 序列化克隆
  3. 反射克隆
  4. 表达式树克隆


7

将你列出的可能解决方案按正确的性能顺序排序。

当您编写代码手动克隆每个属性值时,您将获得最佳性能。

反射将产生与手动克隆类似的结果,但速度稍慢。

序列化是最糟糕的情况。但实现最快。

这是一篇描述其他可能解决方案的好文章。

所以这里是所有可能的克隆方法列表:

  1. 手动克隆
  2. 使用MemberwiseClone克隆
  3. 使用反射克隆
  4. 使用序列化克隆
  5. 使用IL克隆
  6. 使用扩展方法克隆

我个人会选择"使用IL克隆",因为它比反射略快,并且您不必手动克隆所有内容。



2
最佳性能的方法是在您的代码中创建克隆。因此,使用方式“1”。

1

这里有一个ICloneable接口。如果要克隆一个实现了 ICloneable 接口的对象,使用它的方法是最好的解决方案。


1
他在询问如何实现它。 - SLaks

0
作为 CGbR 的作者,我想邀请你尝试一下它是否适用于你的情况。
你只需要拥有 NuGet 包 和实现 ICloneable 接口的部分类定义。生成器将会自动生成一个文件和一个 Clone(bool deep) 方法。
public partial class Root : ICloneable
{
    public Root(int number)
    {
        _number = number;
    }
    private int _number;

    public Partial[] Partials { get; set; }

    public IList<ulong> Numbers { get; set; }

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

    private Root()
    {
    }
} 

public partial class Root
{
    public Root Clone(bool deep)
    {
        var copy = new Root();
        // All value types can be simply copied
        copy._number = _number; 
        if (deep)
        {
            // In a deep clone the references are cloned 
            var tempPartials = new Partial[Partials.Length];
            for (var i = 0; i < Partials.Length; i++)
            {
                var value = Partials[i];
                value = value.Clone(true);
                tempPartials[i] = value;
            }
            copy.Partials = tempPartials;
            var tempNumbers = new List<ulong>(Numbers.Count);
            for (var i = 0; i < Numbers.Count; i++)
            {
                var value = Numbers[i];
                tempNumbers[i] = value;
            }
            copy.Numbers = tempNumbers;
        }
        else
        {
            // In a shallow clone only references are copied
            copy.Partials = Partials; 
            copy.Numbers = Numbers; 
        }
        return copy;
    }
}

性能:在我们工作中需要克隆的基准测试中,我们将其与DataContractSerializerMemoryStream进行了比较。生成的代码快了600倍。


0

看起来你已经做好了找到解决方案的艰苦工作,现在你需要在特定情况下测试它们所有,并找出最好的方案。

大部分情况下,这真的取决于你所序列化的数据的种类。


0

反射可以用于生成DynamicMethod,这比手动复制更有效(自动属性可以通过直接访问字段来复制,跳过作用域并绕过可见性检查)。 DynamicMethod为您提供了一个委托,您可以将其保存在静态只读字段中以克隆对象。这是快速简便的方法,但不一定是最干净的方法。 序列化速度慢且不适合。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接