Haskell(GHC)专业化旅游和高效类型家庭



Haskell就是关于抽象的。但是,由于堆上所有抽象(多态)数据指针的公共表示,抽象会花费我们额外的CPU周期和额外的内存使用。有一些方法可以使抽象代码在高性能要求下更好地发挥作用。据我所知,完成这项工作的一种方法是专门化——基本上是额外的代码生成(手动或通过编译器),对吗?

让我们假设下面的所有代码都是Strict(这有助于编译器执行更多优化?)

如果我们有一个函数sum:

sum :: (Num a) => a -> a -> a

我们可以使用specializepragma:生成它的专用版本

{-#SPECIALIZE sum :: Float -> Float -> Float#-}

现在,如果haskell编译器可以在编译时确定我们在两个Float上调用sum,那么它将使用它的专用版本。没有堆分配,对吧?

功能-已完成。相同的杂注可以应用于类实例。这里的逻辑不会改变,是吗?

但是数据类型呢?我怀疑TypeFamilies在这里负责?

让我们尝试专门化依赖长度的索引列表。

--UVec for unboxed vector
class UVec a where
data Vec (n :: Nat) a :: *
instance UVec Float where
data Vec n Float where
VNilFloat :: Vec 0 Float
VConsFloat :: {-#UNPACK#-}Float ->
Vec n Float -> 
Vec (N :+ 1) Float

但是Vec有一个问题。我们不能将其构造函数的模式匹配为CCD_ 7的每个实例不必为CCD_。这迫使我们为Vec的每个实例在Vec上实现每个函数(因为缺乏模式匹配意味着它在Vec上不可能是多态的)。在这种情况下,最佳做法是什么?

正如您所说,如果不知道a是什么,我们就无法在UVec a上进行模式匹配。一种选择是使用另一个类型类,用自定义函数扩展向量类。

class UVec a => UVecSum a where
sum :: UVec a -> a
instance UVecSum Float where
sum = ... -- use pattern match here

如果稍后在v :: UVec Float中使用sum v,则将调用我们在实例中定义的特定于Float的代码。

部分答案,但可能会有所帮助。

据我所知,完成这项工作的一种方法是专业化——基本上是额外的代码生成(手动或编译器),对吗?

是的,这类似于C++模板中的代码实例化。

如果haskell编译器能够在编译时确定我们对两个浮点调用sum,那么它将使用它的专用版本。没有堆分配,对吧?

是的,编译器会尽可能调用专用版本。不确定您对堆分配的意思。

关于依赖类型的向量:通常(我从Idris中知道这一点),编译器会在可能的情况下消除向量的长度。它用于更强的类型检查。在运行时,长度信息是无用的,并且可以被丢弃。

最新更新