关于减少此Haskell函数中的分配(和工作)的建议

时间:2016-07-03 10:55:41

标签: performance haskell optimization allocation

我的(更大)Haskell代码中有以下函数(有一些支持代码可以清楚说明是什么):

import qualified Data.Set as S
import qualified Data.IntMap.Strict as M
import Data.Ord
import Data.Monoid

data Atom = Neg { index :: Int }
          | Pos { index :: Int }
          deriving (Eq, Ord, Show, Read) 

newtype Clause = Clause { atoms :: S.Set Atom }
  deriving (Eq, Show, Read)

instance Ord Clause where
  compare = comparing (Down . S.size . atoms) <> comparing atoms

newtype Form = Form { clauses :: S.Set Clause }
  deriving (Eq, Ord, Show, Read)

type Interpretation = M.IntMap Bool

-- the function of interest
interpret :: Interpretation -> Form -> Maybe Bool
interpret interp = evalForm
  where evalAtom x@(Pos _) = M.lookup (index x) interp
        evalAtom x@(Neg _) = not <$> M.lookup (index x) interp
        evalClause (Clause x)
          | S.member (Just False) evaluated = Just False
          | evaluated == S.singleton (Just True) = Just True
          | otherwise = Nothing
          where evaluated = S.map evalAtom x
        evalForm (Form x)
          | S.member (Just True) evaluated = Just True
          | evaluated == S.singleton (Just False) = Just False
          | otherwise = Nothing
          where evaluated = S.map evalClause x

在分析了我的Haskell程序之后,我发现这个interpret函数的分配占我程序中所有分配的近40%(以及大约40%的CPU工作)。

有什么方法可以减少interpret的工作量或者分配的金额?这可能会让我获得巨大的性能提升(我可能真的需要,因为我需要多次运行此代码,以进行实验)。

2 个答案:

答案 0 :(得分:2)

我会尝试S.foldr

从你的代码中看起来好像这些是AND子句,所以我假设一个空子句是假的。

evalClause (Clause x) = S.foldr f (Just False) $ S.map evalAtom x
     where f b@(Just False) _              = b
           f (Just True)    y              = y
           f Nothing        y@(Just False) = y
           f Nothing        y              = Nothing

evalForm类似。

使用列表而不是集合也可能是有益的。实施的集合是严格的,并且(我认为)不会触发一些优化,如融合/砍伐森林等。列表是懒惰产生的,并且在这种代码中应该表现得更好。

evalClause (Clause x) = foldr f (Just False) . map evalAtom $ S.toList x
     ...

答案 1 :(得分:2)

观察:

Maybe Bool只能有三个可能的值 - NothingJust FalseJust True

evaluatedevalClause中的

evalForm都有Set (Maybe Bool)类型,可以用三位代表Int。

我会定义:

data MaybeBool = Nuthin | JustFalse | JustTrue
  deriving (Eq, Ord, Enum, Bounded, Show, Read)

并更改intepret的签名,返回MaybeBool

然后将evaluated定义为这样的位集:

import Data.Bits

evaluated = foldl' combine 0 (map evalAtom (S.toList x))
  where combine s a = s .|. (1 `shiftLeft` fromEnum a)

evaluated将是0到7之间的Int,如果Nutin在集合中,则设置为0,如果JustFalse在集合中,则设置为位1,如果JustTrue,则设置为位2 {1}}在集合中。这将消除计算中的集合分配。