根据这篇文章,
就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
成为了Int
s.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)那可能已经改变了,但我对此表示怀疑.我没有检查过,所以页面可能已经过时了.
我猜想GHC自2008年上次更新以来已经发生了很大的变化.此外,您正在使用LLVM后端,因此这可能会对性能产生一些影响.GHC可以(并且将会,因为你已经使用过-O2
)从中删除任何错误处理代码f
,因为它静态地知道这f
是完全的.同样不能说g
.我猜这是LLVM后端然后解压缩构造函数标签f
,因为它可以很容易地看到分支条件没有使用任何其他东西.不过,我不确定.
归档时间: |
|
查看次数: |
606 次 |
最近记录: |