Haskell的位置属性是什么?

Ele*_*fee 11 memory performance haskell

现代CPU被优化,以便访问和修改存储器中的相同位置(时间局部性)以及存储器中的连续位置(空间局部性)是非常快速的操作.

现在,由于Haskell是一种纯粹的不可变语言,你自然不能覆盖现有的内存块,可能使得foldlfor具有连续访问的结果变量的循环慢得多的事情将在C中.

Haskell是否在内部做任何事情来减轻这种性能损失?一般来说,它的地方性质是什么?

Lui*_*las 12

一般规则是,对于"vanilla"Haskell编程,你很少(如果有的话)控制内存布局和内存局部性.

但是,确实存在许多允许这种控制的更高级功能,以及在这些之上公开友好抽象的库.该vector库可能是最流行的后者.该库提供了几种固定大小的数组类型,其中两种(Data.Vector.UnboxedData.Vector.Storable)通过将向量及其内容表示为连续的内存数组来为您提供数据局部性. Data.Vector.Unboxed甚至包含一个简单的自动"数组结构"转换 - 一对未装箱的矢量对象将表示为一对未装箱的矢量,每个矢量对应一个.

另一个例子是JuicyPixels用于图像处理的库,它将内存中的图像表示为连续的位图.这实际上是最低限度的Data.Vector.Storable,它利用标准工具(Foreign.Storable)将用户定义的Haskell数据类型转换为原始字节.

但一般模式是这样的:在Haskell中,当您对内存局部性感兴趣时,您可以确定哪些数据需要从中受益,并将其捆绑在一个自定义数据类型中,其实现旨在提供位置和性能保证.编写这样的数据类型是一项高级任务,但大部分的工作已经以可重用的方式完成(例如,JuicyPixels大多数情况下只是重用vector).

还要注意:

  1. vector在应用嵌套矢量转换时,提供流融合优化以消除中间数组.如果你生成一个从0到1,000,000的向量,过滤掉偶数,将(^2)函数映射到该值并对结果的元素求和,没有分配任何数组 - 库有智能将其重写为从0到0的累加器循环1,000,000.因此foldl,一个向量不一定比一个for循环慢- 可能根本就没有数组!
  2. vector也提供可变数组.更一般地说,在Haskell中,如果你真的坚持,你可以覆盖现有的内存.它只是(a)不是语言中的默认范例,因此(b)有点笨重,但如果你只需要在一些性能敏感的地方需要它,那就绝对容易处理.

所以大多数时候,"我想要记忆位置"的答案就是"使用" vector.


Mat*_*hid 10

Haskell是一种非常高级的语言,你问一个关于极低级别细节的问题.

总的来说,Haskell的性能可能类似于任何垃圾收集语言,如Java或C#.特别是,Haskell具有可变数组,其性能与任何其他数组相似.(您可能需要未装箱的阵列来匹配C性能.)

对于类似折叠的东西,如果最终结果类似于机器整数,则可能在循环的整个持续时间内最终在处理器寄存器中.因此,最终的机器代码几乎与"C中连续访问的变量"完全相同.(如果结果是字典或其他东西,那么可能不是.但这也和C一样.)

更一般地说,如果locallity对您来说很重要,那么任何垃圾收集语言可能都不是您的朋友.但是,再次,您可以使用未装箱的阵列来解决这个问题.

所有这些谈话都非常棒,但是如果你真的想知道一个特定的Haskell程序有多快,那就对它进行基准测试.事实证明,编写良好的Haskell程序通常非常快.(就像大多数编译语言一样.)

补充:您可以要求GHC以Core格式输出部分编译的代码,该代码比Haskell低,但比机器代码高.这可以让你看到编译器决定做什么(特别是内嵌内容,删除抽象的地方等).这可以帮助你找出最终代码的样子,而不必一路走下去到机器代码.

  • @ElectricCoffee GHC用于编译为C.我认为代码路径现在只用于移植; 默认情况下,它通过本机后端进行编译,或者如果选择它则编译为LLVM.(我可能错了......) (3认同)
  • @MathematicalOrchid可能还想添加一个专门用于阅读Core的句子或段落.这通常可以很好地了解哪些构造已经优化到紧密循环,哪些没有. (3认同)