【发布时间】:2015-09-24 15:29:36
【问题描述】:
我有那个 Haskell 函数,这导致我的程序的所有分配超过 50%,导致 GC 占用了我 60% 的运行时间。我使用一个小堆栈 (-K10K) 运行,所以没有堆栈溢出,但我可以让这个函数更快,分配更少吗?
这里的目标是计算矩阵与向量的乘积。例如,我不能使用hmatrix,因为这是使用ad Automatic Differentiation 包的更大功能的一部分,所以我需要使用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 这样的一些东西会导致空间泄漏)。
运行分析告诉我,除了这个函数是花费大部分时间和分配的地方之外,还有大量 Cells 正在创建,Cells 是来自 @987654341 的数据类型@包。
运行一个简单的测试:
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% 的时间都在忙。
有什么想法吗?
【问题讨论】:
-
更多信息!你是如何运行这个程序的?你的测试工具在哪里?您使用的是什么具体类型? Haskell 编译器的标志和版本是什么?
-
添加了一些信息。该函数通过使用自己的类型(Num 实例)的 ad grad 函数调用。分析显示“单元”的分配。
-
一些半知半解的建议:您是否考虑过在
ST中使用可变状态?还有stream-fusion/conduit/pipes?也许(?)将您的矢量列表转换为其他东西甚至是值得的,例如unboxed vector?我没有任何这些技术的经验,但也许这些链接可以帮助你进一步。 -
只是为了排除一个明显的事情:如果你在
go的最后一个子句中添加一个爆炸模式,例如go (y:ys) (x:xs) !ix = go ys xs (y*x+ix) -
你能给出一个最小的可执行示例吗?
标签: performance list haskell garbage-collection automatic-differentiation