4

我正在尝试优化一个并发集合,以尽量减少读取的锁争用。第一遍是使用链表,它允许我只锁定写入,而许多同时读取可以继续畅通无阻。这使用了一个自定义IEnumerator产生下一个链接值。一旦我开始将集合上的迭代与普通进行比较,List<T>我注意到我的实现速度大约是原来的一半(对于from x in c select x1*m* 项目的集合,我的集合分别为24 毫秒List<T>49 毫秒)。

所以我想我会使用 aReaderWriteLockSlim并在读取上牺牲一点争用,这样我就可以使用 aList<T>作为我的内部存储。由于我必须在迭代开始时捕获读锁并在完成时释放它,所以我首先为 my IEnumerable, foreach做了一个yield模式List<T>。现在我只有66ms

我查看了 List 实际做了什么,它使用了一个内部存储T[]和一个自定义IEnumerator来向前移动索引并返回当前索引值。现在,手动T[]用作存储意味着更多的维护工作,但是,我正在追逐微秒。

然而,即使模仿IEnumerator移动数组上的索引,我能做的最好的事情也是大约~38ms。那么是什么提供了List<T>它的秘诀,或者说什么是迭代器的更快实现呢?

更新:原来我的主要速度罪魁祸首是运行调试编译,而List<T>显然是发布编译。在发布时,我的实现仍然比 . 慢一点List<T>,尽管在单声道上它现在更快。

我从朋友那里得到的另一个建议是 BCL 更快,因为它在 GAC 中,因此可以由系统预编译。将不得不在 GAC 中进行测试以测试该理论。

4

1 回答 1

4

在每次迭代时获取和释放锁听起来是个坏主意——因为如果你在迭代列表时执行Addor ,这将使迭代器无效。例如,当然不会喜欢这样。RemoveList<T>

您的用例是否允许调用者ReaderWriterLockSlim围绕他们的整个迭代过程进行,而不是基于每个项目?这将更有效更健壮。如果没有,您打算如何处理并发问题?如果作者在我必须到达的位置之前添加了一个元素,那么一个简单的实现将返回相同的元素两次。删除会发生相反的情况——迭代器会跳过一个元素。

最后,.NET 4.0 是一种选择吗?我知道那里有一些高度优化的并发集合......

编辑:我不太确定您目前在手动构建迭代器方面的情况如何,但您可能想要调查的一件事是使用 struct IEnumerator<T>,并让您的集合明确声明它返回 - 那就是做什么List<T>。这确实意味着使用可变结构,这会让小猫在世界各地哭泣,但如果这对性能绝对至关重要,并且你认为你可以忍受这种恐惧,那么至少值得一试。

于 2009-11-18T06:38:45.340 回答