【问题标题】:Adding clones to a list is slow将克隆添加到列表中很慢
【发布时间】:2019-03-02 03:03:49
【问题描述】:

我正在尝试加快循环,即克隆 2_500_000 个对象。 克隆本身在整个循环中需要 800 毫秒,但是当我将它们添加到列表中时,它需要 3 秒..

List<T> list = new List<T>();

Stopwatch sw = new Stopwatch();
sw.Start();

foreach(T entity in listSource)
{
    T entityCloned = GetEntityClone(entity); // Taking 800ms totally

    if (entityCloned != null)
        list.Add(entityCloned);
}

sw.Stop();

你能帮我找出为什么那些Adds 花了这么多时间吗?

【问题讨论】:

  • 试试list.AddRange(listSource.Select(s =&gt; GetEntityClone(s)).Where(s =&gt; s != null));
  • 如果克隆成为null 的唯一方法是原始本身是null,那么您可能想改写listSource.Where(s =&gt; s != null).Select(s =&gt; GetEntityClone(s))(在克隆前过滤)
  • List&lt;T&gt; list = new List&lt;T&gt;(2500000); 为了防止内存重新分配
  • 什么是 T?一个类,结构?类通过引用传递。将第 N 个引用插入 List 不会花费很长时间。但是必须复制结构。将结构插入列表需要复制GetEntityClone() 返回的结构。复制大型结构需要时间
  • 简单的new List&lt;T&gt;(listSource.Count) 怎么样?如果静态数组可以降低 33% 的性能,那么给列表一个初始容量也可以达到同样的效果。

标签: c# list performance object clone


【解决方案1】:

不幸的是,循环很多东西和深度复制对象需要时间。我认为 3 秒不一定是不合理的时间。

但是您可以提高速度。

首先,如果您知道结果列表需要保存多少项目,您可以set the internal capacity beforehand 以防止列表必须调整大小。调整大小是一项昂贵的活动,必要时可以避免。这可以通过手动更改列表的容量属性或将容量作为constructor argument for the list 传递来完成。

一旦分配了容量,添加到列表的the complexity 应该是O(1),不需要重新设置(这是一个O(n) 复杂性任务see this answer)。在这种情况下,添加到列表中不太可能成为瓶颈。

您还可以从您要预先复制的初始列表中删除空值,以消除每次都必须评估的 if 语句的需要。使用linq

var noNulls = listSource.where(o =&gt; o != null)

【讨论】:

  • 不幸的是,我无法过滤空值,因为我的克隆方法是自行过滤的,我已经尝试通过提供源列表的大小来设置内部容量,但它根本没有改变..
  • 将您的克隆方法添加到问题中可能会很有用,这样我们就可以看到那里发生了什么。你能做到吗?
  • OP 提到深度复制不会花费大量时间,但添加到列表中会。有人可能会争辩说 OP 犯了错误并且没有正确测量,但这应该在直接解决深度克隆问题之前进行调查。
  • 好点。我确实认为添加到列表中不太可能成为瓶颈,特别是如果 OP 尝试添加容量。
【解决方案2】:

我通过使用数组而不是列表节省了一些时间(大约 33%):

MyObject 类定义:

public class MyObject
{
    public int Id { get; set; }
    public bool Flag { get; set; }

    public static MyObject GetEntityClone(MyObject obj)
    {
        if (obj == null) return null;

        var newObj = new MyObject()
        {
            Id = obj.Id,
            Flag = obj.Flag
        };

        return newObj;
    }
}

代码:

var sourceList = new List<MyObject>();

// let's mock the source data, every 27th element will be null
for (int i = 0; i < 2500000; ++i)
{
    if (i % 27 != 0)
        sourceList.Add(new MyObject { Id = i, Flag = (i % 2 == 0) });
}

var destArray = new MyObject[2500000];

Stopwatch sw = new Stopwatch();
sw.Start();
Console.WriteLine(sw.ElapsedMilliseconds);

var currentElement = 0;
for (int i = 0; i < sourceList.Count; ++i)
{
    MyObject entityCloned = MyObject.GetEntityClone(sourceList[i]);

    if (entityCloned != null)
        destArray[currentElement++] = entityCloned;
}

var result = new MyObject[currentElement];
Array.Copy(destArray, 0, result, 0, currentElement);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

【讨论】:

    【解决方案3】:

    尝试以下并行工作:

    ConcurrentBag<T> list = new ConcurrentBag<T>();
    Parallel.ForEach(listSource, entity =>
    {
        T entityCloned = GetEntityClone(entity); //Taking 800ms totally
        if (entityCloned != null)
            list.Add(entityCloned);
    
    });
    
    var listVersion = list.ToList();
    

    【讨论】:

      猜你喜欢
      • 2015-12-06
      • 1970-01-01
      • 1970-01-01
      • 2012-02-01
      • 1970-01-01
      • 2021-03-18
      • 2021-12-24
      • 2015-09-23
      • 2014-12-21
      相关资源
      最近更新 更多