function语言天生就很慢?
为什么函数式语言在基准testing中总是落后于C? 如果你有一个静态types的函数语言,在我看来它可以被编译成与C相同的代码,或者更好的代码,因为更多的语义可用于编译器。 为什么看起来所有的函数式语言都比C语言慢,为什么他们总是需要垃圾回收和过度使用堆?
有没有人知道适用于embedded式/实时应用程序的function语言,其中内存分配保持在最低限度,生产的机器代码是精简而快速的?
function语言天生就很慢?
在某种意义上,是的。 它们需要基础架构,不可避免地会增加开销,而这些开销会超过手工汇编所能达到的理论值。 特别是,一stream的词法closures只适用于垃圾回收,因为它们允许值超出范围。
为什么函数式语言在基准testing中总是落后于C?
首先,要小心select的偏见。 C作为基准套件中的最低公分母,限制了可以完成的任务。 如果你有一个比较C和function语言的基准,那么它几乎肯定是一个非常简单的程序。 可以说非常简单,今天它没有什么实际意义。 用C作为基准来解决更复杂的问题实际上是不可行的。
最明显的例子就是并行性。 今天,我们都有多核心。 即使我的手机是多核。 多核并行在C中非常困难,但在函数式语言(我喜欢F#)中可以很容易。 其他例子包括从持久数据结构中受益的任何事物,例如,撤销缓冲器对于纯function数据结构是微不足道的,但是在命令式语言如C中可能是大量的工作。
为什么看起来所有的函数式语言都比C语言慢,为什么他们总是需要垃圾回收和过度使用堆?
函数式语言看起来会比较慢,因为你只会看到比较代码的基准,这些代码很容易用C语言编写,而且你永远也看不到比较function性语言开始擅长的任务的基准。
但是,您已经正确地确定了今天函数式语言中可能最大的瓶颈:它们的分配率过高。 干得好!
function语言分配如此之多的原因可以分解为历史和固有的原因。
从历史上看,Lisp的实现已经做了50年了很多拳击现在。 这个特性扩展到许多使用类Lisp中间表示的其他语言。 多年来,语言实施者不断采用拳击手段来解决语言实施中的复杂问题。 在面向对象的语言中,默认情况下总是堆分配每个对象,即使它明显可以堆栈分配。 然后效率负担被推到垃圾收集器上,并且大量的努力已经被投入build立垃圾收集器,其性能可以接近堆栈分配,典型地通过使用凹凸分配的苗圃生成。 我认为应该花更多的精力研究function性语言devise,最大限度地减less针对不同需求进行优化的装箱和垃圾收集器devise。
分代垃圾收集器对堆分配的语言非常有用,因为它们可以像堆栈分配一样快。 但是他们在别处增加了大量的pipe理费 今天的程序越来越多地使用像队列这样的数据结构(例如用于并发程序devise),并且这些程序给出了世代垃圾收集器的病态行为。 如果队列中的项目超过了第一代,那么它们全部被标记,然后它们全部被复制(“撤离”),然后所有对其旧位置的引用被更新,然后它们有资格收集。 这比它需要的慢3倍左右(例如与C相比)。 像Beltway (2002)和Immix (2008)这样的马克地区collections家有可能解决这个问题,因为苗圃被一个可以被collections起来的地区取代,就好像它是一个苗圃一样,或者如果它包含大部分可到达的价值,被另一个地区取代,并留下年龄,直到它包含大部分无法达到的价值。
尽pipeC ++已经存在,Java的创build者却犯了一个错误,就是对generics进行types擦除,导致不必要的装箱。 例如, 我testing了一个比JVM运行速度快17倍的简单哈希表,因为.NET没有犯这个错误(它使用了泛化的generics),也因为.NET有价值types。 我实际上责怪Lisp使Java变慢。
所有现代的函数式语言实现都会继续过多地打包。 像Clojure和Scala这样基于JVM的语言几乎没有select,因为他们所定位的虚拟机甚至不能expression值types。 OCaml在编译过程的早期就提供了types信息,并在运行时使用标记的整数和装箱来处理多态性。 因此,OCaml经常会将个别的浮点数和盒元组合在一起。 例如,OCaml中的三个字节由一个指针(其中embedded了一个隐含的1位标记,在运行时被重复检查)表示为一个具有64位标头和192位主体的堆分配块三个标记的63位整数(其中3个标记再次在运行时被重复检查!)。 这显然是疯了。
一些工作已经在function语言中进行拆箱优化,但从未真正获得过关注。 例如,标准ML的MLton编译器是一个完整的程序优化编译器,可以进行复杂的拆箱优化。 可悲的是,这是在它的时间之前,“冗长”的编译时间(可能在一台现代机器上低于1秒!)阻止了人们使用它。
.NET是唯一打破这个趋势的主要平台,但令人惊讶的是,这似乎是一个意外。 尽pipe对于具有价值types的键和值进行了大量优化的Dictionary
实现(因为它们是拆箱的),但是像Eric Lippert这样的微软员工仍然声称价值types的重要性在于它们传递值的语义,而不是性能源于他们的无箱内部表示的特征。 埃里克似乎被certificate是错误的:更多的.NET开发人员似乎更关心拆箱,而不是传值。 事实上,大多数结构是不可改变的,因此,它们在语义上是透明的,所以在传值和传递之间没有语义上的区别。 性能是可见的,结构可以提供巨大的性能改进。 结构的性能甚至保存了Stack Overflow和结构用于避免像Rapid Addition这样的商业软件中的GC延迟!
function语言的重要分配的另一个原因是固有的。 像哈希表这样的命令式数据结构在内部使用巨大的单片arrays。 如果这些是持久的,那么每次更新时都需要复制巨大的内部数组。 因此,像平衡二叉树这样的纯function数据结构被分割成许多小堆分配块,以便于从一个版本集合到下一个版本的重用。
Clojure使用一个简洁的技巧来缓解这个问题,当像字典这样的集合只在初始化时被写入,然后从很多地方读取。 在这种情况下,初始化可以使用突变来构build“幕后”结构。 然而,这对增量更新没有帮助,并且所得到的集合仍然比读取它们的命令等同物要慢很多。 另一方面,纯function数据结构提供持久性,而命令性数据结构则不然。 然而,很less有实际应用从实践中持久获益,所以这通常不是有利的。 因此,对不纯粹的function语言的渴望,你可以毫不费力地下降到命令式的风格,并获得好处。
有没有人知道适用于embedded式/实时应用程序的function语言,其中内存分配保持在最低限度,生产的机器代码是精简而快速的?
如果你还没有看看Erlang和OCaml。 两者对于内存受限的系统都是合理的,但都不会产生特别好的机器码。
没有什么是固有的。 下面是一个解释 OCaml运行速度比等效C代码更快的例子,因为OCaml优化器由于语言的差异而具有不同的可用信息。 当然,如果一般地声称OCaml的速度比C快,那将是愚蠢的。关键是,这取决于你在做什么以及如何做。
也就是说, OCaml是一个(主要)function性语言的例子,它实际上是为了performance而devise的,与纯度相反。
函数式语言要求消除在语言抽象层面上可见的可变状态。 因此,通过命令式语言进行适当的变异的数据需要被复制,而变化发生在拷贝上。 举一个简单的例子,看看Haskell和C.
此外,垃圾收集是必需的,因为free()
不是纯函数,因为它有副作用。 因此,在语言抽象层面释放不涉及副作用的内存的唯一方法就是垃圾回收。
当然,原则上,一个足够聪明的编译器可以优化大部分的拷贝。 这已经在一定程度上做了,但是让编译器有足够的智能去理解你的代码在这个层次上的语义是很难的。
简短的回答:因为C很快。 如在, 疯狂地疯狂地快速 。 一种语言根本不需要“缓慢”地把它交给C.
C之所以快速是因为它是由真正的优秀编程人员创build的,gcc已经在几十年的时间里得到了优化,并且已经有超过99%的语言编写者使用了数十种精彩的编码器。
总之,除了需要非常特定的函数编程结构的专门任务外,你不会打败C语言。
C很快,因为它基本上是汇编程序的一组macros。当你用C编写一个程序时,没有“幕后”。当你决定是时候这样做的时候,你可以分配内存,而且你可以用同样的方式释放内存。 当你正在编写一个实时应用程序时,这是一个巨大的优势,其中可预测性是重要的(实际上比其他任何事情都重要)。
另外,C编译器通常非常快,因为语言本身很简单。 它甚至不做任何types的检查:)这也意味着更容易发现错误。 缺乏types检查的缺点在于函数名称只能以其名称导出,这使得C代码很容易与其他语言的代码
到目前为止,function语言并没有被用于工业项目,所以没有足够的认真的工作进入优化器。 而且,为命令式目标优化命令式的代码可能更容易。
function语言有一个壮举,可以让他们现在很快超越命令式语言:平行并行化。
并不是说它很容易,但它可以embedded到语言环境中,而不需要开发人员去思考。
像C这样的线程无关语言中强健的multithreading的开销对许多项目来说是不可接受的。
程序语言的控制stream程更好地匹配现代计算机的实际处理模式。
C非常接近编译产生的汇编代码,因此被称为“跨平台汇编”。 计算机制造商已经花费了几十年时间使得汇编代码尽可能快地运行,所以Cinheritance了所有这些原始速度。
相比之下,function语言的无副作用,内在并行性并没有很好地映射到单个处理器上。 函数可以被调用的任意顺序需要被序列化到CPU瓶颈:没有非常聪明的编译,你会一直在进行上下文切换, 所有的预取都不会起作用,因为你经常跳跃所有的地方,…基本上,所有的电脑制造商所做的优化工作已经完成了好的,可预测的程序语言几乎是无用的。
然而! 随着越来越less的强大的内核(而不是一个或两个涡轮增压内核),function语言应该开始缩小差距,因为它们自然而然地是横向扩展的。
那么Haskell只比GCC的C ++慢1.8倍,这比典型的基准testing任务的GCC的C实现要快。 这使得Haskell速度非常快,甚至比C#(单声道)还要快。
相对语言速度
- 1.0 C ++ GNU g ++
- 1.1 C GNU gcc
- 1.2 ATS
- 1.5 Java 6服务器
- 1.5清洁
- 1.6帕斯卡免费帕斯卡
- 英特尔Fortran 1.6
- 1.8 Haskell GHC
- 2.0 C#单声道
- 2.1斯卡拉
- 2.2 Ada 2005 GNAT
- 2.4 Lisp SBCL
- 3.9 Lua LuaJIT
资源
为了logging我在iPhone上使用Lua for Games,因此如果您愿意,可以轻松使用Haskell或Lisp,因为它们更快。
我不同意tuinstoel。 重要的问题是函数式语言是否提供了更快的开发时间,并且在使用什么函数式语言的时候会使代码更快。 查看维基百科的效率问题部分 ,了解我的意思。
更大的可执行文件大小的另一个原因可能是懒惰评估和非严格性。 编译器不能在编译时计算某些expression式的计算结果,所以有些运行时会被塞进可执行文件来处理这个问题(要求对所谓的thunks进行求值)。 至于表演,懒惰既好又坏。 一方面,它允许额外的潜在优化,另一方面,代码大小可能更大,程序员更可能做出错误的决定,例如参见Haskell的foldl与foldr与foldl'与foldr'的比较 。
一个函数式语言很容易并行运行,例如erlang,比C慢,是的,不是编译成本地代码,而是更容易并行运行,并行模式比顺序模式更快。 好的语言在一种情况下是不同的。