函数式语言编译器优于命令式语言编译器的优点

作为这个问题的后续内容F#内置不变性对C#有什么好处? –am我正确地假设F#编译器可以在知道它处理很大程度上不可变的代码时进行某些优化吗? 我的意思是,即使开发人员编写“Functional C#”,编译器也不会知道开发人员试图编写的所有不可变性,因此它无法进行相同的优化,对吧?

通常情况下,函数式语言的编译器能够进行使用命令式语言无法实现的优化 – 即使是尽可能多的不可写性编写的语言?

我会说基本上’不’。

从不变性或参照透明度中获得的主要“优化”优势是,当您看到像...f(x)...f(x)...这样的代码时,能够进行“公共子表达式消除”。 但是如果没有非常精确的信息,很难做到这样的分析,而且由于F#在.Net运行时运行而.Net无法将方法标记为纯粹(无效),因此需要大量的内置信息和分析。甚至尝试做任何这个。

另一方面,在像Haskell这样的语言(主要是’Haskell’,因为很少有像Haskell这样的语言’,任何人都听说过或使用过:))这是懒惰和纯粹的,分析更简单(一切都是纯洁,坚果)。

也就是说,这种“优化”通常可以与系统的其他有用方面(性能可预测性,调试……)进行严重交互。

经常有“足够聪明的编译器可以做X”的故事,但我认为“足够聪明的编译器”是,而且永远都是一个神话。 如果你想要快速代码,那么写快速代码; 编译器不会保存你。 如果您想要公共子表达式消除,那么创建一个局部变量(自己动手)。

这主要是我的意见,欢迎你投票或不同意(事实上我听说’多核’建议作为一个上升的原因,可能’优化可能再次变得性感’,从表面看起来似乎是合理的)。 但是,如果您对任何编译器进行任何非平凡优化(源代码中的注释不支持)抱有希望,那么请准备好等待很长时间才能实现您的希望。

不要误解我的意思 – 不变性很好,很可能会帮助你在许多情况下编写“快速”代码。 但不是因为编译器优化它 – 而是因为代码易于编写,调试,获得正确,并行化,配置文件,并决定哪些是花费时间的最重要的瓶颈(可能会重写它们)。 如果您需要高效的代码,请使用可让您快速开发,测试和分析的开发过程。

我是否正确假设F#编译器可以在知道它处理大部分不可变代码时进行某些优化?

不幸的是不是 对于编译器编写者来说,“很大程度上不可变”和“不可变”之间存在巨大差异。 即使是保证不变性对于优化器也不是那么重要; 它买的主要是你可以写一个非常积极的内联 。

通常情况下,函数式语言的编译器能够进行使用命令式语言无法实现的优化 – 即使是尽可能多的不可写性编写的语言?

是的,但这主要是能够在更多地方更轻松地应用经典优化的问题。 例如,不变性使得应用公共子表达式消除变得更加容易,因为不变性可以保证某些存储器单元的内容不会被更改。

另一方面,如果您的函数式语言不仅仅是不可变的而且是纯粹的 (没有像I / O这样的副作用),那么您启用了一个新的优化类,它涉及将源代码级表达式重写为更高效的表达式。 最重要和最有趣的一点是快捷砍伐森林 ,这是一种避免为中间结果分配内存空间的方法。 阅读的一个很好的例子是流融合 。

如果您正在编译用于高性能的静态类型的函数式语言,以下是一些重点:

  • 有效使用记忆。 如果可以的话,使用“未装箱”的值,避免分配和额外的堆间接。 特别是河流融合和其他森林砍伐技术都非常有效,因为它们消除了分配。

  • 拥有超快速分配器,并在多个分配上分摊堆耗尽检查。

  • 内联函数有效。 特别是跨模块边界的内联小函数。

  • 通常通过闭包转换有效地表示一流的函数。 有效处理部分应用的function 。

  • 不要忽视经典的标量和循环优化。 他们对TIL和Objective Caml等编译器产生了巨大的影响。

如果你有一个像Haskell或Clean这样的懒惰函数式语言,那么还有很多专门的东西与thunk相关。


脚注:

  • 完全不变的一个有趣的选择是能够执行非常细粒度的并行性。 这个故事的结尾尚未被告知。

  • 为F#编写一个好的编译器比编写一个典型的编译器(如果有这样的东西)更难,因为F#受到如此严格的限制:它必须很好地完成function,但它必须在.NET框架内有效地工作,这是没有设计function语言。 我们应该向Don Syme和他的团队倾斜,因为他们在严重受限制的问题上做得非常出色。

没有。

F#编译器不会尝试分析方法或lambda的引用透明性。 .NET BCL根本就不是为此而设计的。

F#语言规范确实保留了关键字“pure”,因此可以在vNext中手动将方法标记为纯,从而允许更加积极地减少lambda表达式的图形。

但是,如果使用记录或代数类型,F#将创建默认比较和相等运算符,并提供复制语义。 在许多其他好处(模式匹配,封闭世界假设)中,这减轻了重大负担!

是的,如果你不考虑F#,但考虑Haskell。 没有副作用的事实确实为优化提供了很多可能性。

例如,考虑使用C语言:

 int factorial(int n) { if (n <= 0) return 1; return n* factorial(n-1); } int factorialuser(int m) { return factorial(m) * factorial(m); } 

如果在Haskell中编写了相应的方法,则在调用factorialuser时将不会再次调用factorial。 有可能在C#中做到这一点,但我怀疑当前的编译器是否这样做,即使对于一个简单的例子也是如此。 随着事情变得越来越复杂,C#编译器很难优化到Haskell可以做到的水平。

注意,目前F#并不是真正的“纯粹”function语言。 所以,我带来了Haskell(很棒!)。

不幸的是,因为F#只是大多数纯粹的,所以没有那么多的积极优化机会。 事实上,在某些地方F#“与C#相比”使代码变得“悲观”(例如,制作结构的防御性副本以防止可观察到的突变)。 从好的方面来说,尽管如此,编译器总体上做得很好,在大多数地方提供了与C#相当的性能,同时使程序更易于推理。

function语言的其他优化有时是可能的,但不一定是因为不变性。 在内部,许多编译器将代码转换为SSA(单个静态赋值)forms,其中函数内的每个局部变量只能分配一次。 这可以用于命令式和函数式语言。 例如:

 x := x + 1 y := x + 4 

可以变成

 x_1 := x_0 + 1 y := x_1 + 4 

其中x_0x_1是不同的变量名。 这极大地简化了许多转换,因为您可以移动一些代码而不必担心它们在程序中的特定点具有什么价值。 这不适用于存储在内存中的值(即全局变量,堆值,数组等)。 同样,这是针对function语言和命令式语言完成的。

大多数function语言提供的一个好处是强类型系统。 这允许编译器做出其他情况下无法做出的假设。 例如,如果你有两个不同类型的引用,编译器知道它们不能别名(指向同一个东西)。 这不是C编译器可能做出的假设。