在许多行业使用的编译器中,为什么单静态分配优先于连续传递样式?


16

根据Wikipedia页面上的静态单分配(SSA)页面,大型和知名项目(例如LLVM,GCC,MSVC,Mono,Dalvik,SpiderMonkey和V8 使用SSA,而项目使用连续传递样式(CPS)在比较中有点不足。

我的想法是,主要实现功能语言的编译器和解释器更喜欢CPS-特别是,Haskell和Scheme由于对突变的限制或需要一流的连续支持而似乎对CPS风格有强烈的倾向(我想(Smalltalk也可能需要)。我遇到的使用CPS的主要文献似乎是那些主要在Scheme上工作或在某些方面与Scheme相关的文献。

除了采用的势头之外,SSA还用于工业中有什么特殊原因吗?SSA和CPS有密切关系;这意味着很容易用另一种方式陈述,但是对于CPS来说,信息表示可能不太紧凑或效率较低。


3
IIRC是在传统数据流分析时代转移到SSA时设计的用于命令式语言的传统优化方法,比在CPS中更容易实现。特别是,use-def和def-use链可以直接“读取” SSA表示形式。惯性很重要
别名

Answers:


9

我想象许多典型命令式语言的编译器实现者对CPS和基于CPS的编译技术并不熟悉。在函数式编程社区中,CPS和基于CPS的编译都是非常著名的技术-后者来自Guy Steele的著作。但是,即使在FP社区中,大多数编译器也不会使用基于CPS的技术进行编译,除非该语言本身支持像这样的控件运算符call/cc。使用的内容更像是行政范式(ANF)(有时也称为Monadic范式,由于显而易见的原因而密切相关),它与SSA的关系比CPS更紧密

如果我没记错的话,管理标准格式之所以得名,是因为基于CPS的编译会导致中间代码中的beta-redexes与源代码中的任何内容都不对应。这些被称为“管理性旧版本”。这些可以在编译时减少,但是在执行CPS转换方面有大量研究,该CPS转换将首先输出无需管理redex的代码。然后,目标是以正常形式产生输出,其中所有“行政” redex都被减少,这就是“行政正常形式”的由来。很快就意识到,将其视为一个两步过程并没有太大的好处:CPS转换,减少管理性繁琐工作。尤其是,行政范式看起来很像(手工)实行的单子风格,在Haskell中最为明显。CPS转换可以理解为向Monadic样式的转换,而您恰好在使用CPS monad(因此,实际上有多种方法可以“转换”为与不同评估顺序相对应的Monadic样式)。但是,总的来说,您可能会使用完全不同的monad,因此,转换为monadic样式以及管理规范形式与CPS并没有特别的关系。

尽管如此,CPS与ANF相比还是有一些好处。特别是,您可以在CPS中仅通过标准优化(例如beta减少)就可以进行某些优化,而这些优化似乎是ANF的特别规则。从单子视图来看,这些规则对应于通勤转换。现在的结果是,有一种理论可以解释应该添加哪些规则以及为什么添加该规则。一个最近的一篇文章给出了一个(新的)很清晰的描述(从逻辑的角度来看)及其相关的工作部分作为一个简短而体面的调查,并引用到对我提到的主题文献。

CPS的问题与其主要优点之一相关。CPS转换允许您实现像一样的控制运算符call/cc,但这意味着CPS中间代码中的每个非本地函数调用都必须被视为潜在地执行控制效果。如果您的语言包括控制运算符,那么这应该是应该的(尽管即使如此,大多数函数可能都没有执行任何控制恶作剧)。如果您的语言包括控制运算符,那么在使用连续性方面存在全局不变性,而局部不变性并不明显。这意味着无法对一般的CPS代码执行优化,而对于这种行为特别良好的CPS使用来说,这是合理的。一种体现的方式是数据和控制流分析精度的变化。(CPS转换在某些方面有所帮助,而其他方面则有所伤害,尽管它所提供的帮助主要是由于重复而不是CPS本身本身。)1当然,您可以添加规则并调整分析以对此进行补偿(即,利用全局变量),但是您已经部分击败了基于CPS的编译的主要优点之一,即许多(看似)特殊用途的临时优化成为通用优化的特殊情况(尤其是beta减少) )。

最终,除非您的语言具有控制运算符,否则通常没有太多理由使用基于CPS的编译方案。弥补了我上面提到的问题之后,通常就消除了基于CPS的编译的好处,并产生了与不使用CPS等效的东西。那时,CPS只是使卷积的中间代码看起来没有太大好处。2007年基于CPS进行编译的论点解决了其中一些问题,并使用其他形式的CPS转换带来了其他好处。我之前提到的(2017)论文部分论述了论文提出的内容。

1 SSA和CPS之间的等效性是否或多或少地使这成为不可能?号的第一件事情的纸张引入此等价的状态是等价确实工作任意 CPS代码,但它的工作用于CPS的输出变换(它们限定)为一个语言没有控制操作员。


2
自从最初的答案以来已经过去了一段时间,但我感到很
满意

2

我不是编译器专家,所以请耐心等待我所说的话,我希望真正的编译器专家能为您提供帮助。有人告诉我:

  • CPSed代码往往会在非本地跳很多,这会破坏缓存的局部性,从而破坏性能。
  • 与传统的编译相比,CPS需要更昂贵的垃圾回收,传统的编译通常可以在堆栈上存储很多东西(分配和释放速度更快)。

两者合谋使CPS转换编译的代码相对较慢。


3
我认为您正在将CPS转换的代码(或从源到源的转换)与使用CPS的一种中间语言混合在一起。假设您的输入语言不支持诸如的控制效果call/cc,那么例如,延续将与堆栈规则一起使用-不需要垃圾收集器,并且延续将或多或少地对应于控制流的边缘图,因此不会有任何额外的跳跃。您不需要使用一些高阶函数的通用实现来实现延续。
德里克·埃尔金斯

@DerekElkins对我来说,OP在问什么不清楚。
Martin Berger
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.