繁体   English   中英

将列表克隆到现有列表中的最有效方法是最大程度地减少内存重新分配?

[英]Most efficient way to clone a list into an existing list, minimizing memory reallocation?

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

我能想到的最有效的算法是以下算法,它将增加目标列表与所需源列表相匹配的能力,但不会减少其自身。 (这是该项目可接受的行为。)

    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);
            }
        }
    }

但是,这似乎有很多代码,逻辑和循环。

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

if (destination.Capacity < source.Capacity)
{
    /* Increase capacity in destination */
    destination.Capacity = source.Capacity;
}

这可能是错误的...来源。 source.Capacity可能超出了必要...

然后,您正在将destination已经包含的元素“复制”到“新” destination “缓冲区”中。 此副本是不必要的,因为这样将丢弃destination元素

因此,您至少应:

if (destination.Capacity < source.Count)
{
    /* Increase capacity in destination */
    destination.Clear();
    destination.Capacity = source.Capacity;
}

这样,如果更改了Capacity ,则无需复制任何元素(请注意,我正在使用Capacity = Count ,因为虽然这将在短期内节省内存,但从长远来看可能会导致内存的多次分配)。 请注意,我正在检查source.Count ,但是对于Capacity的分配,我使用的是同一source.Capacity 如果多次调用该方法,则可以最大程度地减少destination重新分配。

小优化:

if (destination.Capacity < source.Count)
{
    /* Increase capacity in destination */
    destination.Capacity = 0;
    destination.Capacity = source.Capacity;
}

这是因为List.Clear()使用Array.Clear() ,所以它实际上将内部元素destination.Capacity = 0 ,而destination.Capacity = 0被优化为仅将内部数组重新分配给static _emptyArray

您甚至可以尝试:

public static void CloneInto(this List<T> source, List<T> destination)
{
    destination.Capacity = 0; // or destination.Clear();
    destination.AddRange(source);
}

但是通过查看源代码 ,我认为它的速度比必要的慢(因为它首先将元素复制到T[] itemsToInsert = new T[count];所以它会复制两个元素...

然后:

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();

结果:

测试

但是,如果仅涉及内存,则您的方法要比.ToList()更好,并且您可以做更多的事情来提高性能。 也许您可以使用parallel.for这样的并行循环,但是不确定。

你为什么不只用

destination = source.ToList();

ToList()创建列表的副本,之前位于目标位置的所有内容将在分配后立即准备好进行垃圾回收。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM