我正在写一个7卡扑克手评估员作为我的宠物项目之一。在尝试优化速度时(我喜欢挑战),我惊讶地发现,与数组索引查找相比,Dictionary键查找的性能相当慢。
例如,我运行了这个示例代码,列举了所有52个选择7 = 133,784,560个可能的7张牌:
var intDict = new Dictionary<int, int>();
var intList = new List<int>();
for (int i = 0; i < 100000; i ++)
{
intDict.Add(i, i);
intList.Add(i);
}
int result;
var sw = new Stopwatch();
sw.Start();
for (int card1 = 0; card1 < 46; card1++)
for (int card2 = card1 + 1; card2 < 47; card2++)
for (int card3 = card2 + 1; card3 < 48; card3++)
for (int card4 = card3 + 1; card4 < 49; card4++)
for (int card5 = card4 + 1; card5 < 50; card5++)
for (int card6 = card5 + 1; card6 < 51; card6++)
for (int card7 = card6 + 1; card7 < 52; card7++)
result = intDict[32131]; // perform C(52,7) dictionary key lookups
sw.Stop();
Console.WriteLine("time for dictionary lookups: {0} ms", sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for (int card1 = 0; card1 < 46; card1++)
for (int card2 = card1 + 1; card2 < 47; card2++)
for (int card3 = card2 + 1; card3 < 48; card3++)
for (int card4 = card3 + 1; card4 < 49; card4++)
for (int card5 = card4 + 1; card5 < 50; card5++)
for (int card6 = card5 + 1; card6 < 51; card6++)
for (int card7 = card6 + 1; card7 < 52; card7++)
result = intList[32131]; // perform C(52,7) array index lookups
sw.Stop();
Console.WriteLine("time for array index lookups: {0} ms", sw.ElapsedMilliseconds);
输出:
time for dictionary lookups: 2532 ms
time for array index lookups: 313 ms
是否期望这种行为(性能下降8倍)? IIRC,一个字典平均有O(1)查找,而一个数组有最坏情况的O(1)查找,所以我确实希望数组查找更快,但不是这么多!
我目前正在将词典中的扑克手牌排名存储起来。我想如果这和字典查找一样快,我必须重新考虑我的方法并使用数组,虽然索引排名会有点棘手,我可能不得不问另一个问题。
答案 0 :(得分:59)
不要忘记Big-O符号只说明复杂性在大小(等)方面的增长 - 它没有给出任何涉及的常数因素的指示。这就是为什么有时键甚至线性搜索比键字查找更快,因为键数足够少。在这种情况下,你甚至不用数组进行搜索 - 只是一个直接的索引操作。
对于直接索引查找,数组基本上是理想的 - 它只是
的情况pointer_into_array = base_pointer + offset * size
(然后指针取消引用。)
执行字典查找相对复杂 - 与有很多键时按键进行线性查找相比非常快,但比直接数组查找要复杂得多。它必须计算密钥的哈希值,然后确定应该在哪个桶中,可能处理重复的哈希值(或重复的桶),然后检查是否相等。
与往常一样,为作业选择正确的数据结构 - 如果你真的可以直接索引到一个数组(或List<T>
),那么是的,这将是非常快的。
答案 1 :(得分:8)
是否期望这种行为(性能下降8倍)?
为什么不呢?每个数组查找几乎都是隐含/可忽略的,而字典查找可能至少需要一个额外的子程序调用。
他们两个都是O(1)意味着即使你在每个集合中有50倍的项目,性能下降仍然只是它的一个因素(8)。
答案 2 :(得分:5)
某事可能需要一个千年,而且仍然是O(1)。
如果您在反汇编窗口中单步执行此代码,您将很快了解其中的区别。
答案 3 :(得分:3)
当密钥空间非常大并且无法映射到稳定的顺序顺序时,字典结构最有用。如果您可以将键转换为相对较小范围内的简单整数,那么您将很难找到比数组更好的数据结构。
关于实施说明;在.NET中,字典基本上是哈希。通过确保密钥散布到大量独特值的空间中,您可以在某种程度上提高其密钥查找性能。看起来在你的情况下,你使用一个简单的整数作为一个键(我相信哈希值自己的值) - 所以这可能是你能做的最好的。
答案 4 :(得分:2)
数组查找是关于你能做的最快的事情 - 基本上它只是从数组的开头到你想要找到的元素的一位指针算术。另一方面,字典查找可能会稍微慢一点,因为它需要进行散列并关注自己寻找正确的存储桶。虽然预期的运行时间也是O(1) - 但算法常量更大,因此它会更慢。
答案 5 :(得分:2)
欢迎使用Big-O表示法。你总是要考虑到有一个常数因素。
做一个Dict-Lookup当然比数组查找要贵得多。
Big-O只告诉你算法如何扩展。将查找量加倍并查看数字如何变化:两者都需要两次左右。
答案 6 :(得分:0)
从Dictionary is O(1)检索元素的成本,但这是因为字典是作为哈希表实现的 - 因此您必须首先计算哈希值以了解要返回的元素。 Hashtables通常效率不高 - 但它们适用于大型数据集或具有大量唯一哈希值的数据集。
列表(除了是用来编写数组而不是链表的垃圾词外)会更快,因为它会通过直接计算你想要返回的元素来返回值。