如何优化毕达哥拉斯三元组的实现

时间:2019-01-11 20:35:03

标签: haskell optimization allocation

这是haskell代码

import GHC.Int

triples = [(x, y, z) | z <- [(1::Int32)..],
                       x <- [(1::Int32) .. z + 1],
                       y <- [x.. z + 1],
                       x * x + y * y == z * z]

main = mapM_ print (Prelude.take 1000 triples)

具有以下个人资料

       triples +RTS -p -RTS

    total time  =       47.10 secs   (47103 ticks @ 1000 us, 1 processor)
    total alloc = 62,117,115,176 bytes  (excludes profiling overheads)

COST CENTRE MODULE    SRC                      %time %alloc

triples     Main      triples.hs:(5,1)-(8,46)  100.0  100.0

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

MAIN         MAIN                  <built-in>              118          0    0.0    0.0   100.0  100.0
 CAF         Main                  <entire-module>         235          0    0.0    0.0   100.0  100.0
  main       Main                  triples.hs:10:1-46      236          1    0.0    0.0     0.0    0.0
  triples    Main                  triples.hs:(5,1)-(8,46) 237          1  100.0  100.0   100.0  100.0
 CAF         GHC.Conc.Signal       <entire-module>         227          0    0.0    0.0     0.0    0.0
 CAF         GHC.IO.Encoding       <entire-module>         216          0    0.0    0.0     0.0    0.0
 CAF         GHC.IO.Encoding.Iconv <entire-module>         214          0    0.0    0.0     0.0    0.0
 CAF         GHC.IO.Handle.FD      <entire-module>         206          0    0.0    0.0     0.0    0.0
 CAF         GHC.IO.Handle.Text    <entire-module>         144          0    0.0    0.0     0.0    0.0
 main        Main                  triples.hs:10:1-46      238          0    0.0    0.0     0.0    0.0

等效的rust代码运行速度快一个数量级。对我来说这很奇怪。

fn triples() -> impl Iterator<Item=(i32, i32, i32)> {
    (1..).flat_map(|z| {
        (1..z + 1).flat_map(move |x| {
            (x..z + 1).filter_map(move |y| {
                if x * x + y * y == z * z {
                    Some((x, y, z))
                } else {
                    None
                }
            })
        })
    })
}

fn main() {
    for triple in triples().take(1000) {
        println!("{:?}", triple);
        // unsafe {printf("(%i, %i, %i)\n".as_ptr() as *const i8, x, y, z)};
    }
}

结果是

[I] ~/c/pythagoras (master|✚1…) $ time ./range > /dev/null
0.16user 0.00system 0:00.16elapsed 100%CPU (0avgtext+0avgdata 2248maxresident)k
0inputs+0outputs (0major+124minor)pagefaults 0swaps
[I] ~/c/pythagoras (master|✚1…) $ time ./triples > /dev/null
2.39user 0.00system 0:02.39elapsed 99%CPU (0avgtext+0avgdata 4736maxresident)k
0inputs+0outputs (0major+473minor)pagefaults 0swaps

两个结果都带有-O3标志。

是否可以在节省惯用的haskell代码的同时优化分配?也许某些融合库或某些方法可以做到这一点?

编辑1。好的,使用Int代替Int32Int64可以使代码更快,这很好。仍然使用fflvm,它的速度要比生锈慢两倍,并且根据配置文件判断,它仍然大部分时间都花在分配上。是什么阻止了haskell重用三元组,而不是仅将其分配一次?

2 个答案:

答案 0 :(得分:5)

您的代码有两个问题:

  1. 出于性能考虑,您应该编译而不分析,并进行优化。分析会增加大量开销。在我的系统上,ghc -prof的运行时间超过了 40 秒,与您的时间类似。没有ghc -O2的{​​{1}}仅产生 4.2 秒。

  2. 在64位系统上使用-prof。您不应该这样做,因为非本机大小的Int32操作会被编译以减慢离线primop的速度。当我将Int更改为Int32时,运行时间变为 0.44 秒。如果我在LLVM代码后端另外使用Int,则会得到 0.2 秒。

答案 1 :(得分:2)

也许会更改您的实现?

triples = [(m^2-n^2,2*m*n,m^2+n^2) | m<-[2..], n<-[1..(m-1)]]