假设你需要一个你需要经常迭代的整数列表/数组,我的意思是非常频繁。原因可能有所不同,但是它说它是高容量处理的最内循环的核心。
一般来说,由于其大小的灵活性,人们会选择使用列表(列表)。最重要的是,msdn文档声称列表在内部使用数组,并且应该执行速度快(使用Reflector快速查看确认这一点)。不用说,有一些开销。
有没有人真正测量过这个?会在列表中迭代6M次,与数组相同吗?
答案 0 :(得分:197)
非常容易衡量......
在少数紧密循环处理代码中,我知道长度是固定的我使用数组进行微小优化;数组可以略微更快如果你使用索引器/表单 - 但IIRC认为它取决于数组中的数据类型。但除非您需要进行微优化,否则请保持简单并使用List<T>
等。
当然,这只适用于您阅读所有数据的情况;对于基于密钥的查找,字典会更快。
这是我使用“int”的结果(第二个数字是校验和来验证它们都做了同样的工作):
(已修改以修复错误)
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();
}
}
答案 1 :(得分:51)
答案 2 :(得分:24)
我认为表现非常相似。 使用List vs a Array时所涉及的开销是,当你向列表添加项目时,恕我直言,当列表必须增加它在内部使用的数组的大小时,当达到数组的容量时。 / p>
假设您有一个容量为10的List,那么一旦您想要添加第11个元素,List就会增加它的容量。 您可以通过将列表的容量初始化为它将保留的项目数来降低性能影响。
但是,为了弄清楚迭代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();
在我的系统上;迭代数组需要33毫秒;迭代列表需要66毫秒。
说实话,我没想到变化会那么多。 所以,我把迭代放在一个循环中:现在,我执行两次迭代1000次。 结果是:
迭代列表需要67146毫秒 迭代数组需要40821毫秒
现在,变化不再那么大,但仍然......
因此,我启动了.NET Reflector,并且List类的索引器的getter看起来像这样:
public T get_Item(int index)
{
if (index >= this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
return this._items[index];
}
正如您所看到的,当您使用List的索引器时,List会检查您是否未超出内部数组的范围。这笔额外的支票需要支付费用。
答案 3 :(得分:19)
如果你只是从任一个中获取单个值(不在循环中),那么两者都进行边界检查(你在托管代码中记得)它只是列表执行两次。 稍后请参阅说明,了解为什么这可能不是什么大问题。
如果您使用自己的(int int i = 0; i&lt; x。[Length / Count]; i ++),那么关键区别如下:
如果您使用的是foreach,那么关键区别如下:
边界检查通常没什么大不了的(特别是如果你在具有深度管道和分支预测的cpu上 - 这些日子大多数都是常规)但只有你自己的分析可以告诉你这是否是一个问题。 如果你在代码的一部分中避免堆分配(很好的例子是库或哈希码实现),那么确保变量被输入为List而不是IList将避免这个陷阱。 如果重要的话一如既往。
答案 4 :(得分:11)
[另见this question ]
我修改了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();
}
}
答案 5 :(得分:2)
测量结果很好,但是根据您在内循环中所做的事情,您会得到明显不同的结果。衡量你自己的情况。如果您正在使用多线程,那么这就是一项非常重要的活动。
答案 6 :(得分:2)
实际上,如果你在循环中执行一些复杂的计算,那么数组索引器与列表索引器的性能可能会非常小,最终,这并不重要。
答案 7 :(得分:2)
请勿尝试通过增加元素数来增加容量。
<强>性能强>
List For Add: 1ms
Array For Add: 2397ms
Stopwatch watch;
#region --> List For Add <--
List<int> intList = new List<int>();
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 60000; rpt++)
{
intList.Add(rand.Next());
}
watch.Stop();
Console.WriteLine("List For Add: {0}ms", watch.ElapsedMilliseconds);
#endregion
#region --> Array For Add <--
int[] intArray = new int[0];
watch = Stopwatch.StartNew();
int sira = 0;
for (int rpt = 0; rpt < 60000; rpt++)
{
sira += 1;
Array.Resize(ref intArray, intArray.Length + 1);
intArray[rpt] = rand.Next();
}
watch.Stop();
Console.WriteLine("Array For Add: {0}ms", watch.ElapsedMilliseconds);
#endregion
答案 8 :(得分:1)
这是一个使用字典,IEnumerable:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
static class Program
{
static void Main()
{
List<int> list = new List<int>(6000000);
for (int i = 0; i < 6000000; i++)
{
list.Add(i);
}
Console.WriteLine("Count: {0}", list.Count);
int[] arr = list.ToArray();
IEnumerable<int> Ienumerable = list.ToArray();
Dictionary<int, bool> dict = list.ToDictionary(x => x, y => true);
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 Ienumerable)
{
chk += i;
}
}
Console.WriteLine("Ienumerable/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
foreach (int i in dict.Keys)
{
chk += i;
}
}
Console.WriteLine("Dict/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);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
foreach (int i in Ienumerable)
{
chk += i;
}
}
watch.Stop();
Console.WriteLine("Ienumerable/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
foreach (int i in dict.Keys)
{
chk += i;
}
}
watch.Stop();
Console.WriteLine("Dict/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
Console.ReadLine();
}
}
答案 9 :(得分:1)
我担心其他答案中发布的基准测试仍然会为编译器留出空间来优化,消除或合并循环,所以我写了一个:
结果是,直接数组的性能比访问IList中包含的数组的性能高出约250%:
以下是代码:
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;
}
答案 10 :(得分:0)
由于List&lt;&gt;在内部使用数组,基本性能应该是相同的。有两个原因,列表可能会稍微慢一些:
要检查它是否对您有任何影响,最好将发布的计时功能调整为您计划使用的大小列表,并查看特殊情况的结果。
答案 11 :(得分:0)
由于我有类似的问题,这让我起步很快。
我的问题更具体一点,“反射数组实现的最快方法是什么”
Marc Gravell所做的测试显示了很多,但不完全是访问时间。他的时机包括循环数组和列表。因为我想出了一个我想要测试的第三种方法,一个'字典',只是为了比较,我扩展了hist测试代码。
Firts,我使用常量进行测试,这给了我一定的时间,包括循环。这是一个“裸”时间,不包括实际访问。 然后我通过访问主题结构进行测试,这给了我和'开销包括'时间,循环和实际访问。
'裸'时序和'开销无效'时序之间的差异让我看到了“结构访问”时间。
但这个时间有多准确?在测试期间,窗口会做一些时间切片以获得舒尔。我没有关于时间切片的信息,但我认为它在测试期间是均匀分布的并且是几十毫秒的量级,这意味着定时的精度应该在+/- 100毫秒左右的量级。有点粗略估计?无论如何,系统性误差的来源。
此外,测试是在“调试”模式下完成的,没有优化。否则,编译器可能会更改实际的测试代码。
所以,我得到两个结果,一个用于常量,标记为'(c)',一个用于访问标记'(n)',差异'dt'告诉我实际访问需要多长时间。
这就是结果:
Dictionary(c)/for: 1205ms (600000000)
Dictionary(n)/for: 8046ms (589725196)
dt = 6841
List(c)/for: 1186ms (1189725196)
List(n)/for: 2475ms (1779450392)
dt = 1289
Array(c)/for: 1019ms (600000000)
Array(n)/for: 1266ms (589725196)
dt = 247
Dictionary[key](c)/foreach: 2738ms (600000000)
Dictionary[key](n)/foreach: 10017ms (589725196)
dt = 7279
List(c)/foreach: 2480ms (600000000)
List(n)/foreach: 2658ms (589725196)
dt = 178
Array(c)/foreach: 1300ms (600000000)
Array(n)/foreach: 1592ms (589725196)
dt = 292
dt +/-.1 sec for foreach
Dictionary 6.8 7.3
List 1.3 0.2
Array 0.2 0.3
Same test, different system:
dt +/- .1 sec for foreach
Dictionary 14.4 12.0
List 1.7 0.1
Array 0.5 0.7
对时序误差的更好估计(如何消除由于时间切片引起的系统测量误差?)可以说更多的结果。
看起来List / foreach具有最快的访问速度,但开销正在扼杀它。
List / for和List / foreach之间的区别是stange。也许涉及一些兑现?
此外,对于访问数组,使用for
循环或foreach
循环无关紧要。时间结果及其准确性使结果“可比”。
使用字典是迄今为止最慢的,我只考虑它,因为在左侧(索引器)我有一个稀疏的整数列表,而不是这个测试中使用的范围。
以下是修改后的测试代码。
Dictionary<int, int> dict = new Dictionary<int, int>(6000000);
List<int> list = new List<int>(6000000);
Random rand = new Random(12345);
for (int i = 0; i < 6000000; i++)
{
int n = rand.Next(5000);
dict.Add(i, n);
list.Add(n);
}
int[] arr = list.ToArray();
int chk = 0;
Stopwatch watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
int len = dict.Count;
for (int i = 0; i < len; i++)
{
chk += 1; // dict[i];
}
}
watch.Stop();
long c_dt = watch.ElapsedMilliseconds;
Console.WriteLine(" Dictionary(c)/for: {0}ms ({1})", c_dt, chk);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
int len = dict.Count;
for (int i = 0; i < len; i++)
{
chk += dict[i];
}
}
watch.Stop();
long n_dt = watch.ElapsedMilliseconds;
Console.WriteLine(" Dictionary(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
int len = list.Count;
for (int i = 0; i < len; i++)
{
chk += 1; // list[i];
}
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine(" List(c)/for: {0}ms ({1})", c_dt, chk);
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();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine(" List(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
for (int i = 0; i < arr.Length; i++)
{
chk += 1; // arr[i];
}
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine(" Array(c)/for: {0}ms ({1})", c_dt, 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();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Array(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
foreach (int i in dict.Keys)
{
chk += 1; // dict[i]; ;
}
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Dictionary[key](c)/foreach: {0}ms ({1})", c_dt, chk);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
foreach (int i in dict.Keys)
{
chk += dict[i]; ;
}
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Dictionary[key](n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
foreach (int i in list)
{
chk += 1; // i;
}
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine(" List(c)/foreach: {0}ms ({1})", c_dt, chk);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
foreach (int i in list)
{
chk += i;
}
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine(" List(n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
foreach (int i in arr)
{
chk += 1; // i;
}
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine(" Array(c)/foreach: {0}ms ({1})", c_dt, chk);
chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
foreach (int i in arr)
{
chk += i;
}
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Array(n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);
答案 12 :(得分:0)
在一些简短的测试中,我发现两者的组合在我称之为合理密集的数学方面更好:
输入:List<double[]>
时间:00:00:05.1861300
输入:List<List<double>>
时间:00:00:05.7941351
输入:double[rows * columns]
时间:00:00:06.0547118
运行代码:
int rows = 10000;
int columns = 10000;
IMatrix Matrix = new IMatrix(rows, columns);
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int r = 0; r < Matrix.Rows; r++)
for (int c = 0; c < Matrix.Columns; c++)
Matrix[r, c] = Math.E;
for (int r = 0; r < Matrix.Rows; r++)
for (int c = 0; c < Matrix.Columns; c++)
Matrix[r, c] *= -Math.Log(Math.E);
stopwatch.Stop();
TimeSpan ts = stopwatch.Elapsed;
Console.WriteLine(ts.ToString());
我希望我们有一些顶级硬件加速矩阵类,就像.NET团队使用System.Numerics.Vectors
类一样!
C#可能是最好的ML语言,在这个领域有更多的工作!