为什么OfType 比Cast 更快?

回答以下问题: 如何将MatchCollection转换为字符串数组

鉴于两个Linq表达式:

var arr = Regex.Matches(strText, @"\b[A-Za-z-']+\b") .OfType() //OfType .Select(m => m.Groups[0].Value) .ToArray(); 

 var arr = Regex.Matches(strText, @"\b[A-Za-z-']+\b") .Cast() //Cast .Select(m => m.Groups[0].Value) .ToArray(); 

OfType 由用户Alex进行基准测试,稍微快一些(并由我自己确认)。

这对我来说似乎违反直觉,因为我认为OfType 必须同时进行’is’比较 cast(T)。

任何启示将被赞赏为什么这是这种情况:)

我的基准测试与您的基准测试不一致。

我和亚历克斯一样运行了相同的基准测试,得到了相反的结果。 然后我稍微调整了基准,并再次观察到CastOfType更快。

其中并不多,但我相信Cast确实有优势,因为它的迭代器更简单。 (不是检查。)

编辑:实际上经过一些进一步的调整后,我设法让CastOfType快50倍。

下面是基准的代码,它给出了迄今为止我发现的最大差异:

 Stopwatch sw1 = new Stopwatch(); Stopwatch sw2 = new Stopwatch(); var ma = Enumerable.Range(1, 100000).Select(i => i.ToString()).ToArray(); var x = ma.OfType().ToArray(); var y = ma.Cast().ToArray(); for (int i = 0; i < 1000; i++) { if (i%2 == 0) { sw1.Start(); var arr = ma.OfType().ToArray(); sw1.Stop(); sw2.Start(); var arr2 = ma.Cast().ToArray(); sw2.Stop(); } else { sw2.Start(); var arr2 = ma.Cast().ToArray(); sw2.Stop(); sw1.Start(); var arr = ma.OfType().ToArray(); sw1.Stop(); } } Console.WriteLine("OfType: " + sw1.ElapsedMilliseconds.ToString()); Console.WriteLine("Cast: " + sw2.ElapsedMilliseconds.ToString()); Console.ReadLine(); 

调整我做了:

  • 在开始时执行“生成字符串列表” 一次 ,并“结晶”它。
  • 在开始计时之前执行每个操作之一 – 我不确定这是否有必要,但我认为这意味着JITter事先生成代码而不是我们的计时?
  • 多次执行每个操作,而不仅仅是一次。
  • 如果这有所不同,请交替订单。

在我的机器上,这导致Cast为~350ms,OfType为~18000ms。

我认为最大的区别是我们不再计算MatchCollection找到下一场比赛需要多长时间。 (或者,在我的代码中, int.ToString()需要多长时间。)这大大降低了信噪比。

编辑:正如六个变量所指出的那样,造成这种巨大差异的原因是Cast会短路并且不会因为它可以投射整个IEnumerable而打扰单个项目。 当我从使用Regex.Matches切换到数组以避免测量正则表达式处理时间时,我还切换到使用可Regex.MatchesIEnumerable东西,从而激活了这个短路。 当我改变我的基准以禁用这种短路时,我获得了一点点优势,而不是一个巨大的优势。

OfType()应该更慢,因为在实际的显式转换操作之前执行安全类型检查,同时Cast()只执行显式转换。

理论上OfType在许多元素具有“错误类型”的情况下会更快,所以循环在检查之后进一步枚举,如果在同一个集合上的Cast() ,你将在“错误类型”的每个元素上结束InvalidCastException 。所以这会相对较慢。

使用ILSpy提取的源代码:

 // System.Linq.Enumerable private static IEnumerable OfType(IEnumerable source) { if (source == null) { throw Error.ArgumentNull("source"); } foreach (object current in source) { // **Type check** if (current is TResult) { // **Explicit cast** yield return (TResult)current; } } yield break; } // System.Linq.Enumerable public static IEnumerable Cast(this IEnumerable source) { IEnumerable enumerable = source as IEnumerable; if (enumerable != null) { return enumerable; } if (source == null) { throw Error.ArgumentNull("source"); } foreach (object current in source) { // **Explicit cast only** yield return (TResult)current; } yield break; } 

只需在您的方法中颠倒OfTypeCast的顺序,您就会注意到没有区别。 第一个总是比第二个运行得快。 这是一个糟糕的微基准测试的情况。

将代码包装在循环中以随机顺序运行它们:

 OfType: 1224 Cast: 2815 Cast: 2961 OfType: 3010 OfType: 3027 Cast: 2987 ... 

然后再说:

 Cast: 1207 OfType: 2781 Cast: 2930 OfType: 2964 OfType: 2964 OfType: 2987 ... 

解除Regex.Matches ,这似乎会导致问题:

 Cast: 1247 OfType: 210 OfType: 170 Cast: 171 ... 

 OfType: 1225 Cast: 202 OfType: 171 Cast: 192 Cast: 415 

所以不行。 OfType并不比Cast快。 不, Cast不比OfType快。

实际上isof()首先检查类型,然后转换它,其中cast()只是第二部分。 所以很明显isof()会比直接投射慢

http://codenets.blogspot.in/2010/06/cast-vs-oftype.html