【发布时间】:2011-08-24 09:26:49
【问题描述】:
我开始在我的一些 C# 算法中使用一些 LinkedList 而不是列表,希望能够加快它们的速度。但是,我注意到他们只是感觉慢了一些。像任何优秀的开发人员一样,我认为我应该尽职尽责并验证我的感受。所以我决定对一些简单的循环进行基准测试。
我认为用一些随机整数填充集合就足够了。我在调试模式下运行此代码以避免任何编译器优化。这是我使用的代码:
var rand = new Random(Environment.TickCount);
var ll = new LinkedList<int>();
var list = new List<int>();
int count = 20000000;
BenchmarkTimer.Start("Linked List Insert");
for (int x = 0; x < count; ++x)
ll.AddFirst(rand.Next(int.MaxValue));
BenchmarkTimer.StopAndOutput();
BenchmarkTimer.Start("List Insert");
for (int x = 0; x < count; ++x)
list.Add(rand.Next(int.MaxValue));
BenchmarkTimer.StopAndOutput();
int y = 0;
BenchmarkTimer.Start("Linked List Iterate");
foreach (var i in ll)
++y; //some atomic operation;
BenchmarkTimer.StopAndOutput();
int z = 0;
BenchmarkTimer.Start("List Iterate");
foreach (var i in list)
++z; //some atomic operation;
BenchmarkTimer.StopAndOutput();
这是输出:
Linked List Insert: 8959.808 ms
List Insert: 845.856 ms
Linked List Iterate: 203.632 ms
List Iterate: 125.312 ms
这个结果让我很困惑。链接列表插入应该是 O(1),而列表插入是 Θ(1),如果需要调整大小,则需要 O(n)(因为复制)。由于枚举器,两个列表迭代都应该是 O(1)。我查看了反汇编的输出,并没有对情况有太多了解。
其他人对这是为什么有任何想法?我错过了什么明显的东西吗?
注意:这里是简单 BenchmarkTimer 类的源代码:http://procbits.com/2010/08/25/benchmarking-c-apps-algorithms/
【问题讨论】:
-
链接列表不是为每个插入(LinkedListNode)分配内存并创建一个新对象,而列表插入除了为调整大小的数组的副本之外不分配内存?这种副作用对时间的影响可能比算法的 Big-O 值更大。
-
这可能是真的,但为什么迭代更慢?
-
小心!您似乎对 Big-O-notation 感到困惑。它不会告诉您算法/实现的速度有多快,它会告诉您它扩展的程度。
-
顺便说一句,添加到最后,
List<>只需要调整O(log2(n))的大小,n是最终列表的大小(在您的情况下约为 24 倍),因为它的容量每增加一倍时间。无论如何,0xA3 是完全正确的。 -
0xA3:我一定是用 big-O 错误地陈述了我的分析?我认为通过举一个包含 2000 万次迭代的示例来证明规模问题。
标签: c# .net performance list linked-list