【问题标题】:Is performance of partial or curried functions well defined in Haskell?Haskell 中是否很好地定义了部分或柯里化函数的性能?
【发布时间】:2011-05-09 04:35:20
【问题描述】:

在以下代码中:

ismaxl :: (Ord a) => [a] -> a -> Bool
ismaxl l x = x == maxel
           where maxel = maximum l

main = do
  let mylist = [1, 2, 3, 5]
  let ismax = ismaxl mylist
  --Is each call O(1)?  Does each call remember maxel?
  let c1 = ismax 1
  let c2 = ismax 2
  let c3 = ismax 3
  let c5 = ismax 5
  putStrLn (show [c1, c2, c3, c5])

偏函数是max,计算maxel吗?特别是,有人可以指出关于 Haskell 中部分函数复杂性的规则吗?编译器必须在上面的例子中只调用一次最大值吗?换句话说,部分函数是否保留了对内部 where 子句的先前调用的引用?

我有一些受 CPU 限制的代码执行不可接受,我正在寻找可能的错误,以推理复杂性。

【问题讨论】:

  • 个人资料。个人资料个人资料。
  • 让我补充一下@delnan 所说的suggesting that you profile the code
  • 从什么时候开始在 Haskell 中定义性能?也许你的意思是 Haskell 的一些实现。

标签: performance haskell profiling lazy-evaluation partial-application


【解决方案1】:

作为一个演示,您可以从分析您的 Haskell 代码中学到什么,下面是对您的代码进行一些小的修改的结果。首先,我将mylist 替换为[0..10000000],以确保计算最大值需要一段时间。

这是运行该版本后分析输出中的一些行:

COST CENTRE                    MODULE               %time %alloc

ismaxl                         Main                  55.8    0.0
main                           Main                  44.2  100.0

                                                         individual    inherited
COST CENTRE              MODULE         no.    entries  %time %alloc   %time %alloc

MAIN                     MAIN            1           0   0.0    0.0   100.0  100.0
 CAF:main_c5             Main          225           1   0.0    0.0    15.6    0.0
  main                   Main          249           0   0.0    0.0    15.6    0.0
   ismaxl                Main          250           1  15.6    0.0    15.6    0.0
 CAF:main_c3             Main          224           1   0.0    0.0    15.6    0.0
  main                   Main          246           0   0.0    0.0    15.6    0.0
   ismaxl                Main          247           1  15.6    0.0    15.6    0.0
 CAF:main_c2             Main          223           1   0.0    0.0    14.3    0.0
  main                   Main          243           0   0.0    0.0    14.3    0.0
   ismaxl                Main          244           1  14.3    0.0    14.3    0.0
 CAF:main_c1             Main          222           1   0.0    0.0    10.4    0.0
  main                   Main          239           0   0.0    0.0    10.4    0.0
   ismaxl                Main          240           1  10.4    0.0    10.4    0.0
 CAF:main8               Main          221           1   0.0    0.0    44.2  100.0
  main                   Main          241           0  44.2  100.0    44.2  100.0

很明显这里是重新计算最大值。

现在,将 ismaxl 替换为:

ismaxl :: (Ord a) => [a] -> a -> Bool
ismaxl l = let maxel = maximum l in (== maxel)

...并再次进行分析:

COST CENTRE                    MODULE               %time %alloc

main                           Main                  60.5  100.0
ismaxl                         Main                  39.5    0.0

                                                         individual    inherited
COST CENTRE              MODULE         no.    entries  %time %alloc   %time %alloc

