Haskell(GHC)专业化旅游和高效TypeFamilies

问题描述:

Haskell完全是关于抽象。但是由于所有抽象(多态)数据的共同表示 - 堆中的指针,抽象花费我们额外的CPU周期和额外的内存使用。有一些方法可以使抽象代码更好地满足高性能需求。据我所知,一种方法是专业化 - 基本上额外的代码生成(手动或编译器),是正确的?Haskell(GHC)专业化旅游和高效TypeFamilies

让我们假设下面所有的代码是严格

如果我们有一个功能sum(这有助于编译器进行更多的优化?):

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

我们可以通过specialize产生它的专业版编译指示:

{-#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有问题。我们无法对其构造函数进行模式匹配,因为 每个UVec实例都不必为Vec提供相同的构造函数。这迫使我们为Vec的每个实例在Vec上实现每个函数(因为缺少模式匹配意味着它不能在Vec上多态)。这种情况下的最佳做法是什么?

正如你所说,我们不能在UVec a上模式匹配,而不知道a是什么。 一种选择是使用另一个类型类来扩展具有自定义函数的矢量类。

class UVec a => UVecSum a where 
    sum :: UVec a -> a 

instance UVecSum Float where 
    sum = ... -- use pattern match here 

如果,以后,我们使用sum v其中v :: UVec Float,我们在实例定义的Float特异性代码将被调用。

部分答案,但也许可能有帮助。

据我所知,一种方法是专业化 - 基本上额外的代码生成(手动或编译器),正确吗?

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

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

是的,编译器尽可能调用专用版本。不确定你的意思是关于堆分配。

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

+1

关于运行时长度信息:haskell没有完整的依赖类型。 Haskell在运行时值和类型之间有区别。所以Vec的长度保证被删除(因为长度是一种类型(或种类)而不是运行时值,但haskell不允许在运行时保存类型)。 – russoulmc