Big-O用于编译器

时间:2014-02-04 22:26:35

标签: algorithm compiler-construction

有没有人能够深入了解编译器的典型大O复杂性?

我知道它必须是>= n(其中n是程序中的行数),因为它需要至少扫描每行一次。

我认为对于过程语言来说它也必须是>= n.logn,因为程序可以引入O(n)变量,函数,过程和类型等,并且当在程序中引用它们时它将需要O(log n)查找每个参考文献。

除此之外,我对编译器体系结构的非正式理解已达到极限,我不确定前向声明,递归,函数语言和/或其他技巧是否会增加编译器的算法复杂性。

所以,总结一下:

  1. 对于“典型的”过程语言(C,pascal,C#等),有效设计的编译器存在限制性大O(作为行数的度量)

  2. 对于'典型'函数式语言(lisp,Haskell等),有效设计的编译器存在限制性大O(作为行数的度量)

3 个答案:

答案 0 :(得分:3)

这个问题目前尚无法解决。编译器的复杂性当然不会用源代码文件中的代码行或字符来衡量。这将描述解析器或词法分析器的复杂性,但编译器的其他任何部分都不会触及该文件。

解析后,所有内容都将以更加结构化的方式表示源文件的各种AST。编译器将具有很多的中间语言,每个语言都有自己的AST。各个阶段的复杂性将取决于AST的大小,它与字符数甚至与之前的AST无关。

考虑到这一点,我们可以在线性时间内将大多数语言解析为字符数并生成一些AST。对于具有O(n)叶的树,类型检查等简单操作通常为n。但是接下来我们将把这个AST翻译成一个在原始树上具有潜在,双重,三重甚至指数多个节点的形式。现在我们再次在树上运行单通道优化,但相对于原始AST,这可能是O(2^n),并且领主知道字符数是什么!

我认为你会发现甚至找不到n对于编译器的某些复杂性f(n)应该是什么。

作为棺材中的钉子,编译某些语言是不可判定的,包括java,C#和Scala(事实证明,名义上的子类型+方差会导致不可判断的类型检查)。当然C ++的模板系统是turing complete,这使得可判定的编译等同于停止问题(不可判定)。 Haskell +一些扩展是不可判定的。还有许多我无法想到的其他问题。这些语言的编译器没有最糟糕的案例复杂性。

答案 1 :(得分:1)

从我的编译器类中回到我能记住的东西......这里的一些细节可能有些偏离,但一般的要点应该是非常正确的。

大多数编译器实际上都经历了多个阶段,所以在某种程度上缩小问题是有用的。例如,代码通常通过 tokenizer 运行,它几乎只创建对象来表示最小的文本单位。 var x = 1;将被拆分为var关键字的标记,名称,赋值运算符和文字数,后跟语句终结符(';')。大括号,括号等都有自己的令牌类型。

标记化阶段大致为O(n),尽管在关键字可以是上下文的语言中这可能很复杂。例如,在C#中,fromyield之类的字词可以是关键字,但它们也可以用作变量,具体取决于它们周围的内容。因此,根据您在语言中进行的那种事情的多少,并且根据正在编译的特定代码,只是第一阶段可能会有O(n²)复杂性。 (虽然这在实践中非常罕见。)

在标记化之后,然后是解析阶段,您尝试匹配开始/结束括号(或某些语言中的等效缩进),语句终结符等,并尝试理解标记。您需要确定给定名称是代表特定方法,类型还是变量。明智地使用数据结构来跟踪在各种范围内声明的名称可以使这个任务在大多数情况下几乎是O(n),但同样有例外。

在我看到的一个视频中,Eric Lippert说可以在用户击键之间编译正确的C#代码。但是如果你想提供有意义的错误和警告信息,那么编译器必须做更多的工作。

解析后,可能会有许多额外的阶段,包括优化,转换为中间格式(如字节代码),转换为二进制代码,即时编译(以及可在此时应用的额外优化)所有这些都可以相对较快(大部分时间可能都是O(n)),但这是一个如此复杂的话题,即使单一语言也难以回答这个问题,对于一种语言来说几乎不可能回答它。

答案 2 :(得分:0)

就像我所知道的那样: 它取决于编译器在其解析步骤中使用的解析器类型。 解析器的主要类型是LL和LR,两者都有不同的复杂性。