假设你需要有一个整数列表/数组,你需要频繁迭代,我的意思是非常频繁。原因可能各不相同,但可以说这是在一个大容量处理的最内部循环的核心。
通常情况下,人们会选择使用列表(List),因为它们在大小上具有灵活性。最重要的是,msdn文档声称列表在内部使用数组,并且应该执行得一样快(快速查看Reflector证实了这一点)。尽管如此,还是有一些开销。
有人测量过吗?在一个列表中迭代6M次是否与数组相同?
假设你需要有一个整数列表/数组,你需要频繁迭代,我的意思是非常频繁。原因可能各不相同,但可以说这是在一个大容量处理的最内部循环的核心。
通常情况下,人们会选择使用列表(List),因为它们在大小上具有灵活性。最重要的是,msdn文档声称列表在内部使用数组,并且应该执行得一样快(快速查看Reflector证实了这一点)。尽管如此,还是有一些开销。
有人测量过吗?在一个列表中迭代6M次是否与数组相同?
当前回答
实际上,如果在循环中执行一些复杂的计算,那么数组索引器与列表索引器的性能可能会非常小,最终,这无关紧要。
其他回答
static long[] longs = new long[500000];
static long[] longs2 = {};
static List<long> listLongs = new List<long> { };
static void Main(string[] args)
{
Console.CursorVisible = false;
Stopwatch time = new Stopwatch();
time.Start();
for (int f = 50000000; f < 50255000; f++)
{
listLongs.Add(f);
}
//List Time: 1ms Count : 255000
Console.WriteLine("List Time: " + time.ElapsedMilliseconds + " | Count: " + listLongs.Count());
time.Restart();
time.Start();
for (long i = 1; i < 500000; i++)
{
longs[i] = i * 200;
}
//Array Time: 2ms Length: 500000 (Unrealistic Data)
Console.WriteLine("Array Time: " + time.ElapsedMilliseconds + " | Length: " + longs.Length);
time.Restart();
time.Start();
for (int i = 50000000; i < 50055000; i++)
{
longs2 = longs2.Append(i).ToArray();
}
//Array Time: 17950ms Length: 55000
Console.WriteLine("Array Append Time: " + time.ElapsedMilliseconds + " | Length: " + longs2.Length);
Console.ReadLine();
}
Type | Time | Len |
---|---|---|
Array | 2ms | 500000 |
List | 1ms | 255000 |
Array Append | 17950ms | 55000 |
如果您计划不断地向数组中添加少量数据,那么list更快
这实际上取决于你将如何使用数组。
我担心在其他答案中发布的基准测试仍然会为编译器留下优化,消除或合并循环的空间,所以我写了一个:
使用不可预测的输入(随机) 运行计算结果并将结果打印到控制台 每次重复修改输入数据
结果是,直接数组的性能比访问封装在IList中的数组要好250%:
10亿次数组访问:4000毫秒 10亿次列表访问:10000毫秒 1亿个数组访问:350毫秒 1亿次列表访问:1000毫秒
代码如下:
static void Main(string[] args) {
const int TestPointCount = 1000000;
const int RepetitionCount = 1000;
Stopwatch arrayTimer = new Stopwatch();
Stopwatch listTimer = new Stopwatch();
Point2[] points = new Point2[TestPointCount];
var random = new Random();
for (int index = 0; index < TestPointCount; ++index) {
points[index].X = random.NextDouble();
points[index].Y = random.NextDouble();
}
for (int repetition = 0; repetition <= RepetitionCount; ++repetition) {
if (repetition > 0) { // first repetition is for cache warmup
arrayTimer.Start();
}
doWorkOnArray(points);
if (repetition > 0) { // first repetition is for cache warmup
arrayTimer.Stop();
}
if (repetition > 0) { // first repetition is for cache warmup
listTimer.Start();
}
doWorkOnList(points);
if (repetition > 0) { // first repetition is for cache warmup
listTimer.Stop();
}
}
Console.WriteLine("Ignore this: " + points[0].X + points[0].Y);
Console.WriteLine(
string.Format(
"{0} accesses on array took {1} ms",
RepetitionCount * TestPointCount, arrayTimer.ElapsedMilliseconds
)
);
Console.WriteLine(
string.Format(
"{0} accesses on list took {1} ms",
RepetitionCount * TestPointCount, listTimer.ElapsedMilliseconds
)
);
}
private static void doWorkOnArray(Point2[] points) {
var random = new Random();
int pointCount = points.Length;
Point2 accumulated = Point2.Zero;
for (int index = 0; index < pointCount; ++index) {
accumulated.X += points[index].X;
accumulated.Y += points[index].Y;
}
accumulated /= pointCount;
// make use of the result somewhere so the optimizer can't eliminate the loop
// also modify the input collection so the optimizer can merge the repetition loop
points[random.Next(0, pointCount)] = accumulated;
}
private static void doWorkOnList(IList<Point2> points) {
var random = new Random();
int pointCount = points.Count;
Point2 accumulated = Point2.Zero;
for (int index = 0; index < pointCount; ++index) {
accumulated.X += points[index].X;
accumulated.Y += points[index].Y;
}
accumulated /= pointCount;
// make use of the result somewhere so the optimizer can't eliminate the loop
// also modify the input collection so the optimizer can merge the repetition loop
points[random.Next(0, pointCount)] = accumulated;
}
[另见此问题]
我修改了Marc的答案,使用实际的随机数,在所有情况下都做同样的工作。
结果:
for foreach Array : 1575ms 1575ms (+0%) List : 1630ms 2627ms (+61%) (+3%) (+67%) (Checksum: -1000038876)
在VS 2008 SP1下编译为发行版。在Q6600@2.40GHz、. net 3.5 SP1上运行而不进行调试。
代码:
class Program
{
static void Main(string[] args)
{
List<int> list = new List<int>(6000000);
Random rand = new Random(1);
for (int i = 0; i < 6000000; i++)
{
list.Add(rand.Next());
}
int[] arr = list.ToArray();
int chk = 0;
Stopwatch watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
int len = list.Count;
for (int i = 0; i < len; i++)
{
chk += list[i];
}
}
watch.Stop();
Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
int len = arr.Length;
for (int i = 0; i < len; i++)
{
chk += arr[i];
}
}
watch.Stop();
Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
foreach (int i in list)
{
chk += i;
}
}
watch.Stop();
Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
foreach (int i in arr)
{
chk += i;
}
}
watch.Stop();
Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
Console.WriteLine();
Console.ReadLine();
}
}
我想表演会很相似。 在使用List和Array时所涉及的开销是,恕我直言,当您向列表中添加项时,当列表必须增加它在内部使用的数组的大小时,当数组的容量达到时。
假设你有一个容量为10的List,那么一旦你想添加第11个元素,List就会增加它的容量。 可以通过将列表的Capacity初始化为它将容纳的项数来减少性能影响。
但是,为了弄清楚遍历List是否与遍历数组一样快,为什么不对其进行基准测试呢?
int numberOfElements = 6000000;
List<int> theList = new List<int> (numberOfElements);
int[] theArray = new int[numberOfElements];
for( int i = 0; i < numberOfElements; i++ )
{
theList.Add (i);
theArray[i] = i;
}
Stopwatch chrono = new Stopwatch ();
chrono.Start ();
int j;
for( int i = 0; i < numberOfElements; i++ )
{
j = theList[i];
}
chrono.Stop ();
Console.WriteLine (String.Format("iterating the List took {0} msec", chrono.ElapsedMilliseconds));
chrono.Reset();
chrono.Start();
for( int i = 0; i < numberOfElements; i++ )
{
j = theArray[i];
}
chrono.Stop ();
Console.WriteLine (String.Format("iterating the array took {0} msec", chrono.ElapsedMilliseconds));
Console.ReadLine();
在我的系统上;遍历数组需要33msec;遍历列表花费了66msec。
说实话,我没想到变化会这么大。 所以,我把我的迭代放在一个循环中:现在,我执行了1000次迭代。 结果如下:
迭代List需要67146毫秒 迭代数组需要40821毫秒
现在,变化不再那么大了,但仍然……
因此,我已经启动了。net Reflector, List类的索引器的getter看起来像这样:
public T get_Item(int index)
{
if (index >= this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
return this._items[index];
}
如您所见,当您使用List的索引器时,List会执行一次检查,检查您是否没有超出内部数组的边界。这种额外的检查是有成本的。
很容易测量…
在少量的紧循环处理代码中,我知道长度是固定的,我使用数组来进行额外的微小优化;如果你使用索引器/ for表单,数组可以稍微快一点——但是IIRC认为这取决于数组中数据的类型。但除非你需要进行微观优化,否则尽量保持简单,使用List<T>等。
当然,这只适用于读取所有数据的情况;对于基于键的查找,字典会更快。
下面是我使用“int”的结果(第二个数字是一个校验和,以验证它们都做了相同的工作):
(修改bug)
List/for: 1971ms (589725196)
Array/for: 1864ms (589725196)
List/foreach: 3054ms (589725196)
Array/foreach: 1860ms (589725196)
基于试验台:
using System;
using System.Collections.Generic;
using System.Diagnostics;
static class Program
{
static void Main()
{
List<int> list = new List<int>(6000000);
Random rand = new Random(12345);
for (int i = 0; i < 6000000; i++)
{
list.Add(rand.Next(5000));
}
int[] arr = list.ToArray();
int chk = 0;
Stopwatch watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
int len = list.Count;
for (int i = 0; i < len; i++)
{
chk += list[i];
}
}
watch.Stop();
Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
for (int i = 0; i < arr.Length; i++)
{
chk += arr[i];
}
}
watch.Stop();
Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
foreach (int i in list)
{
chk += i;
}
}
watch.Stop();
Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
foreach (int i in arr)
{
chk += i;
}
}
watch.Stop();
Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
Console.ReadLine();
}
}