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

Suggestions for reducing allocations (and work) in this Haskell function

我的(更大的)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%(以及 [=20] 的大约 40% =] 工作)。

有什么方法可以减少 interpret 的工作量或分配的工作量?这可能会为我赢得巨大的性能提升(这是我真正需要的,因为我需要多次 运行 这段代码,用于实验)。

我会尝试 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 的类似物。

使用列表而不是集合可能也有好处。集合,作为实施,是严格的,并且(我认为)不会触发像 fusion/deforestation/etc 这样的一些优化。列表是延迟生成的,在这种代码中应该表现得更好。

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

一个观察:

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

evaluatedevalClauseevalForm 中都有类型 Set (Maybe Bool) 可以用适合 Int.

的三个位来表示

我会定义:

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

并更改 intepret return 的签名 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 设置,位 2 设置如果 JustTrue 在集合中。这将从您的计算中消除集合的分配。