为什么OfType<>比Cast<>?更快

时间:2012-07-11 10:23:31

标签: c# linq

回答以下问题: How to convert MatchCollection to string array

鉴于两个Linq表达式:

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

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

OfType&LT;&GT;用户Alex进行基准测试的速度略快(并由我自己确认)。

这对我来说似乎违反直觉,因为我已经想过OfType&lt;&gt;必须做'是'比较,一个演员(T)。

任何启示都会被理解为什么会这样:)

4 个答案:

答案 0 :(得分:13)

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

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

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

编辑:实际上经过一些进一步的调整后,我设法让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<string>().ToArray();
var y = ma.Cast<string>().ToArray();

for (int i = 0; i < 1000; i++)
{
    if (i%2 == 0)
    {
        sw1.Start();
        var arr = ma.OfType<string>().ToArray();
        sw1.Stop();
        sw2.Start();
        var arr2 = ma.Cast<string>().ToArray();
        sw2.Stop();
    }
    else
    {
        sw2.Start();
        var arr2 = ma.Cast<string>().ToArray();
        sw2.Stop();
        sw1.Start();
        var arr = ma.OfType<string>().ToArray();
        sw1.Stop();
    }
}
Console.WriteLine("OfType: " + sw1.ElapsedMilliseconds.ToString());
Console.WriteLine("Cast: " + sw2.ElapsedMilliseconds.ToString());
Console.ReadLine();

我做过的调整:

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

在我的机器上,这导致Cast约为350毫秒,OfType约为18000毫秒。

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

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

答案 1 :(得分:9)

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

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

使用ILSpy提取的源代码:

// System.Linq.Enumerable
private static IEnumerable<TResult> OfType<TResult>(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<TResult> Cast<TResult>(this IEnumerable source)
{
    IEnumerable<TResult> enumerable = source as IEnumerable<TResult>;
    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;
}

答案 2 :(得分:6)

只需颠倒方法中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快。

答案 3 :(得分:1)

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

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