Haskell中的严格优化和内存分配

Lan*_*ceH 18 optimization haskell functional-programming

我一直在通过实现一个特征选择算法来学习一些Haskell.

我从20秒开始将基准数据集的性能提升到5秒,其中C程序在0.5秒内处理相同的数据集.数据集可以在这里找到.要运行,调用编译的二进制像这样:./Mrmr 10 test_nci9_s3.csv.

代码在这里,我有兴趣优化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
Run Code Online (Sandbox Code Playgroud)

剖析器说:

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
Run Code Online (Sandbox Code Playgroud)

它显示了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]
Run Code Online (Sandbox Code Playgroud)

将大多数参数显示为Lazily评估和装箱(与严格和未装箱相反).

我尝试过BangPatterns,我尝试过MagicHash,但我似乎无法让它变得更快.

有人有什么建议吗?

Dan*_*ton 2

到目前为止,我不是这方面的专家,但我确实看到了一个小小的改进。在你的来源中我看到了这个:

mutualInfo n ... = foldl' (mutualInfoInnerLoop n $ U.zip xs ys) ...
Run Code Online (Sandbox Code Playgroud)

您不需要n == 0每次调用该函数时都进行检查,因为n调用它时您永远不会更改参数。该xys参数也不会改变,这意味着pxy在调用之间不会改变,因为它仅依赖于xysn。让我们利用这些东西来确保创建一个闭包,它只对这些东西进行一次评估。

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
Run Code Online (Sandbox Code Playgroud)

我不确定 GHC 是否足够聪明,可以自行执行此优化,也不确定这是否可以节省大量时间/空间,但这是我所拥有的最好的。那些刘海图案遍布各处,我想这是否是一个过于严格的情况。