使用类和实例时的Haskell性能表现

9

问题

我想在Haskell中模拟一个多输出函数。这个Haskell代码是自动生成的(而不是手写的)- 这是重要信息,请看下面:

当然,可以通过从函数返回元组来轻松实现这一点,例如

f x y = (x+y, x-y)

但是当使用这样的函数时,我必须知道它返回什么类型的元组:

...
(out_f_1, out_f_2)          = f a b
(out_g_1, out_g_2, out_g_3) = g out_f_1
...

等等……但是在生成代码时,我不知道f的输出类型是什么,因此现在我正在使用Data.List.Select包,并模拟上面的内容:

import Data.List.Select
...
out_f = f a b
out_g = g (sel1 outf)
...

问题在于性能 - 在我的测试程序中,使用 Data.List.Select 的版本比手写的版本慢了两倍。
这是非常明显的情况,因为 Data.List.Select 是使用类和实例编写的,所以它使用某种运行时字典(如果我没有错的话)。(http://hackage.haskell.org/packages/archive/tuple/0.2.0.1/doc/html/src/Data-Tuple-Select.html#sel1
问题是:
我想问你是否有可能以某种方式编译使用 Data.List.Select 的版本,使其与手工制作的版本一样快?
我认为应该有一个开关可以告诉编译器为每个用途“实例化”类和接口(类似于 C++ 中的模板)。
基准测试:
Test1.hs:
import qualified Data.Vector as V
import System.Environment
b :: Int -> Int
b x = x + 5
c x = b x + 1
d x = b x - 1
a x = c x + d x
main = do
   putStrLn "Starting..."
   args <- getArgs
   let iternum = read (head args) :: Int in do
      putStrLn $ show $ V.foldl' (+) 0 $ V.map (\i -> a (iternum-i))
         $ V.enumFromTo 1 iternum
      putStrLn "Done."

使用ghc -O3 Test1.hs进行编译。

Test2.hs:

import qualified Data.Vector as V
import Data.Tuple.Select
import Data.Tuple.OneTuple

import System.Environment
b x = OneTuple $ x + 5
c x = OneTuple $ (sel1 $ b x) + 1
d x = OneTuple $ (sel1 $ b x) - 1
a x = OneTuple $ (sel1 $ c x) + (sel1 $ d x)
main = do
   putStrLn "Starting..."
   args <- getArgs
   let iternum = read (head args) :: Int in do
      putStrLn $ show $ V.foldl' (+) 0 $ V.map (\i -> sel1 $ a (iternum-i))
         $ V.enumFromTo 1 iternum
      putStrLn "Done."

请使用ghc -O3 Test2.hs进行编译。

结果

time ./Test1 10000000 = 5.54 s
time ./Test2 10000000 = 10.06 s

这两个基准测试对我来说表现相同。在这两种情况下,它们都会产生操作未装箱整数的尾递归循环。性能差异的一个可能原因是,由于将所有内容都包装在 OneTuple 中,您的第二个基准测试更受额外指针间接引用的影响,因为 GHC 在这种情况下可以轻松省略类型类字典。 - sabauma
@sabauma - 好的,我明白了!我的测试没有使用-O3标志进行编译(因为我是在没有使用-force-recompile的情况下进行编译的),所以 GHC 没有进行这样的优化。请问如果编译器能够像此类表达式一样进行优化,那么我们为什么要随时使用specialize pragma呢? - Wojciech Danilo
3
这是一个对编译器来说相对容易优化的例子。由于 GHC 能够在编译时内联多态调用并确定使用哪个类型类实例,因此在这种情况下可以省略类型类查找。在不总是内联大型函数的情况下,仍然有利于 GHC 生成专门版本的函数。 - sabauma
2个回答

3

0

好的,我发布的结果并不准确——就像@sabauma所说的那样——如果你启用了优化,这两个代码会同时执行。

@tohava的答案非常好,如果你想明确显示要专门化哪些函数(请参见上面的@sabauma评论)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接