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