MAIN                     MAIN            1           0   0.0    0.0   100.0  100.0
 CAF:main_c5             Main          227           1   0.0    0.0     0.0    0.0
  main                   Main          252           0   0.0    0.0     0.0    0.0
   ismaxl                Main          253           1   0.0    0.0     0.0    0.0
 CAF:main_c3             Main          226           1   0.0    0.0     0.0    0.0
  main                   Main          249           0   0.0    0.0     0.0    0.0
   ismaxl                Main          250           1   0.0    0.0     0.0    0.0
 CAF:main_c2             Main          225           1   0.0    0.0     0.0    0.0
  main                   Main          246           0   0.0    0.0     0.0    0.0
   ismaxl                Main          247           1   0.0    0.0     0.0    0.0
 CAF:main_c1             Main          224           1   0.0    0.0     0.0    0.0
 CAF:main_ismax          Main          223           1   0.0    0.0    39.5    0.0
  main                   Main          242           0   0.0    0.0    39.5    0.0
   ismaxl                Main          243           2  39.5    0.0    39.5    0.0
 CAF:main8               Main          222           1   0.0    0.0    60.5  100.0
  main                   Main          244           0  60.5  100.0    60.5  100.0

...这次它把大部分时间都花在了一次调用 ismaxl 上,其他的都太快了,甚至没有注意到,所以它必须在这里只计算一次最大值。

【讨论】:

    【解决方案2】:

    这是您的代码的修改版本,可让您查看 maxel 是否被重用:

    import Debug.Trace
    
    ismaxl :: (Ord a) => [a] -> a -> Bool
    ismaxl l x = x == maxel
               where maxel = trace "Hello" $ maximum l
    
    main = do
      let mylist = [1, 2, 3, 5]
      let ismax = ismaxl mylist
      --Is each call O(1)?  Does each call remember maxel?
      let c1 = ismax 1
      let c2 = ismax 2
      let c3 = ismax 3
      let c5 = ismax 5
      putStrLn (show [c1, c2, c3, c5])
    

    您会看到maxel 在应用程序之间没有被“记住”。

    一般来说,你不应该期望 Haskell 开始做归约,直到 所有 的参数都被提供给一个函数。

    另一方面,如果您开启了积极的优化,则很难预测特定编译器实际上会做什么。但是您可能不应该依赖编译器中难以预测的任何部分,因为您可以轻松地重写代码以明确您想要的内容。

    【讨论】:

      【解决方案3】:

      在其他好的答案的基础上,根据我的经验,GHC 并不急于执行这种优化。如果我不能轻易地做一些无点的事情,我经常会使用 LHS 上的绑定变量和 lambda 的组合来编写:

      ismaxl :: (Ord a) => [a] -> a -> Bool
      ismaxl l = \x -> x == maxel
                 where maxel = maximum l
      

      我不是特别喜欢这种风格,但它确实确保maxel 在对部分应用的ismaxl 的调用之间共享。

      【讨论】:

      • 更明确地说:ismaxl l = let maxel = maximum l in \x -> x == maxel。对于编译器来说,它或多或少是相同的,但在我看来,“let”在 lambda 之外似乎更明显。
      【解决方案4】:

      我在Haskell Report 中没有找到任何这样的要求,实际上 GHC 似乎没有默认执行这个优化。

      我将你的 main 函数更改为

      main = do
        let mylist = [1..99999]
        let ismax = ismaxl mylist
        let c1 = ismax 1
        let c2 = ismax 2
        let c3 = ismax 3
        let c5 = ismax 5
        putStrLn (show [c1, c2, c3, c5])
      

      简单的分析显示(在我的旧 Pentium 4 上):

      $ ghc a.hs
      $ time ./a.out 
      [False,False,False,False]
      
      real    0m0.313s
      user    0m0.220s
      sys     0m0.044s
      

      但是当我将c2c3c5 的定义更改为let c2 = 2 == 99999 等(保持c1 不变)时,我得到了

      $ ghc a.hs
      $ time ./a.out 
      [False,False,False,False]
      
      real    0m0.113s
      user    0m0.060s
      sys     0m0.028s
      

      【讨论】:

      • 大家都在谈论的行为不在 Haskell 报告中。使用按名称调用而不是惰性(重复替换工作)的 Haskell 实现将是一致的。但是没有人会使用它,因为它太慢了:-P
      猜你喜欢
      • 2018-04-18
      • 1970-01-01
      • 2012-03-14
      • 2011-04-22
      • 1970-01-01
      • 2022-08-14
      • 2014-04-14
      • 2013-06-03
      • 1970-01-01
      相关资源
      最近更新 更多