我一直在通过实现特征选择算法来学习一些Haskell。
我已经将基准数据集从20年代的性能提升到5秒,其中C程序在0.5秒内处理相同的数据集。可以找到数据集here。要运行,请调用已编译的二进制文件,如下所示:./Mrmr 10 test_nci9_s3.csv
。
代码是here,我对优化mutualInfoInnerLoop感兴趣:
mutualInfoInnerLoop :: Double -> Data.Vector.Unboxed.Vector (Int, Int) -> Double -> (Int, Int, Double) -> Double
mutualInfoInnerLoop n xys !acc (!i, !j, !px_py)
| n == 0 || px_py == 0 || pxy == 0 = acc
| otherwise = pxy * logBase 2 ( pxy / px_py ) + acc
where
pxy = ( fromIntegral . U.foldl' accumEq2 0 $ xys ) / n
accumEq2 :: Int -> (Int, Int) -> Int
accumEq2 !acc (!i', !j')
| i' == i && j' == j = acc + 1
| otherwise = acc
剖析器说:
COST CENTRE MODULE %time %alloc
mutualInfoInnerLoop Main 75.0 47.9
mutualInfo Main 14.7 32.1
parseCsv Main 5.9 13.1
CAF GHC.Float 1.5 0.0
readInt Main 1.5 1.2
doMrmr Main 1.5 4.0
它显示了mutualInfoInnerLoop作为50%的分配,75%的运行时在程序中。分配令人不安。
此外,该功能的核心有一个签名:
mutualInfoInnerLoop_rXG
:: GHC.Types.Double
-> Data.Vector.Unboxed.Base.Vector (GHC.Types.Int, GHC.Types.Int)
-> GHC.Types.Double
-> (GHC.Types.Int, GHC.Types.Int, GHC.Types.Double)
-> GHC.Types.Double
[GblId,
Arity=4,
Caf=NoCafRefs,
Str=DmdType U(L)LU(L)U(U(L)U(L)U(L))m]
将大部分参数显示为Lazily评估和装箱(与严格和未装箱相反)。
我尝试过BangPatterns,我尝试过MagicHash,但我似乎无法让它变得更快。
有人有任何建议吗?
答案 0 :(得分:2)
到目前为止,我还没有专家,但我确实看到了一个小改进。在你的来源中我看到了:
mutualInfo n ... = foldl' (mutualInfoInnerLoop n $ U.zip xs ys) ...
每次调用函数时都不需要检查n == 0
,因为在调用函数时从不更改n
参数。 xys
参数也不会发生变化,这意味着pxy
不会在调用中发生变化,因为它仅依赖于xys
和n
。让我们利用这些东西来确保创建一个只关闭这些东西的闭包。
mutualInfoInnerLoop n xys
| n == 0 || pxy == 0 = const
| otherwise = go
where pxy = (fromIntegral . U.foldl' accumEq2 0 $ xys) / n
accumEq2 :: Int -> (Int, Int) -> Int
accumEq2 !acc (!i', !j')
| i' == i && j' == j = acc + 1
| otherwise = acc
go !acc (!i, !j, !px_py)
| px_py == 0 = acc
| otherwise = pxy * logBase 2 ( pxy / px_py ) + acc
我不确定GHC是否足够智能自行执行此优化,我也不确定这会节省多少时间/空间,但这是我得到的最好的。随着那些爆炸模式遍布各地,我想知道这是否是一个过于严格的情况。