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

Dav*_*ish 27 c# linq

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

鉴于两个Linq表达式:

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

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

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

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

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

Raw*_*ing 13

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

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

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

编辑:实际上经过一些进一步的调整后,我设法变得Cast快了50倍OfType.

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

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();
Run Code Online (Sandbox Code Playgroud)

调整我做了:

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

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

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

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


sll*_*sll 10

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;
}
Run Code Online (Sandbox Code Playgroud)


use*_*116 6

只是颠倒的顺序OfTypeCast你的方法,你会注意到,没有任何区别.第一个总是比第二个运行得快.这是一个糟糕的微基准测试的情况.

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

OfType: 1224
Cast: 2815
Cast: 2961
OfType: 3010
OfType: 3027
Cast: 2987
...
Run Code Online (Sandbox Code Playgroud)

然后再说:

Cast: 1207
OfType: 2781
Cast: 2930
OfType: 2964
OfType: 2964
OfType: 2987
...
Run Code Online (Sandbox Code Playgroud)

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

Cast: 1247
OfType: 210
OfType: 170
Cast: 171
...
Run Code Online (Sandbox Code Playgroud)

OfType: 1225
Cast: 202
OfType: 171
Cast: 192
Cast: 415
Run Code Online (Sandbox Code Playgroud)

所以不行.OfType并不快Cast.不,Cast不快OfType.

  • Cast 比 OfType 快得​​多。见:/sf/answers/800258791/ (2认同)