IEnumerable<T>和"yield return"性能问题

3
下午好,
我正在编写一个简单的词法分析器,它基本上是这个修改版。在获得每个标记后,我需要进行轻微的修改并重新分析以重新检查其类型。当然,在词法分析之后,我需要重新使用整个标记列表对其进行一种“解析”。我的问题是,在词法分析器中使用IEnumerable<Token>yield return语句是否会使整个程序的性能变慢......是否最好使用List<Token>来迭代构建列表并使用普通的return语句?那么遍历IEnumerable/List呢?哪一个更快?
非常感谢。

2
尝试对代码进行基准测试...不要为了看似的性能提升而牺牲可读性。 - Mitch Wheat
4个回答

6
你提出了错误的问题,你应该更担心正则表达式的成本。枚举token只是其中非常小的一部分,所以没有必要优化可以使程序性能提高1%但速度只能快一倍的代码。
编写代码、进行性能分析,你会知道第二个版本需要做什么。由于这些工具运行在“人类时间”(当程序用时增加一倍并且需要20毫秒时,人们无法感知到任何不同),最有可能的结果是“没有必要修改任何东西”。

谢谢Hans。但你有什么建议来提高正则表达式的性能呢? - Miguel
你没有理解信息的含义。需要反过来思考:“我发现使用正则表达式存在性能问题,这是我要求它做的事情,这是我测量到的结果。”只有当你记录下真正的问题时,才能期望得到真正的答案。你还没有遇到真正的问题。 - Hans Passant

3

可能会有一些性能影响,但这也使得迭代器可以懒惰地构建。

个人认为应该以最易读的方式编写代码并测量其性能 - 然后开始担心微调这种事情。用一种方法测试它,用另一种方法测试它,看看使用最高效解决方案是否会失去多少可读性(如果有),以及实际获得多少速度。

请注意,与迭代由 List<T> 实现的 IEnumerable<T> 相比,迭代已知为类型 List<T> 的表达式具有非常轻微的性能优势,因为 List<T> 使用可变结构本身实现了迭代器...基本上,如果使用更高的抽象层,你最终会得到一个装箱值,但在这种特殊情况下,我几乎肯定会选择使用正确的抽象层而不是微小的性能提升。


1

Enumerable和yield return语句被转换为GetEnumator()和在IL代码中实现枚举器。

虽然yield return在枚举过程中对每个返回的标记执行一些额外的工作,但我会坚持使用List创建并将其作为返回列表,因为它产生较少的方法调用,因此应该更快。


你为什么认为方法调用会减少?假设每个项目都需要调用Add...而在迭代器块版本中则不需要。在两种情况下,都将有相同数量的MoveNext()/Current调用。 - Jon Skeet

0

现在,我相信你已经意识到你正在过早地进行优化,这是许多人认为的万恶之源。

然而,如果你真的想加快速度,正则表达式似乎是一种昂贵的方法。每次执行Regex.Match()时,都会再次扫描字符串,这将导致至少与标记数量相同的扫描次数。

如果你知道定义标记的边界(例如'{'和'}'),你可以扫描一次字符串以构建可枚举的标记(使用yield或list,我认为这不会有太大区别)。然后调用者可以重新构建字符串,查找要替换标记的值。

当然,这只适用于简单的“搜索和替换”类型的标记。更复杂的标记需要更复杂的处理,例如正则表达式。也许你可以扩展TokenDefinition来指定匹配是简单还是正则表达式。这将减少执行正则表达式的次数,但仍保持所需的灵活性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接