数组与列表的性能

假设你需要有一个整数列表/数组,你需要频繁迭代,我的意思是非常频繁。原因可能各不相同,但可以说这是在一个大容量处理的最内部循环的核心。

通常情况下,人们会选择使用列表(List),因为它们在大小上具有灵活性。最重要的是,msdn文档声称列表在内部使用数组,并且应该执行得一样快(快速查看Reflector证实了这一点)。尽管如此,还是有一些开销。

有人测量过吗?在一个列表中迭代6M次是否与数组相同?

233029 次浏览

很容易测量…

在少量的紧循环处理代码长度是固定的中,我使用数组来进行额外的微小优化;你可以使用索引器/作为表单-但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();
}
}
我认为表演会很相似。 在使用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 msec 迭代数组需要40821 msec

现在,变化不再那么大了,但仍然……

因此,我已经启动了。net Reflector, List类的索引器的getter看起来像这样:

public T get_Item(int index)
{
if (index >= this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
return this._items[index];
}

如您所见,当您使用List的索引器时,List会执行一次检查,检查您是否没有超出内部数组的边界。这种额外的检查是有成本的。

由于List<>在内部使用数组,因此基本性能应该是相同的。为什么这个列表可能会稍微慢一些,有两个原因:

  • 要在列表中查找元素,调用list方法,该方法在底层数组中进行查找。所以你需要一个额外的方法调用。另一方面,编译器可能会识别出这一点,并优化“不必要的”调用。
  • 如果编译器知道数组的大小,它可能会做一些特殊的优化,而对于一个未知长度的列表,它就不能这样做。如果列表中只有几个元素,这可能会带来一些性能改进。

要检查它是否对您有任何影响,最好将发布的计时函数调整为您计划使用的大小列表,并查看您的特殊情况的结果如何。

测量结果很好,但是根据您在内部循环中所做的具体操作,您将得到显著不同的结果。衡量你自己的情况。如果您正在使用多线程,那么这本身就不是一个简单的活动。

实际上,如果在循环中执行一些复杂的计算,那么数组索引器与列表索引器的性能可能会非常小,最终,这无关紧要。

如果你只是从其中一个中获得一个值(不是在循环中),那么两者都进行边界检查(你在托管代码中,记住),只是列表做了两次。

.

.

如果你正在使用你自己的for(int int i = 0;我& lt;x.[Length/Count];i++)则键差如下所示:

    <李>数组:
    • 边界检查被移除
    • 李< / ul > < / > <李>列表
      • 执行边界检查
      • 李< / ul > < / >

      如果你使用foreach,关键区别如下:

        <李>数组:
        • 没有分配对象来管理迭代
        • 边界检查被移除
        • 李< / ul > < / >
        • 通过已知为List的变量进行列表。
          • 迭代管理变量是堆栈分配的
          • 执行边界检查
          • 李< / ul > < / >
          • 通过已知为IList的变量进行列表。
            • 迭代管理变量是堆分配的
            • 执行边界检查 also Lists的值在foreach过程中不能被改变,而数组的值可以被改变 李< / ul > < / >
            边界检查通常不是什么大问题(特别是如果你在一个有深度管道和分支预测的cpu上——这是目前大多数情况下的常态),但只有你自己的分析才能告诉你这是否是一个问题。 如果你在代码中避免堆分配(很好的例子是库或hashcode实现),那么确保变量类型为List而不是IList将避免这个陷阱。

(参见这个问题)

我修改了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 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

这是一个使用字典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();
}
}

我担心在其他答案中发布的基准测试仍然会为编译器留下优化,消除或合并循环的空间,所以我写了一个:

  • 使用不可预测的输入(随机)
  • 运行计算结果并将结果打印到控制台
  • 每次重复修改输入数据

结果是,直接数组的性能比访问封装在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 Gravell所做的测试显示了很多,但并不是确切的访问时间。他的计时还包括对数组和列表的循环。因为我还提出了第三个我想测试的方法,一个“字典”,只是为了比较,我扩展了hist测试代码。

首先,我使用一个常数进行测试,这给了我一个包括循环在内的特定时间。这是一个“裸”计时,不包括实际访问。 然后我做了一个访问主题结构的测试,这给了我和“开销包括”时间,循环和实际访问。< / p >

