到达错误分支时生成调用堆栈具有运行时成本;这很容易理解。
但是 HasCallStack
约束也会影响正常分支的性能吗?如何?
最佳答案
添加HasCallStack
的效果对函数的约束foo
或多或少等同于:
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
因此,未优化的基准性能成本是每个用
HasCallStack
修饰的函数的额外参数的成本。加上为修饰函数的每个调用点提供该参数的 thunk 分配的成本。 (即使没有触发错误,也会支付这些费用。)在实践中,优化的代码将......呃......优化。例如,如果上面的例子是用
-O2
编译的, foo
将被内联和 bar
将专门定义baz
这样调用堆栈的唯一运行时成本是静态指针(指向为 error
调用创建完整调用堆栈的 thunk)被传递给 bar
的专用版本。 (但被忽略,因为没有产生错误)。GHC 似乎不够聪明,无法确定
baz
永远不会跟随error
案例,因此根本不需要堆栈帧。
关于performance - HasCallStack 如何影响 Haskell 中正常分支的性能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57471398/