如果我在Haskell / GHC中使用未装箱的类型(如Int#),我应该注意哪些事项?

时间:2010-09-04 09:05:53

标签: optimization haskell ghc unboxing brainfuck

我正在尝试编写一个解析和执行Brainfuck代码的小脚本,以了解GHC的优化选项,我正在尝试优化代码以便更快一些并了解正在发生的事情。

部分是BF代码的内部代表,我使用一种特殊的数据类型。这是源代码,包括正在进行转换的两个函数:

data BFinstruction
  = AdjustValue Int
  | MovePointer Int
  | GetChar
  | PutChar
  | Loop BFcode
  deriving (Eq)

type BFcode = [BFinstruction]

unsafeCompileBrainfuck :: String -> BFcode
unsafeCompileBrainfuck = fst . parse [] where
  -- arguments: input string, built code; output: output code, rest of input
  parse :: BFcode -> String -> (BFcode,String)
  parse c ('+':s) = parse (AdjustValue   1 :c) s
  parse c ('-':s) = parse (AdjustValue (-1):c) s
  parse c ('>':s) = parse (MovePointer   1 :c) s
  parse c ('<':s) = parse (MovePointer (-1):c) s
  parse c ('.':s) = parse (PutChar         :c) s
  parse c (',':s) = parse (GetChar         :c) s
  parse c (']':s) = (reverse c, s)
  parse c ('[':s) = parse (Loop l          :c) s' where (l,s') = parse [] s
  parse c []      = (reverse c ,"")
  parse c ( _ :s) = parse                   c  s

simplifyBrainfuck :: BFcode -> BFcode
simplifyBrainfuck ((AdjustValue x):(AdjustValue y):zs) = if x + y /= 0
  then simplifyBrainfuck (AdjustValue (x + y):zs)
  else simplifyBrainfuck zs
simplifyBrainfuck ((MovePointer x):(MovePointer y):zs) = if x + y /= 0
  then simplifyBrainfuck (MovePointer (x + y):zs)
  else simplifyBrainfuck zs
simplifyBrainfuck (x                              :zs) = x: simplifyBrainfuck zs
simplifyBrainfuck []                                   = []

这个想法是,代码将从一些输入(字符串)中读取,由上面的代码预先准备和简化,然后由其他一些函数执行。 (假设输入有效)。

为了优化这个例子,我尝试通过这样的方式取消装箱MovePointerAdjustValue构造函数的Int参数:

data BFinstruction -- BangPatterns
  = AdjustValue {-# UNPACK #-} !Int
  | MovePointer {-# UNPACK #-} !Int
  | GetChar
  | PutChar
  | Loop BFcode
  deriving (Eq)

这会将装箱的Int类型转换为未装箱的原始Int#类型,这是GHc的实现细节。在我看来,这个选项只在少数情况下有用,所以我想问一下,如果我想进行这种优化,我必须注意哪些事情。我的目标是允许使用Haskell的好处来执行BF代码 - 懒惰(我想表示代码可能只在内存中保存)和容易。

2 个答案:

答案 0 :(得分:3)

这真的有必要吗?您是否遇到过您认为是盒装值结果的代码的性能问题?如果没有,请不要打扰。

如果您确实相信这种情况,那么this page in the GHC manual似乎以方便的列表格式提供了必要的限制。

主要观点似乎是未被编译器拒绝的多态函数或名称与未装箱类型之间的任何类型的交互仍然可能导致令人讨厌的空间泄漏。另外,在没有尝试的情况下,我怀疑你不会在溢出的情况下抛出异常,例如,所以大概你应该自己检测这种事情。一个简单的测试可以验证是否确实如此。

答案 1 :(得分:2)

这对我来说真的是一个不成熟的优化。当你有大量的BFInstruction坐在那里时,UNPACK非常有用。我怀疑你是否有足够的脑力学代码来使它值得。我同意吉安,它应该足够简单来测试,所以先做。

在任何情况下,使用UNPACK的值要注意的是,您不希望编译器必须重新加载它们。您应该使用数值运算,但除此之外,您必须仔细查看您正在使用的函数,以查看是否曾将解压缩的值用作非严格参数。唯一可以确定的方法是查看核心或接口文件,以查看哪些参数是严格的,哪些参数不是。一如既往,请务必使用至少“-O”进行编译。