【问题标题】:Is performance boost the 'real' purpose of the "GOTO" statement existance?性能提升是“GOTO”语句存在的“真正”目的吗?
【发布时间】:2011-01-28 21:03:31
【问题描述】:

今天我打开了一个 Microsoft 的 .Net 正则表达式实现,我突然想到这可能是在 C# 之类的语言中存在 goto 语句的唯一原因(它主要强调“开发人员的概念,其中 goto 似乎根本没有位置),并且可能也是 Microsoft 使用它的 CLR 库实现的唯一原因 - 以提高性能。我还记得在 System.Web.UI.dll 中的 Microsoft 的 ASP.NET 页面/控件呈现中也看到了类似的优化。这会是一个有效的(或记录在案的?)假设吗?除了提高代码执行时间之外,您是否见过 Microsoft 在任何其他情况下使用它?谢谢。

以下是微软自己的正则表达式实现的摘录(不打算详细检查,只是扫描一下),他们再次广泛使用 goto 似乎只是为了提高性能:

public override void Go() 
{
    int num4;
    int num5;
    string runtext = base.runtext;
    int runtextstart = base.runtextstart;
    int runtextbeg = base.runtextbeg;
    int runtextend = base.runtextend;
    int runtextpos = base.runtextpos;
    int[] runtrack = base.runtrack;
    int runtrackpos = base.runtrackpos;
    int[] runstack = base.runstack;
    int runstackpos = base.runstackpos;
    runtrack[--runtrackpos] = runtextpos;
    runtrack[--runtrackpos] = 0;
    runstack[--runstackpos] = runtextpos;
    runtrack[--runtrackpos] = 1;
    if ((((runtextpos != base.runtextstart) || 
       (4 > (runtextend - runtextpos))) || 
       ((runtext[runtextpos] != '<') || 
       (runtext[runtextpos + 1] != '%'))) || 
       ((runtext[runtextpos + 2] != '-') || 
       (runtext[runtextpos + 3] != '-')))
    {
        goto Label_02F8;
    }
    runtextpos += 4;
    runstack[--runstackpos] = -1;
    runtrack[--runtrackpos] = 1;
    goto Label_0213;
Label_0161:
    if (num5 > num4)
    {
        runtrack[--runtrackpos] = (num5 - num4) - 1;
        runtrack[--runtrackpos] = runtextpos - 1;
        runtrack[--runtrackpos] = 2;
    }
Label_0194:
    num4 = runstack[runstackpos++];
    this.Capture(2, num4, runtextpos);
    runtrack[--runtrackpos] = num4;
    runtrack[--runtrackpos] = 3;
    if (runtextpos >= runtextend)
    {
        goto Label_02F8;
    }
    runtextpos++;
    if (runtext[runtextpos] != '-')
    {
        goto Label_02F8;
    }
    num4 = runstack[runstackpos++];
    this.Capture(1, num4, runtextpos);
    runtrack[--runtrackpos] = num4;
    runtrack[--runtrackpos] = 3;
Label_0213:
    if (num4 != -1)
    {
        runtrack[--runtrackpos] = num4;
    }
    else
    {
        runtrack[--runtrackpos] = runtextpos;
    }
    if ((num4 = runstack[runstackpos++]) != runtextpos)
    {
        runtrack[--runtrackpos] = runtextpos;
        runtrack[--runtrackpos] = 4;
    }
    else
    {
        runstack[--runstackpos] = num4;
        runtrack[--runtrackpos] = 5;
    }
    if (((3 > (runtextend - runtextpos)) || 
    (runtext[runtextpos] != '-')) || 
    ((runtext[runtextpos + 1] != '%') || 
    (runtext[runtextpos + 2] != '>')))
    {
        goto Label_02F8;
    }
    runtextpos += 3;
    num4 = runstack[runstackpos++];
    this.Capture(0, num4, runtextpos);
    runtrack[--runtrackpos] = num4;
    runtrack[--runtrackpos] = 3;
Label_02EF:
    base.runtextpos = runtextpos;
    return;
Label_02F8:
    base.runtrackpos = runtrackpos;
    base.runstackpos = runstackpos;
    this.EnsureStorage();
    runtrackpos = base.runtrackpos;
    runstackpos = base.runstackpos;
    runtrack = base.runtrack;
    runstack = base.runstack;
    switch (runtrack[runtrackpos++])
    {
        case 1:
            runstackpos++;
            goto Label_02F8;

        case 2:
            runtextpos = runtrack[runtrackpos++];
            num4 = runtrack[runtrackpos++];
            if (num4 > 0)
            {
                runtrack[--runtrackpos] = num4 - 1;
                runtrack[--runtrackpos] = runtextpos - 1;
                runtrack[--runtrackpos] = 2;
            }
            goto Label_0194;

        case 3:
            runstack[--runstackpos] = runtrack[runtrackpos++];
            this.Uncapture();
            goto Label_02F8;

        case 4:
            runtextpos = runtrack[runtrackpos++];
            runstack[--runstackpos] = runtextpos;
            runtrack[--runtrackpos] = 5;
            if ((runtrackpos > 40) && (runstackpos > 30))
            {
                runstack[--runstackpos] = runtextpos;
                runtrack[--runtrackpos] = 1;
                runstack[--runstackpos] = runtextpos;
                runtrack[--runtrackpos] = 1;
                num4 = (num5 = runtextend - runtextpos) + 1;
                do
                {
                    if (--num4 <= 0)
                    {
                        goto Label_0161;
                    }
                    runtextpos++;
                }
                while (runtext[runtextpos] != '-');
                runtextpos--;
                goto Label_0161;
            }
            runtrack[--runtrackpos] = 6;
            goto Label_02F8;

        case 5:
            runstack[runstackpos] = runtrack[runtrackpos++];
            goto Label_02F8;
    }
    runtextpos = runtrack[runtrackpos++];
    goto Label_02EF;
}

