C# 将列表克隆到现有列表中,最大限度地减少内存重新分配的最有效方法是什么?

C# 将列表克隆到现有列表中,最大限度地减少内存重新分配的最有效方法是什么?,c#,performance,list,memory-management,clone,C#,Performance,List,Memory Management,Clone,我需要将一个现有列表克隆到另一个现有列表中。由于环境要求非常高的性能,我需要消除不必要的内存重新分配 我能想到的最有效的算法是以下算法,它将根据需要增加目标列表的容量以匹配源列表,但不会减少其自身的容量。这是本项目可接受的行为 public static void CloneInto(this List<T> source, List<T> destination) { if (destination.Capacity < sourc

我需要将一个现有列表克隆到另一个现有列表中。由于环境要求非常高的性能,我需要消除不必要的内存重新分配

我能想到的最有效的算法是以下算法,它将根据需要增加目标列表的容量以匹配源列表,但不会减少其自身的容量。这是本项目可接受的行为

    public static void CloneInto(this List<T> source, List<T> destination)
    {
        if (destination.Capacity < source.Capacity)
        {
            /* Increase capacity in destination */
            destination.Capacity = source.Capacity;
        }

        /* Direct copy of items within the limit of both counts */
        for (var i = 0; i < source.Count && i < destination.Count; i++)
        {
            destination[i] = source[i];
        }

        if (source.Count > destination.Count)
        {
            /* Append any extra items from source */
            for (var i = destination.Count; i < source.Count; i++ )
            {
                destination.Add(source[i]);
            }
        } 
        else if (source.Count < destination.Count)
        {
            /* Trim off any extra items from destination */
            while (destination.Count > source.Count)
            {
                destination.RemoveAt(destination.Count - 1);
            }
        }
    }
然而,这似乎有很多代码、逻辑和循环

有没有更有效的方法将列表克隆到现有列表中,同时避免不必要的内存分配?

为什么不直接使用

destination = source.ToList();
ToList创建列表的一个副本,之前在目标中的所有内容都将在分配后立即准备好进行垃圾收集

if (destination.Capacity < source.Capacity)
{
    /* Increase capacity in destination */
    destination.Capacity = source.Capacity;
}
这样,如果容量发生变化,就不需要复制任何元素。请注意,我使用的是Capacity=Count,因为这在短期内可以节省内存,但在长期内可能会导致内存的多次分配。请注意,我正在对照source.Count进行检查,但对于容量分配,我使用了相同的source.Capacity。如果多次调用该方法,这将最大限度地减少目标的重新分配

小型优化:

if (destination.Capacity < source.Count)
{
    /* Increase capacity in destination */
    destination.Capacity = 0;
    destination.Capacity = source.Capacity;
}
但是通过查看,我认为它比需要的慢,因为它首先将元素复制到T[]itemsToInsert=new T[count];,所以它会复制两个元素

然后:

可以优化为:

destination.RemoveRange(source.Count, destination.Count - source.Count);

性能小测试:

public static List<T> CloneInto<T>(List<T> source, List<T> destination)
{
    if (destination.Capacity < source.Capacity)
    {
        /* Increase capacity in destination */
        destination.Capacity = source.Capacity;
    }

    /* Direct copy of items within the limit of both counts */
    for (var i = 0; i < source.Count && i < destination.Count; i++)
    {
        destination[i] = source[i];
    }

    if (source.Count > destination.Count)
    {
        /* Append any extra items from source */
        for (var i = destination.Count; i < source.Count; i++)
        {
            destination.Add(source[i]);
        }
    }
    else if (source.Count < destination.Count)
    {
        /* Trim off any extra items from destination */
        while (destination.Count > source.Count)
        {
            destination.RemoveAt(destination.Count - 1);
        }
    }

    return destination;
}


