[英]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.