【问题讨论】:

  • 根据标签,这绝对是反编译的IL。反编译器可能很难从 IL 重构高级控制流块,并且经常使用 goto 来表示 IL 级分支指令(这通常是控制流块的结果)。所以你的答案是原始代码和这个反编译代码的性能将是相同的,因为它们具有相同的 IL 表示。
  • 我认为大多数 .net 库的源代码都可以下载(使用相当严格的 MS 参考许可证)。您可以在那里查看原始构造的外观。
  • 此外,为了清晰起见,goto 的存在已得到充分验证。例如,考虑到您不能将break 排除在多层嵌套循环之外。 goto 可能是你唯一的选择,除非你重写你的循环,让它读起来完全是废话,以换取避免使用goto。性能参数实际上很弱。
  • 明确地说,这个生成的代码,对吧?不是 RegEx 类的一部分,而是来自相关类生成的“已编译”RegEx。
  • 感谢 cdhowie 的解释。但是在 imo 中,如果你不能打破嵌套循环,那么你的设计就有问题,超过 2-3 层的嵌套循环在所有情况下都非常糟糕,在这种情况下,人们应该明确地重新评估“大局” ' 然后回来重写代码。

标签: c# .net performance goto


【解决方案1】:

我看到的一个用例是自动生成的代码。高级控制流构造对我们人类来说很容易,但作为代码生成算法的输出不一定很方便。

你的代码被反编译了吗?在这种情况下,它可能只是以反编译器在不使用gotos 的情况下无法处理的方式编写。在 IL 级别上,所有控制流构造都被转换为gotos,反编译器会尝试猜测它们是什么。在复杂的情况下,它可能找不到使用高级构造表示它的好方法,因此求助于gotos。

另一个用例是状态机。您有许多状态,在每个状态之后,它都可以使用 goto 语句转换到新状态。解析器是状态机的常见应用。

【讨论】:

  • 从技术上讲,IL 级别的控制流构造被转换为 分支,而不是 goto。 goto 是 C# 语言关键字。在 IL 中,相关的操作码是 brtruebrfalsebr
  • “你有很多状态”——哦,是的,你是对的,我记得在 IEnumerable 生成的代码中也看到过它们。
  • @cdhowie: 分支,gotos,名字是什么?我们编码员有时不太聪明。结构化编程是几十年前出现的,为了让编码人员跳上它,一些杂志编辑大声疾呼“gotos被认为是有害的”,而我们其他人只是“是的——他说的!”。它们并不比机器离不开的分支指令更有害、更慢或更快。
  • @Mike:我只是想提供清晰和信息。我并不是因为术语而对答案投了反对票。
  • @cdhowie:抱歉,我还没写完评论就关闭了。
【解决方案2】:

好的,这是一个关于生成的代码的问题,与为什么 C# 有一个goto 语句没有真正的关系。