static void Main(string[] args)
{
    List<string> list1 = new List<string>();
    List<string> list2 = new List<string>();

    for (int i = 0; i < 100000; i++)
    {
        list1.Add(Guid.NewGuid().ToString());                
    }

    Stopwatch s = new Stopwatch();

    s.Start();
    CloneInto(list1, list2);

    s.Stop();

    Console.WriteLine("Your Method: " + s.Elapsed.Ticks);

    s.Reset();
    s.Start();

    list2 = list1.ToList();

    s.Stop();

    Console.WriteLine("ToList() Method: " + s.Elapsed.Ticks);

    Console.ReadKey();
结果:


但是,如果它仅仅是关于内存的,那么您的方法比.ToList更好,而且您也无法做更多的工作来提高性能。可能您可以使用类似parallel.for的并行循环,但对此不确定。

您在您的环境中测试过a.Concatb吗?我不确定.Concat如何适合此环境?当源代码有更多项时,我想我可以。Concat尾部部分作为destination.Concatsource.Skipdestination.Count-这就是你的意思吗?这似乎是函数签名中的错误设计。你确定这是强制性的吗?例如,将destination.Capacity设置为大于当前值的值会导致重新分配和复制列表,其中包含不需要的、立即被覆盖的值。如果签名将返回目的地列表,您只需创建一个副本,并在容量不够大时返回。您是否测量/基准了任何内容?这些名单一般有多大?这看起来像是一个不明智的优化。不要把时间花在你们并没有遇到的问题上。若原始性能很重要,为什么不放弃列表并使用t[]?运行时直接支持基于零的一维数组,即向量,而List是建立在该数组之上的类型,因此可能会有一些开销,无论多么小。阅读问题,他希望将集合移动到现有集合。LInsoDeTeh,是的,这在功能上达到了目的,但会导致额外的GC工作,在destination.Capacity>=source.Capacity的场景中,它将导致不必要的内存分配。不是最佳性能和内存使用。我已经阅读了问题。它确实会做到这一点。代码完成后,目标将具有与源完全相同的条目,与代码相同的结果。@BrendanHill-对于中等大小的列表,GC将以这种方式减少工作量。@LInsoDeTeh在该行代码完成后,目标将有一个新引用,指向一个新对象,函数返回后,该引用将在以后被删除并进行垃圾收集。用于在外部函数调用中传递原始引用的变量仍然具有对原始未修改列表的引用。你的答案是错误的。我想你是想比较TestPosith.Studio.Engest.Cube和Stest-Test.Pult=源代码。在中间代码片段中计数。@ Amit我决定不应用评论的那部分。我已经解释了原因。在将来,将容量设置为计数还是容量将成为一个因素,取决于该函数的使用方式。你可能是对的,也可能是错的,不进行分析。也就是说,不管发生什么,if条件都是错误的,因为if destination.Capacity足够容纳source.Count。没有理由重新分配。@Amit我明白你的意思了。。。我同意你的批评。在source.Count>destination.Capacity的情况下,最有效的方法可能就是destination=source.ToList。
while (destination.Count > source.Count)
{
    destination.RemoveAt(destination.Count - 1);
}
destination.RemoveRange(source.Count, destination.Count - source.Count);
public static List<T> CloneInto<T>(List<T> source, List<T> destination)
{
    if (destination.Capacity < source.Capacity)
    {
        /* Increase capacity in destination */
        destination.Capacity = source.Capacity;
    }

    /* Direct copy of items within the limit of both counts */
    for (var i = 0; i < source.Count && i < destination.Count; i++)
    {
        destination[i] = source[i];
    }

    if (source.Count > destination.Count)
    {
        /* Append any extra items from source */
        for (var i = destination.Count; i < source.Count; i++)
        {
            destination.Add(source[i]);
        }
    }
    else if (source.Count < destination.Count)
    {
        /* Trim off any extra items from destination */
        while (destination.Count > source.Count)
        {
            destination.RemoveAt(destination.Count - 1);
        }
    }

    return destination;
}


static void Main(string[] args)
{
    List<string> list1 = new List<string>();
    List<string> list2 = new List<string>();

    for (int i = 0; i < 100000; i++)
    {
        list1.Add(Guid.NewGuid().ToString());                
    }

    Stopwatch s = new Stopwatch();

    s.Start();
    CloneInto(list1, list2);

    s.Stop();

    Console.WriteLine("Your Method: " + s.Elapsed.Ticks);

    s.Reset();
    s.Start();

    list2 = list1.ToList();

    s.Stop();

    Console.WriteLine("ToList() Method: " + s.Elapsed.Ticks);

    Console.ReadKey();