Performance HasCallStack如何影响Haskell中正常分支的性能?

Performance HasCallStack如何影响Haskell中正常分支的性能?,performance,haskell,Performance,Haskell,当到达错误分支时生成调用堆栈具有运行时开销;这很容易理解 但是HasCallStack约束也会影响正常分支的性能吗?如何?向函数foo添加HasCallStack约束的效果或多或少相当于: 将调用堆栈的附加输入参数添加到foo的参数列表中 在调用foo的任何位置,通过将信息帧(包括函数名“foo”和调用它的源位置)推到输入调用堆栈上,为其构造调用堆栈参数(如果foo是从另一个具有HasCallStack约束的函数调用的)或者到空调用堆栈上(如果从没有HasCallStack约束的函数调用)

当到达错误分支时生成调用堆栈具有运行时开销;这很容易理解


但是
HasCallStack
约束也会影响正常分支的性能吗?如何?

向函数
foo
添加
HasCallStack
约束的效果或多或少相当于:

  • 将调用堆栈的附加输入参数添加到
    foo
    的参数列表中
  • 在调用
    foo
    的任何位置,通过将信息帧(包括函数名“foo”和调用它的源位置)推到输入调用堆栈上,为其构造调用堆栈参数(如果
    foo
    是从另一个具有
    HasCallStack
    约束的函数调用的)或者到空调用堆栈上(如果从没有
    HasCallStack
    约束的函数调用)
所以。。。如果您有一些功能:

foo :: HasCallStack => Int -> String -> String
foo n = bar n '*'

bar :: HasCallStack => Int -> Char -> String -> String
bar n c str = if n >= 0 then c' ++ ' ':str ++ ' ':c'
              else error "bad n"
  where c' = replicate n c

baz :: String
baz = foo 3 "hello"
然后将
HasCallStack
添加到
foo
bar
(但不使用
baz
)的效果基本上与您编写的相同:

foo cs n = bar cs' n
  where cs' = pushCallStack ("bar", <loc>) cs
bar cs n c str
  = if n >= 0 then c' ++ ' ':str ++ ' ':c'
    else error cs' "bad n"
  where c' = replicate n c
        cs' = pushCallStack ("error", <loc>) cs
baz = foo cs' 3 "hello"
  where cs' = pushCallStack ("foo", <loc>) emptyCallStack
foo cs n=bar cs'n
其中cs'=pushCallStack(“bar”)cs
巴cs n c str
=如果n>=0,则c'++':str++':c'
else错误cs的“错误n”
其中c'=复制n c
cs'=pushCallStack(“错误”,)cs
baz=foo cs'3“你好”
其中cs'=pushCallStack(“foo”),emptyCallStack
因此,基线、未优化的性能成本是使用
HasCallStack
修饰的每个函数的额外参数成本加上为修饰函数的每个调用点提供该参数的thunk分配成本。(即使未触发错误,也会支付这些费用。)

在实践中,优化的代码将。。。嗯。。。优化。例如,如果上面的例子是用
-O2
编译的,
foo
将是内联的,
bar
将专门用于
baz
的定义,这样调用堆栈的唯一运行时开销就是静态指针(指向为
错误
调用创建完整调用堆栈的thunk)传递到专用版本的
(但被忽略,因为不会生成错误)


GHC似乎不够聪明,无法确定
baz
永远不会遵循
错误
的情况,因此根本不需要堆栈框架。

我想在运行时必须保留更多信息,以防出现错误。我们必须将“正常分支”视为未出现错误的计算,但可能会在将来出现。因此,向total函数中添加
HasCallStack
,可以像没有
HasCallStack
一样进行优化,但GHC不够聪明,无法对其进行如此优化,因此,即使在total函数中添加
HasCallStack
,现在也会有运行时开销,对吗?我认为GHC可以更好地使用“静态总计”函数,而不需要分析运行时行为;这样的函数仍然需要一个额外的参数,但如果是内联的,可能会优化调用堆栈。不过,我认为你不能信赖它。