为什么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)。
任何启示将被赞赏为什么这是这种情况:)
我的基准测试与您的基准测试不一致。
我和亚历克斯一样运行了相同的基准测试,得到了相反的结果。 然后我稍微调整了基准,并再次观察到Cast
比OfType
更快。
其中并不多,但我相信 Cast
确实有优势,因为它的迭代器更简单。(不是检查。)
编辑:实际上经过一些进一步的调整后,我设法让 Cast
比OfType
快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.Matches
到IEnumerable
东西,从而激活了这个短路。 当我改变我的基准以禁用这种短路时,我获得了一点点优势,而不是一个巨大的优势。
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; }
只需在您的方法中颠倒OfType
和Cast
的顺序,您就会注意到没有区别。 第一个总是比第二个运行得快。 这是一个糟糕的微基准测试的情况。
将代码包装在循环中以随机顺序运行它们:
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()会比直接投射慢