它不仅是生成的,而且是为状态机 (DFA) 生成的代码。甚至手写的状态机有时也会使用 goto。

回答标题问题:goto 是为了向后兼容并解决嵌套循环(退出)等小角落情况,例如状态机。它与性能几乎没有关系。

【讨论】:

  • “它与性能几乎没有关系。”我不完全确定这一点,因为 goto 跳过了否则很难编写的代码。另外你是什么意思“向后兼容性”?计算机只能进行跳转,也就是 goto - 它没有其他方法可以访问当前序列之外的内存。自从计算机诞生以来就是这样,我怀疑未来很长一段时间都会这样
  • @John: 2,向后:C# 在某种程度上向后兼容 C 甚至 C++。虽然不完全。 1、性能:你无法从高级代码中真正分辨出来。大多数优化都在 JIT 中,然后是流水线 Pentium。你几乎没有控制权。
  • Henk Holterman:谢谢,我明白你的意思了。 “你几乎没有控制权”是的,除非想自己编写 asm 代码并在大多数情况下获得更差的性能,但仍然无法控制需要调用的内核函数,换句话说 - “你几乎没有控制权”
  • @John:还要确保你明白我所说的“流水线奔腾”是什么意思。 P4 的兴起否定或逆转了许多旧的优化技巧。
  • Henk Holterman,是的,我很清楚流水线是什么,但我不确定 P4 的改进与这个问题有什么关系。更多的优化是如此硬件制造商 - 操作系统制造商相关,这并不好笑。例如 fastcall windows 函数存储在哪里?在一个特殊的记忆中,嗯,因为 MS 付钱给英特尔与他们合作
【解决方案3】:

您看到 goto 是反编译过程的结果。在这种情况下,它缺少 if/else 范围的翻译。反编译过程不是一对一的翻译,所以反编译器经常不能正确翻译。 .Net 大量使用 goto,因为它是 MSIL/机器代码移动的唯一方式。在最低级别,它在与堆栈一起玩的内存地址之间跳转。

对于我们这些普通的开发者来说,避免它是可以的,因为我们有可以做我们需要的作用域的命令。 while (true) { } 实际上只是转换为“if (true) goto someaddress”,并且在范围的末尾是“goto that ifaddressupthere”,因此直接使用 goto 几乎没有或根本没有性能。

我一直在使用 Mono.Cecil 编写一些程序集重写的东西,所以我有一些在 Reflector 和 MSIL 中查看我的代码的经验。编译后的代码在大多数情况下都得到了很好的优化。

【讨论】:

  • 我不认为这是真的,在汇编级别是的,跳转是唯一的绕过方式(但不是 MSIL,因为它是一个非常高的抽象)但是你如何解释有时 goto 是没有生成,有时它是?我认为它可能只在展开循环和明显可以跳过的代码时生成 goto。在这种情况下,性能就是原因。
  • @John C# 编译器在编译为 IL 时将所有循环转换为 goto 的等价物,因为 IL 不知道循环。它只知道条件分支和无条件分支。
  • MSIL 是 .Net 的汇编语言。它可以像 goto、call、callvirt 和 pinvoke 这样的好东西。它不能做诸如while、do、select、for、foreach和yield之类的事情。这些是特定于语言的功能,当编译器从您选择的语言编译到 MSIL 时,它们会被翻译成一整套 goto。 (好吧,实际上yield 是一个完全独立的交易......有一段时间可以从yield 语句中查看MSIL 代码。)
  • Tedd Hansen,我不知道 yield 是否真的很特别,它只是带有自己生成的类,它再次在 IL 中转换为正常循环,所以我从 IL 立场说点没有区别。你觉得有什么不同?当需要条件分支时,我会看到与任何其他非生成代码相同的 IL。 C# 中没有真正的协程可以产生任何影响或具有不同的操作码来支持光纤或其他幻想。或者至少我不知道这样。
【解决方案4】:

在某些情况下goto 会提高代码的可读性,例如跳出嵌套循环。

Item item = null;
foreach(var a in A)
{
    foreach(var b in a.B)
    {
        if (b.foo == someCondition)
        {
            item = b.item;
            goto AfterLoop;
        }
    }
}
AfterLoop:

没有goto,您需要大量if (hasFound) {break;} 类型的代码。

【讨论】:

    猜你喜欢
    • 2011-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-06
    相关资源
    最近更新 更多