簡體   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