问题
我想在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-O3
标志进行编译(因为我是在没有使用-force-recompile
的情况下进行编译的),所以 GHC 没有进行这样的优化。请问如果编译器能够像此类表达式一样进行优化,那么我们为什么要随时使用specialize pragma
呢? - Wojciech Danilo