newtypes比枚举更快吗?

mni*_*ish 22 haskell ghc

根据这篇文章,

就GHC而言,枚举不算作单构造函数类型,因此当用作严格的构造函数字段或严格的函数参数时,它们不会受益于解包.这是GHC的一个缺陷,但它可以解决.

而是建议使用newtypes.但是,我无法使用以下代码验证这一点:

{-# LANGUAGE MagicHash,BangPatterns #-}
{-# OPTIONS_GHC  -O2 -funbox-strict-fields -rtsopts -fllvm -optlc --x86-asm-syntax=intel #-}
module Main(main,f,g)
where       
import GHC.Base  
import Criterion.Main

data D = A | B | C
newtype E = E Int deriving(Eq)

f :: D -> Int#
f z | z `seq` False = 3422#
f z = case z of
  A -> 1234#
  B -> 5678#
  C -> 9012#

g :: E -> Int#
g z | z `seq` False = 7432#
g z = case z of
  (E 0) -> 2345#
  (E 1) -> 6789#
  (E 2) -> 3535#

f' x = I# (f x)
g' x = I# (g x)

main :: IO ()
main = defaultMain [ bench "f" (whnf f' A) 
                   , bench "g" (whnf g' (E 0)) 
                   ]
Run Code Online (Sandbox Code Playgroud)

查看程序集,枚举D的每个构造函数的标记实际上是解压缩的,并直接在指令中进行硬编码.此外,该功能f缺少错误处理代码,速度超过10%g.在更现实的情况下,我在将枚举转换为新类型后也经历了减速.谁能给我一些关于此的见解?谢谢.

Dan*_*her 18

这取决于用例.对于您拥有的功能,预计枚举的性能会更好.基本上,三个构造者D成为了Ints.Int#当严格性分析允许时,GHC知道它静态地检查了参数只能有三个值中的一个0#, 1#, 2#,所以它不需要插入错误处理代码f.因为E,没有给出三个值中只有一个可能的静态保证,因此它需要添加错误处理代码g,这会显着降低速度.如果更改定义,g以便最后一个案例成为

E _ -> 3535#
Run Code Online (Sandbox Code Playgroud)

差异完全或几乎完全消失(我得到1% - 2%更好的基准f仍然,但我没有做足够的测试,以确定这是真正的差异还是基准测试的工件).

但这不是维基页面所讨论的用例.它所讨论的是当类型是其他数据的一个组件时,将构造函数解压缩到其他构造函数中,例如

data FooD = FD !D !D !D

data FooE = FE !E !E !E
Run Code Online (Sandbox Code Playgroud)

然后,如果编译-funbox-strict-fields,三个Int#s可以解压缩到构造函数中FooE,所以你基本上得到相当于

struct FooE {
    long x, y, z;
};
Run Code Online (Sandbox Code Playgroud)

虽然FooD具有多构造函数类型的字段D并且无法解压缩到构造函数FD(1)中,所以基本上会给你

struct FooD {
    long *px, *py, *pz;
}
Run Code Online (Sandbox Code Playgroud)

这显然会产生重大影响.

我不确定单构造函数参数的情况.对于包含数据的类型(如元组),这有明显的优势,但是我不知道它将如何应用于普通枚举,你只需要case一个工作分离并且包装器对我来说没有意义.

无论如何,worker/wrapper转换不是一个单构造函数的东西,构造函数的特化可以给几个构造函数的类型带来同样的好处.(将创建多少个构造函数的特化项取决于的值-fspec-constr-count.)


(1)那可能已经改变了,但我对此表示怀疑.我没有检查过,所以页面可能已经过时了.

  • 不,我不敢.我使用`{ - #UNPACK# - }`pragma在`W'``的第二个参数上忽略了不可用的UNPACK编译指示,并且在核心中,没有解包严格的枚举字段,包括7.4.2和7.6 0.1. (2认同)

Pth*_*ame 5

我猜想GHC自2008年上次更新以来已经发生了很大的变化.此外,您正在使用LLVM后端,因此这可能会对性能产生一些影响.GHC可以(并且将会,因为你已经使用过-O2)从中删除任何错误处理代码f,因为它静态地知道这f是完全的.同样不能说g.我猜这是LLVM后端然后解压缩构造函数标签f,因为它可以很容易地看到分支条件没有使用任何其他东西.不过,我不确定.