【问题标题】:Why is OfType<> faster than Cast<>?为什么 OfType<> 比 Cast<> 快?
【发布时间】:2012-07-11 10:23:30
【问题描述】:

回答以下问题: 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 被用户 Alex 基准测试为稍微快一些(并由我本人确认)。

这对我来说似乎违反直觉,因为我认为 OfType 必须同时进行“是”比较,强制转换 (T)。

任何关于为什么会这样的启示将不胜感激:)

【问题讨论】:

  • 我不认为这是重复的,因为问题不是 什么 有区别,而是为什么一个比另一个慢。
  • @danbystrom 在an answer 中,您链接到Ash 的问题解释了OfType 的速度较慢。 OP 询问为什么当序列的所有元素实际上都是这种类型时它实际上更快。
  • 当它可以只使用as 和空检查时,为什么它会做is 然后进行强制转换?
  • 这不是重复的。
  • 你的答案就在这里Cast and OfType :yes Jon Skeet

标签: c# linq


【解决方案1】:

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

我运行了与 Alex 相同的基准测试,得到了相反的结果。然后我稍微调整了基准,并再次观察到 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() 需要多长时间。)这大大降低了信噪比。

编辑:正如 Sixlettervariables 所指出的,造成这种巨大差异的原因是 Cast 如果可以投射整个 IEnumerable 将短路并且不会费心投射单个项目。当我从使用Regex.Matches 切换到数组以避免测量正则表达式处理时间时,我也切换到使用可转换为IEnumerable&lt;string&gt; 的东西,从而激活了这种短路。当我改变我的基准以禁用这种短路时,我得到了Cast 轻微的优势,而不是 大量 一个。

【讨论】:

  • @Alex 完成。我认为我的基准测试中没有任何漏洞,但如果有,请随时指出。
  • 我开始相信我看到的奇怪结果是由正则表达式引起的......我正在摆弄更多,我的结果现在与你的一致
  • @Rawling:在这种情况下,Cast 更快,因为它简单地返回不受干扰的枚举,因为它实现了IEnumerable&lt;string&gt;。投射特殊情况。
  • @six ...呃。当然。现在我记得为什么我们首先使用Regex.Matches...我会看看我是否可以提出一个基准,它会给我一个有用的结果,同时仍然减少正则表达式正在做的工作。
  • @Rawling:我已经在回答中这样做了。随机试验后没有明显差异。
【解决方案2】:

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

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

使用 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;
}

【讨论】:

    【解决方案3】:

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

    【讨论】:

    【解决方案4】:

    实际上 isof() 首先检查类型,然后将其强制转换,而 cast() 仅执行第二部分。所以显然 isof() 会比直接投射要慢

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-11-02
      • 2015-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多