“裸”计时和“开销包含”计时之间的差异给了我一个“结构访问”计时的指示。

但是这个时机有多准确呢?在测试窗口期间将为shure做一些时间切片。我没有关于时间切片的信息,但我假设它在测试期间是均匀分布的,在几十毫秒的数量级,这意味着计时的准确性应该在+/- 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之间的区别是奇怪的。也许涉及到兑现?

此外,对于数组的访问,使用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);

简短的回答:

在。net中,List<T>Array<T>具有相同的速度/性能,因为在。net中,ListArray的包装器。

再说一遍:List在里面是Array !在。net中,List<T>是来自其他语言的ArrayList<T>


详细说明在哪些情况下需要使用什么:

  • 数组需要使用:

    • 越频繁越好。它速度快,对相同数量的信息占用最小的RAM范围。
    • 如果你知道所需细胞的确切数量
    • 如果数据保存在数组<85000 b(对于整数数据,85000/32 = 2656个元素)
    • 如果需要高随机访问速度
  • 列表需要使用:

    • 如果需要将单元格添加到列表的末尾(通常)
    • 如果需要在列表的开始/中间添加单元格(不经常)
    • 如果数据保存在数组<85000 b(对于整数数据,85000/32 = 2656个元素)
    • 如果需要高随机访问速度
  • LinkedList需要使用:

    • 如果需要在列表的开始/中间/结尾添加单元格(通常)

    • 如果只需要顺序访问(向前/向后)

    • 如果你需要保存大型项目,但项目数量低。

    • 最好不要用于大量的项目,因为它会使用额外的内存链接。

      如果你不确定你需要LinkedList——你不需要它。

      只是不要使用它。


更多的细节:

color meaning

数组vs列表vs链表

更多细节:

https://stackoverflow.com/a/29263914/4423545 < a href = " https://stackoverflow.com/a/29263914/4423545 " > < / >

在一些简短的测试中,我发现两者的结合在我所谓的合理密集数学中会更好:

类型: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语言,只要在这方面多做一些工作!

对于@Marc Gravell的回答,我有两点需要澄清。

测试是在。net 6 x64版本中完成的。

测试代码结束。

数组和列表没有以相同的方式测试

在相同条件下测试array和List, " forquot;也应该进行修改。

for (int i = 0; i < arr.Length; i++)

新版本:

int len = arr.Length;
for (int i = 0; i < len; i++)

瓶颈列表/foreach:

List (List/foreach测试)的瓶颈是可以修复的。

改为:

list.ForEach(x => chk += x);

在Windows 10 pro 21H1 x64的笔记本电脑上测试运行,内核为i7-10510U

List/for Count out: 1495ms (589725196)
List/for Count in: 1706ms (589725196)
Array/for Count out: 945ms (589725196)
Array/for Count in: 1072ms (589725196)
List/foreach: 2114ms (589725196)
List/foreach fixed: 1210ms (589725196)
Array/foreach: 1179ms (589725196)

结果解释

Array/for比原来的测试更快。(减少12%)

List/foreach fixedList/for快。

List/foreach fixed很接近Array/foreach

这个测试我已经运行了几次。结果改变了,但数量级保持不变。

这个测试的结果表明,您确实必须对性能有很大的需求才能强制使用Array。

根据用于操作List的方法,性能可以除以2。

这个测试是局部的。没有随机存取、直接存取、写存取测试等。

是我弄错了什么地方,还是你有其他提高性能的想法?

测试代码:

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 Count out: {0}ms ({1})", watch.ElapsedMilliseconds, chk);


chk = 0;
Stopwatch watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
for (int i = 0; i < list.Count; i++)
{
chk += list[i];
}
}
watch.Stop();
Console.WriteLine("List/for Count in: {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 Count out: {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 Count in: {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++)
{
list.ForEach(i => chk += i);
}
watch.Stop();
Console.WriteLine("List/foreach fixed: {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();
}
}
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();
}
类型 时间 Len
数组 2女士 500000
列表 1毫秒 255000
数组添加 女士17950 55000

如果您计划不断地向数组中添加少量数据,那么list更快

这实际上取决于你将如何使用数组。