Performance 优化创建过多垃圾(而不是堆栈溢出)的列表函数
我有一个Haskell函数,它导致我的程序中超过50%的分配,导致我60%的运行时间被GC占用。我使用一个小堆栈(Performance 优化创建过多垃圾(而不是堆栈溢出)的列表函数,performance,list,haskell,garbage-collection,automatic-differentiation,Performance,List,Haskell,Garbage Collection,Automatic Differentiation,我有一个Haskell函数,它导致我的程序中超过50%的分配,导致我60%的运行时间被GC占用。我使用一个小堆栈(-K10K)运行,因此没有堆栈溢出,但是我可以用更少的分配使这个函数更快吗 这里的目标是计算矩阵与向量的乘积。例如,我不能使用,因为这是使用包的更大函数的一部分,所以我需要使用Num列表。在运行时,我假设使用Numeric.AD模块意味着我的类型必须是Scalar Double listMProd :: (Num a) => [a] -> [a] -> [a] li
-K10K
)运行,因此没有堆栈溢出,但是我可以用更少的分配使这个函数更快吗
这里的目标是计算矩阵与向量的乘积。例如,我不能使用,因为这是使用包的更大函数的一部分,所以我需要使用Num
列表。在运行时,我假设使用Numeric.AD
模块意味着我的类型必须是Scalar Double
listMProd :: (Num a) => [a] -> [a] -> [a]
listMProd mdt vdt = go mdt vdt 0
where
go [] _ s = [s]
go ls [] s = s : go ls vdt 0
go (y:ys) (x:xs) ix = go ys xs (y*x+ix)
基本上,我们循环遍历矩阵,相乘并添加累加器,直到到达向量的末尾,存储结果,然后再次继续重新启动向量。我有一个quickcheck
测试,验证我得到的结果是否与hmatrix中的矩阵/向量积相同
我试过使用foldl
,foldr
等。我试过的任何东西都不能使函数更快(而且像foldr
这样的东西会导致空间泄漏)
运行profiling告诉我,除了此函数是花费大部分时间和分配的地方之外,还有大量创建的内容,Cells
是ad
包中的一种数据类型
要运行的简单测试:
import Numeric.AD
main = do
let m :: [Double] = replicate 400 0.2
v :: [Double] = replicate 4 0.1
mycost v m = sum $ listMProd m v
mygrads = gradientDescent (mycost (map auto v)) (map auto m)
print $ mygrads !! 1000
这在我的机器上告诉我GC 47%的时间都很忙
有什么想法吗?一个非常简单的优化是使
go
函数严格受其累加器参数的限制,因为它很小,如果A
是原始函数,并且始终需要进行充分的评估,则可以解除其绑定:
{-# LANGUAGE BangPatterns #-}
listMProd :: (Num a) => [a] -> [a] -> [a]
listMProd mdt vdt = go mdt vdt 0
where
go [] _ !s = [s]
go ls [] !s = s : go ls vdt 0
go (y:ys) (x:xs) !ix = go ys xs (y*x+ix)
在我的机器上,它的加速比为3-4倍(用-O2
编译)
另一方面,中间列表不应该很严格,这样就可以进行融合。更多信息!你是如何运行这个程序的?你的测试线束在哪里?你用的是什么混凝土类型?Haskell编译器的标志和版本是什么?添加了一些信息。该函数通过ad grad函数调用,ad grad函数使用自己的类型(Num实例)。剖析显示了“单元格”的分配。一些半知半解的建议:您是否考虑使用“可变代码状态”和“代码> ST <代码>?和/?也许(?)甚至值得将向量列表转换为其他内容,例如?我对这些技术都没有经验,但这些链接可能会对你有进一步的帮助。排除一件显而易见的事情:如果你在
go
的最后一句中添加一个bang模式,例如go(y:ys)(x:xs)!ix=go-ys-xs(y*x+ix)
你能给出一个最小的可执行示例吗?嗯,好主意,但在我的用例中根本没有帮助(在速度或GC使用方面没有改进)。我认为函数是通过ad库调用的,这一事实影响了性能(我看到一个单元格数据类型带有严格的Int字段)。