【问题标题】:Strictness optimization and memory allocation in HaskellHaskell 中的严格优化和内存分配
【发布时间】:2011-12-11 17:45:34
【问题描述】:

我一直在通过实现特征选择算法来学习一些 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]

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

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

大家有什么建议吗?

【问题讨论】:

  • 我猜你已经尝试过 ghc -O2 了?
  • 是的,我有。源代码也用{-# LANGUAGE BangPatterns #-}{-# OPTIONS_GHC -O2 -fexcess-precision -funbox-strict-fields #-}注释
  • 您能否提供Int 和您用于基准测试的文件?
  • 核心不足为奇。您已将 mutualInfoInnerLoop 注释为 NOINLINE,因此 GHC 不会内联它。因此,没有一个论点可以被拆箱。如果将 NOINLINE 更改为 INLINE 会发生什么?
  • 如果我们的想法是满足或击败 C 代码,那么我们也需要它。

标签: optimization haskell functional-programming


【解决方案1】:

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

mutualInfo n ... = foldl' (mutualInfoInnerLoop n $ U.zip xs ys) ...

您无需在每次调用该函数时都检查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

我不确定 GHC 是否足够聪明,可以自行执行此优化,也不确定这会节省多少时间/空间,但这是我所拥有的最好的。到处都是刘海图案,不知道是不是太严格了。

【讨论】:

  • 很好的建议!实际上,我最终将 n == 0 移动到 doMrmr 中,这似乎是个好主意。至于这种构造,如果不解包 i 或 j,我们就无法构造 accumEq2 ——这让我想知道 accumEq2 的动态构造特性是否是问题所在。
  • 实际上,所有这些 bang 模式似乎都有帮助,缩短了半秒(我们实际上在 4 秒左右)。
  • 哦,我忘记了 accumEq2 中使用了 i 和 j。我的错。
猜你喜欢
  • 2018-08-22
  • 2023-03-22
  • 1970-01-01
  • 2017-11-23
  • 1970-01-01
  • 2016-10-25
  • 2012-11-12
  • 2021-09-06
  • 1970-01-01
相关资源
最近更新 更多