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

标签 performance haskell

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

但是 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"
    

    然后添加 HasCallStackfoobar (但不理会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/

    相关文章:

    java - 使用最新 java 版本重新编译旧 java 项目的性能优势

    java - 快速计算两个算术级数的交点

    c# - 太多的扩展方法会降低性能吗?怎么运行的

    haskell - 为什么 Hackage 上对的 Monad 实例没有返回实现?

    haskell - 赋值前处理monad值

    haskell - 谁能解释 ((.)$(.)) (==) 1 (1+) 0 的含义

    haskell - 在函数 Haskell 中定义变量

    Java Web 应用程序真的很慢

    list - 在列表列表中查找 x

    mysql - 用户界面自动完成 : Make multiple ajax requests or load all data at once for a list of locations in a city?