我同意Dietrich Epp的观点:这是使GHC快速的几件事的结合。
首先,Haskell是非常高级的。这使编译器可以执行积极的优化而不会破坏您的代码。
考虑一下SQL。现在,当我编写一条SELECT
语句时,它看起来像是一个命令式循环,但不是。它可能看起来像它遍历该表中的所有行试图找到指定的条件匹配的那一种,但实际上在“编译”(数据库引擎),可以查找,而不是做一个指数-该指数具有完全不同的性能特征。但是由于SQL如此高级,“编译器”可以替代完全不同的算法,透明地应用多个处理器或I / O通道或整个服务器,等等。
我认为Haskell是相同的。您可能认为您只是要求Haskell将输入列表映射到第二个列表,将第二个列表过滤到第三个列表,然后计算产生的项目数。但是您没有看到GHC在后台应用流融合重写规则,而是将整个过程转换成一个紧密的机器代码循环,从而无需分配即可在一次数据传递中完成整个工作-那种事情会繁琐,容易出错且无法手动书写。由于代码中缺少底层细节,因此这仅是真正可能的。
另一种看待它的方式可能是…… Haskell 为什么不应该快?它应该做什么使它变慢?
它不是像Perl或JavaScript这样的解释型语言。它甚至不是像Java或C#这样的虚拟机系统。它一直编译到本机代码,因此没有任何开销。
与OO语言[Java,C#,JavaScript ...]不同,Haskell具有完整类型擦除(例如C,C ++,Pascal ...)。所有类型检查仅在编译时进行。因此,也没有运行时类型检查来减慢您的速度。(就这一点而言,没有空指针检查。例如,在Java中,JVM必须检查空指针并在您引用一个空指针时抛出异常。Haskell不必费心该检查。)
您说“在运行时动态创建函数”听起来很慢,但是如果仔细看,实际上并没有这样做。可能看起来像您,但事实并非如此。如果您说(+5)
好的,那就是硬编码到您的源代码中。它不能在运行时更改。因此,它并不是真正的动态功能。即使是咖喱函数也实际上只是将参数保存到数据块中。实际上,所有可执行代码都在编译时存在。没有运行时解释。(与其他具有“评估功能”的语言不同。)
想想帕斯卡。它很旧,没有人真正使用它,但是没人会抱怨Pascal 速度很慢。关于它,有很多事情让人不喜欢,但慢速并不是其中之一。Haskell实际上并没有做与Pascal不同的事情,除了具有垃圾回收而不是手动内存管理。不变的数据允许对GC引擎进行多项优化(这种惰性评估随后会使情况有些复杂)。
我认为问题是Haskell 看起来很先进,很高级,而且每个人都认为“哦,哇,这真的很强大,它一定非常慢! ”但是事实并非如此。或者至少,它不是您期望的那样。是的,它有一个了不起的类型系统。但是你知道吗?这一切都发生在编译时。在运行时,它已经消失了。是的,它允许您使用一行代码来构造复杂的ADT。但是你知道吗?一个ADT只是一个简单的普通的C union
的struct
秒。而已。
真正的杀手is是懒惰的评估。当您正确地执行了代码的严格性/惰性时,您可以编写出愚蠢的快速代码,仍然优雅而美丽。但是,如果您弄错了这些东西,您的程序就会变慢数千倍,而发生这种情况的确不是很明显。
例如,我编写了一个简单的小程序来计算每个字节出现在文件中的次数。对于25KB的输入文件,该程序需要20分钟的运行时间并吞没了6 GB的RAM!太荒谬了!但是后来我意识到了问题所在,添加了一个爆炸样式,运行时间降至0.02秒。
这是Haskell意外缓慢的地方。而且它需要一段时间才能习惯。但是随着时间的流逝,编写真正快速的代码变得更加容易。
是什么让Haskell如此之快?纯度。静态类型。懒惰。但最重要的是,它具有足够高的层次,以使编译器可以从根本上更改实现而不会超出代码的期望。
但是我想那只是我的看法...