简体   繁体   English

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

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

I have a need to clone an existing list into another, existing list. 我需要将现有列表克隆到另一个现有列表中。 As the environment demands very high performance, I need to eliminate unnecessary memory reallocation. 由于环境要求非常高的性能,因此我需要消除不必要的内存重新分配。

The most efficient algorithm I can think of is the following, which will increase the capacity of the destination list to match the source one as required, but will not decrease its own. 我能想到的最有效的算法是以下算法,它将增加目标列表与所需源列表相匹配的能力,但不会减少其自身。 (This is acceptable behavior for this project.) (这是该项目可接受的行为。)

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

However this seems a lot of code, logic and loops. 但是,这似乎有很多代码,逻辑和循环。

Is there a more efficient way to clone a list into an existing list while avoiding unnecessary memory allocations? 有没有一种更有效的方法将列表克隆到现有列表中,同时避免不必要的内存分配?

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

This is probably wrong... The source.Capacity could be bigger than necessary... 这可能是错误的...来源。 source.Capacity可能超出了必要...

And you are "copying" the elements already contained in destination to a "new" destination "buffer". 然后,您正在将destination已经包含的元素“复制”到“新” destination “缓冲区”中。 This copy is unnecessary because then the elements of destination are discarded 此副本是不必要的,因为这样将丢弃destination元素

So you should at least: 因此,您至少应:

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

In this way, if the Capacity is changed, no elements need to be copied (note that I'm using the Capacity = Count , because while this would economize memory in the short term, in the long term it could cause multiple allocations of memory). 这样,如果更改了Capacity ,则无需复制任何元素(请注意,我正在使用Capacity = Count ,因为虽然这将在短期内节省内存,但从长远来看可能会导致内存的多次分配)。 Note that I'm checking against the source.Count , but for the assignment of Capacity I'm using the same source.Capacity . 请注意,我正在检查source.Count ,但是对于Capacity的分配,我使用的是同一source.Capacity This to minimize reallocations of destination if the method is called multiple times. 如果多次调用该方法,则可以最大程度地减少destination重新分配。

Small optimization: 小优化:

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

This because List.Clear() uses Array.Clear() , so it really zeroes the internal elements, while destination.Capacity = 0 is optimized to simply reassign the internal array to a static _emptyArray . 这是因为List.Clear()使用Array.Clear() ,所以它实际上将内部元素destination.Capacity = 0 ,而destination.Capacity = 0被优化为仅将内部数组重新分配给static _emptyArray

You could even try: 您甚至可以尝试:

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

but by looking at the source code , I think it is slower than necessary (because it first copies the elements to T[] itemsToInsert = new T[count]; , so it does two copies of the elements... 但是通过查看源代码 ,我认为它的速度比必要的慢(因为它首先将元素复制到T[] itemsToInsert = new T[count];所以它会复制两个元素...

Then: 然后:

while (destination.Count > source.Count)
{
    destination.RemoveAt(destination.Count - 1);
}

can be optimized to: 可以优化为:

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

Little test on perfomance : 性能测试:

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

result: 结果:

测试

But if it is all about memory only - your method is better than .ToList() and there isn't much you can do to improve performance even more. 但是,如果仅涉及内存,则您的方法要比.ToList()更好,并且您可以做更多的事情来提高性能。 May be you can use parallel loops like parallel.for but not sure about that. 也许您可以使用parallel.for这样的并行循环,但是不确定。

Why don't you just use 你为什么不只用

destination = source.ToList();

ToList() creates a copy of the list and everything which was in destination before, will immediately be ready for garbage collection after the assignment. ToList()创建列表的副本,之前位于目标位置的所有内容将在分配后立即准备好进行垃圾回收。

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

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