假设你需要有一个整数列表/数组,你需要频繁迭代,我的意思是非常频繁。原因可能各不相同,但可以说这是在一个大容量处理的最内部循环的核心。
通常情况下,人们会选择使用列表(List),因为它们在大小上具有灵活性。最重要的是,msdn文档声称列表在内部使用数组,并且应该执行得一样快(快速查看Reflector证实了这一点)。尽管如此,还是有一些开销。
有人测量过吗?在一个列表中迭代6M次是否与数组相同?
假设你需要有一个整数列表/数组,你需要频繁迭代,我的意思是非常频繁。原因可能各不相同,但可以说这是在一个大容量处理的最内部循环的核心。
通常情况下,人们会选择使用列表(List),因为它们在大小上具有灵活性。最重要的是,msdn文档声称列表在内部使用数组,并且应该执行得一样快(快速查看Reflector证实了这一点)。尽管如此,还是有一些开销。
有人测量过吗?在一个列表中迭代6M次是否与数组相同?
当前回答
由于List<>在内部使用数组,因此基本性能应该是相同的。为什么这个列表可能会稍微慢一些,有两个原因:
要在列表中查找元素,调用list方法,该方法在底层数组中进行查找。所以你需要一个额外的方法调用。另一方面,编译器可能会识别出这一点,并优化“不必要的”调用。 如果编译器知道数组的大小,它可能会做一些特殊的优化,而对于一个未知长度的列表,它就不能这样做。如果列表中只有几个元素,这可能会带来一些性能改进。
要检查它是否对您有任何影响,最好将发布的计时函数调整为您计划使用的大小列表,并查看您的特殊情况的结果如何。
其他回答
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更快
这实际上取决于你将如何使用数组。
对于@Marc Gravell的回答,我有两点需要澄清。
测试是在。net 6 x64版本中完成的。
测试代码结束。
数组和列表没有以相同的方式测试
为了在相同条件下测试array和List,还需要修改for。
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)
结果解释
数组/for比原始测试快。(减少12%)
List/foreach fixed比List/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();
}
}
因为我有一个类似的问题,这让我快速开始。
我的问题更具体一点,'自反数组实现的最快方法是什么'
Marc Gravell所做的测试显示了很多,但并不是确切的访问时间。他的计时还包括对数组和列表的循环。因为我还提出了第三个我想测试的方法,一个“字典”,只是为了比较,我扩展了hist测试代码。
首先,我使用一个常数进行测试,这给了我一个包括循环在内的特定时间。这是一个“裸”计时,不包括实际访问。 然后我做了一个访问主题结构的测试,这给了我和“开销包括”时间,循环和实际访问。
“裸”计时和“开销包含”计时之间的差异给了我一个“结构访问”计时的指示。
但是这个时机有多准确呢?在测试窗口期间将为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 List中是Array的包装器。
再说一遍:List在里面是数组!在。net List中<T>是其他语言中的<T>数组列表。
详细说明在哪些情况下需要使用什么:
Array need to use: So often as possible. It's fast and takes smallest RAM range for same amount information. If you know exact count of cells needed If data saved in array < 85000 b (85000/32 = 2656 elements for integer data) If needed high Random Access speed List need to use: If needed to add cells to the end of list (often) If needed to add cells in the beginning/middle of the list (NOT OFTEN) If data saved in array < 85000 b (85000/32 = 2656 elements for integer data) If needed high Random Access speed LinkedList need to use: If needed to add cells in the beginning/middle/end of the list (often) If needed only sequential access (forward/backward) If you need to save LARGE items, but items count is low. Better do not use for large amount of items, as it's use additional memory for links. If you not sure that you need LinkedList -- YOU DON'T NEED IT. Just do not use it.
更多的细节:
更多细节:
https://stackoverflow.com/a/29263914/4423545