令人惊讶的性能差异:List.Contains、Sorted List.ContainsKey、DataRowCollection.Contains、Data Table.Select、DataTab
Posted
技术标签:
【中文标题】令人惊讶的性能差异:List.Contains、Sorted List.ContainsKey、DataRowCollection.Contains、Data Table.Select、DataTable.FindBy【英文标题】:Surprising performance differences: List.Constains,SortedList.ContainsKey,DataRowCollection.Contains,DataTable.Select, DataTable.FindBy 【发布时间】:2010-07-28 12:26:39 【问题描述】:最初我想问一个查询数据表中特殊行的最快方法。
我测试了 5 种不同方法的性能,结果令人惊讶(对我来说)。
背景: 我在 MS Sql-Server 2005 数据库中创建了一个视图。此视图当前的总行数为 6318 行。因为我必须经常检查这个视图中是否存在给定的 id,所以我想知道最有效的方法是什么。我在一个强类型数据集中创建了一个 DataAdapter,它返回所有行并填充一个 Datatable。 我的第一种方法是创建一个共享的通用列表(Int32),并在应用程序启动时用视图中的 ID 填充它。然后使用List.Contains检查当前ID是否在这个List中。因为所有行都是不同的,所以我想知道使用 SortedList 及其ContainsKey-metod 是否更快。 然后我还检查了使用其Select-Method 直接访问Datable 的性能,它的自动生成(当列被定义为主键时)FindBy-method 和最后但并非最不重要的DatarowCollection.Contains-方法。 所以我有 5 种方法来检查我的 ID 是否在该视图中(或映射的列表/排序列表)。
我用System.Diagnostics.StopWatch 测量了它们的性能并得到了一些有趣的结果。我认为 SortedList.ContainsKey 必须比 List.Contains 快,因为它们是不同的且已排序,但事实恰恰相反。 但对我来说最令人惊讶的是 DataRowCollection.Contains-Method(我首先忘记的)是迄今为止最快的。它甚至比 dataTable.FindBy-method 快 50 倍。
-
是什么导致了这些差异?
我忘记了更好的方法吗?
我的测量方法是否正确(我认为我最好将它们循环并取该值)?
这些值是否可转移或取决于数据表/集合的大小?
在我的更新(1000000 次迭代)之后,ContainsKey 是最快的。这是因为我总是检查相同的 id 还是一般情况下?是否有某种 SortedList 没有字典的 KeyValue-Pair 开销?
结果 [1000000 次迭代*]
时间跨度 1 = SortedList.ContainsKey = Ø 0.65634 [238.1095] 毫秒 时间跨度 2 = List.Contains = Ø 0.06802 [57045.37955] 毫秒 时间跨度 3 = DataTable.FindByIdData(自动生成的方法)= Ø 0.31580 [1542.62345] 毫秒 时间跨度 4 = DataTable.Select = Ø 0.27790 [26029.39635] 毫秒时间跨度 5 = DataRowCollection.Contains = Ø 0.00638 [1202.79735] 毫秒
1.)
Timespan 1: 0,6913 ms
Timespan 2: 0,1053 ms
Timespan 3: 0,3279 ms
Timespan 4: 0,1002 ms
Timespan 5: 0,0056 ms
2.)
Timespan 1: 0,6405 ms
Timespan 2: 0,0588 ms
Timespan 3: 0,3112 ms
Timespan 4: 0,3872 ms
Timespan 5: 0,0067 ms
3.)
Timespan 1: 0,6502 ms
Timespan 2: 0,0588 ms
Timespan 3: 0,3092 ms
Timespan 4: 0,1268 ms
Timespan 5: 0,007 ms
4.)
Timespan 1: 0,6504 ms
Timespan 2: 0,0586 ms
Timespan 3: 0,3092 ms
Timespan 4: 0,3893 ms
Timespan 5: 0,0063 ms
5.)
Timespan 1: 0,6493 ms
Timespan 2: 0,0586 ms
Timespan 3: 0,3215 ms
Timespan 4: 0,386 ms
Timespan 5: 0,0063 ms
Timespan 1: 0,6913 0,6405 0,6502 0,6504 0,6493 = Ø 0,65634
Timespan 2: 0,1053 0,0588 0,0588 0,0586 0,0586 = Ø 0,06802
Timespan 3: 0,3279 0,3112 0,3092 0,3092 0,3215 = Ø 0,31580
Timespan 4: 0,1002 0,3872 0,1268 0,3893 0,3860 = Ø 0,27790
Timespan 5: 0,0056 0,0067 0,0070 0,0063 0,0063 = Ø 0,00638
为了完整起见VB.Net源码部分:
Dim applies As Boolean
Dim clock As New System.Diagnostics.Stopwatch
clock.Start()
For i As Int32 = 1 To 1000000
applies = sortedListAC17NextClaims.ContainsKey(myClaim.idData)
Next
clock.Stop()
Dim timeSpan1 As String = "Timespan 1: " & clock.Elapsed.TotalMilliseconds.ToString & " ms"
clock.Reset()
clock.Start()
For i As Int32 = 1 To 1000000
applies = listAC17NextClaims.Contains(myClaim.idData)
Next
clock.Stop()
Dim timeSpan2 As String = "Timespan 2: " & clock.Elapsed.TotalMilliseconds.ToString & " ms"
clock.Reset()
clock.Start()
For i As Int32 = 1 To 1000000
applies = Not MyDS.AC17NextClaims.FindByIdData(myClaim.idData) Is Nothing
Next
clock.Stop()
Dim timeSpan3 As String = "Timespan 3: " & clock.Elapsed.TotalMilliseconds.ToString & " ms"
clock.Reset()
clock.Start()
For i As Int32 = 1 To 1000000
applies = MyDS.AC17NextClaims.Select("idData=" & myClaim.idData).Length > 0
Next
clock.Stop()
Dim timeSpan4 As String = "Timespan 4: " & clock.Elapsed.TotalMilliseconds.ToString & " ms"
clock.Reset()
clock.Start()
For i As Int32 = 1 To 1000000
applies = MyDS.AC17NextClaims.Rows.Contains(myClaim.idData)
Next
clock.Stop()
Dim timeSpan5 As String = "Timespan 5: " & clock.Elapsed.TotalMilliseconds.ToString & " ms"
更新: 我已经改变了我的结果和上面的来源。方括号中是 1000000 次迭代的值。现在结果完全不同了。现在最快的方法肯定是 SortedList 的 ContainsKey。
更新 2: 我忘记了使用List.BinarySearch 的替代方法。 这对我来说似乎是最快的:
clock.Start()
For i As Int32 = 1 To 1000000
applies = listAC17NextClaims.BinarySearch(myClaim.idData) > -1
Next
clock.Stop()
只需要 219.1805 毫秒即可执行 1000000 次迭代,因此在没有 SortedList-KeyValue-Pair 开销的情况下是最快的。 我可以使用它而不对列表进行排序,因为 DataAdapter 使用 Order By 子句填充了数据表。
【问题讨论】:
对表中的600M项目做最后一个,并用每种方法搜索两次,丢弃每个第一次运行的时间。 那会花费太多时间,而且是我的另一个要求。 【参考方案1】:为什么不使用具有HashTable 作为基础数据结构的集合(Dictionary<TKey, TValue>
或HashSet<T>
)? HashTables
应该提供 O(1)
查找时间,如果没有键冲突(如你所说)并且不需要“排序”的开销。
编辑:如果您仅想要存储密钥,则应使用 .NET 3.5 及更高版本中提供的HashSet<T>。
From MSDN 在 SortedList 上:
对 SortedList 对象的操作倾向于 比在 a 上的操作慢 Hashtable 对象因为 排序。
要面向 .NET 2.0,您可以自行开发或使用像 Wintellect's Power Collections 这样的预构建版本(您也可以轻松地使用源代码)。
【讨论】:
排序的开销只有一次,因为 sortedlist 是共享的。我的一个问题是,是否有比 SortedList 更好的集合/字典,因为它具有 KeyValue-Pairs 的内存开销。我不需要 KeyValue-Pair,因为视图中只有 ID。在我检查包含之前对其进行排序时,通用列表是否更快,或者是否有另一种可以以非线性方式搜索的集合类型?在我的情况下,键与似乎多余的值相同。 @Tim - 我很确定SortedList
会使用二进制搜索来查找元素(即O(log(n))
)。您可以改为使用 仅 存储密钥的 HashSet<T>
(并且仍将是 O(1)
进行搜索)。
感谢您的提示,但不幸的是我使用的是框架 2.0,而 HashSet 是 3.5 的一部分。
我想知道为什么 Array.BinarySearch(myList.ToArray(),myClaim.idData) 需要 18404.573 ms 进行 1000000 次迭代,并且比 ContainsKey(1:77) 慢,即使 DataTable.FindBy 和 DataRowCollection 也是如此。包含。
@Tim - 因为你每次都打电话给.ToArray()
,这会非常昂贵。另外,请参阅我上面关于使用 .NET 2.0 的编辑。老实说,您可以使用 Dictionary<TKey, TValue>
来增加额外的内存开销...【参考方案2】:
在我看来,您提供的工作几乎不足以在这里获得有用的时间安排。你所有的时间都是亚毫秒级的,几乎可以肯定只是噪音——缓存、jit-ing、抢占等。
让你的集合足够大,可以花几秒钟的时间来运行,或者至少在一个紧密的循环中运行每个测试足够多的时间来花几秒钟的时间。
【讨论】:
JITing 已经介绍过了,他跑了 5 次,但是是的,通常的做法是进行多次运行并从中计算平均时间。 他的时间低于 70us - 我认为这种计时技术(或 Windows 上的任何其他计时技术)的有用分辨率不会下降这么多。 不清楚这 5 次运行是否在 VM 的同一个实例中。如果他重新执行问题中的代码,那么每次都会JIT,不是吗? 1000000 次迭代确实有很大的不同。现在结果更改为 ContainsKey 方法(查看我的更新)。 对 - 但不要忘记,这仍然只告诉您使用该方法搜索 6K 项目列表 1000000 次更快,而不是搜索 60 亿项目列表的最快方法一次。您可能主要测量算法的启动时间而不是其搜索性能。这就是为什么 O(f(n)) 表示法只是故事的一部分——因为用 O(f(n)) + K 可能更好地近似实时,如果 n 很小,K 可能占主导地位。跨度> 【参考方案3】:如前所述,您的代码只运行一次操作。通常的策略是多次运行代码(例如,进行 3 次搜索)以获得更大的数字(因此,如果 3 次搜索需要 0.9 秒,您可以说搜索需要 0.3 秒)。然后循环几次以允许您计算平均值(如果您愿意,包括标准偏差,以过滤掉疯狂的结果),然后最重要的是,运行一次而不注意记录时间,以便任何 JITing执行。
另外,在没有附加调试器的情况下以发布模式运行代码。
【讨论】:
以上是关于令人惊讶的性能差异:List.Contains、Sorted List.ContainsKey、DataRowCollection.Contains、Data Table.Select、DataTab的主要内容,如果未能解决你的问题,请参考以下文章
关于c++gonodejspython的计算性能测试,结果令人惊讶