Skip to content

Speed up foldMap for sequences #504

@oisdk

Description

@oisdk

I noticed that foldMapWithIndex on sequences is significantly faster than foldMap in certain cases:

benchmarking 50000/getSum . foldMap Sum
time                 1.051 ms   (1.003 ms .. 1.093 ms)
                     0.988 R²   (0.983 R² .. 0.994 R²)
mean                 1.123 ms   (1.094 ms .. 1.154 ms)
std dev              110.7 μs   (93.16 μs .. 131.6 μs)
variance introduced by outliers: 72% (severely inflated)

benchmarking 50000/getSum . foldMapWithIndex (const Sum)
time                 435.5 μs   (433.0 μs .. 438.2 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 433.1 μs   (431.5 μs .. 435.4 μs)
std dev              6.490 μs   (4.910 μs .. 9.372 μs)

benchmarking 50000/foldl' (+) 0
time                 420.6 μs   (419.2 μs .. 422.8 μs)
                     1.000 R²   (0.999 R² .. 1.000 R²)
mean                 429.2 μs   (425.4 μs .. 433.8 μs)
std dev              15.71 μs   (12.04 μs .. 19.77 μs)
variance introduced by outliers: 30% (moderately inflated)

Unfortunately, the core output is too large for me to really understand, but I think there's some specialisation not happening which does happen in the manually-specialised foldMapWithIndex.

When the definition for foldMap mimics foldMapWithIndex:

instance Foldable Seq where
    foldMap f' (Seq xs') = foldMapTreeE (lift_elem f') xs'
      where
        lift_elem :: (a -> m) -> (Elem a -> m)
#if __GLASGOW_HASKELL__ >= 708
        lift_elem g = coerce g
#else
        lift_elem g = \(Elem a) -> g a
#endif
        foldMapTreeE :: Monoid m => (Elem a -> m) -> FingerTree (Elem a) -> m
        foldMapTreeE _ EmptyT = mempty
        foldMapTreeE f (Single xs) = f xs
        foldMapTreeE f (Deep _ pr m sf) = 
            foldMapDigitE f pr <>
            foldMapTreeN (foldMapNodeE f) m <>
            foldMapDigitE f sf

        foldMapTreeN :: Monoid m => (Node a -> m) -> FingerTree (Node a) -> m
        foldMapTreeN _ EmptyT = mempty
        foldMapTreeN f (Single xs) = f xs
        foldMapTreeN f (Deep _ pr m sf) = 
            foldMapDigitN f pr <>
            foldMapTreeN (foldMapNodeN f) m <>
            foldMapDigitN f sf

        foldMapDigitE :: Monoid m => (Elem a -> m) -> Digit (Elem a) -> m
        foldMapDigitE f t = foldDigit (<>) f t

        foldMapDigitN :: Monoid m => (Node a -> m) -> Digit (Node a) -> m
        foldMapDigitN f t = foldDigit (<>) f t

        foldMapNodeE :: Monoid m => (Elem a -> m) -> Node (Elem a) -> m
        foldMapNodeE f t = foldNode (<>) f t

        foldMapNodeN :: Monoid m => (Node a -> m) -> Node (Node a) -> m
        foldMapNodeN f t = foldNode (<>) f t
#if __GLASGOW_HASKELL__
    {-# INLINABLE foldMap #-}
#endif

The speeds are more what you'd expect:

benchmarking 50000/getSum . foldMap Sum
time                 317.9 μs   (316.3 μs .. 319.2 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 318.2 μs   (316.8 μs .. 320.2 μs)
std dev              5.982 μs   (4.938 μs .. 7.338 μs)
variance introduced by outliers: 11% (moderately inflated)
             
benchmarking 50000/getSum . foldMapWithIndex (const Sum)
time                 431.7 μs   (428.6 μs .. 434.0 μs)
                     0.999 R²   (0.998 R² .. 1.000 R²)
mean                 443.1 μs   (436.4 μs .. 451.8 μs)
std dev              23.79 μs   (18.88 μs .. 33.82 μs)
variance introduced by outliers: 48% (moderately inflated)
             
benchmarking 50000/foldl' (+) 0
time                 409.0 μs   (405.5 μs .. 414.5 μs)
                     0.998 R²   (0.996 R² .. 1.000 R²)
mean                 418.6 μs   (415.3 μs .. 426.1 μs)
std dev              19.48 μs   (15.71 μs .. 27.21 μs)
variance introduced by outliers: 42% (moderately inflated)

Although I'm not entirely sure why.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions