22

在以下代码中:

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 限制的代码执行不可接受,并且我正在寻找可能的错误,以推理复杂性。

4

4 回答 4

17

作为一个演示,你可以从分析你的 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,其他人都太快了甚至没有注意到,所以它必须在这里只计算一次最大值。

于 2010-11-12T17:11:52.907 回答
11

这是您的代码的修改版本,可让您查看是否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 开始做归约,直到所有的参数都被提供给一个函数。

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

于 2010-11-12T17:15:19.463 回答
6

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

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

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

于 2010-11-12T18:25:30.790 回答
1

我无法在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

但是当我改变c2,c3c5tolet c2 = 2 == 99999等的定义时(保持c1原样),我得到

$ ghc a.hs
$ time ./a.out 
[False,False,False,False]

real    0m0.113s
user    0m0.060s
sys     0m0.028s
于 2010-11-12T17:11:15.063 回答