IntMap traverseWithKey faster than mapWithKey?

Viewed 106

I was reading this part of Parallel and Concurrent Programming in Haskell, and found the sequential version of the program to be far slower than the parallel one with one core:

Sequential:

$ cabal run fwsparse -O2 --ghc-options="-rtsopts" -- 1000 800 +RTS -s
...
Total   time   11.469s  ( 11.529s elapsed)

Parallel:

$ cabal run fwsparse1 -O2 --ghc-options="-rtsopts -threaded" -- 1000 800 +RTS -s -N1
...
Total   time    4.906s  (  4.988s elapsed)

According to the book, the sequential one should be slightly faster than the parallel one (if it runs on a single core).

The only difference in the two programs was this function:

Sequential:

update g k = Map.mapWithKey shortmap g

Parallel:

update g k = runPar $ do
    m <- Map.traverseWithKey (\i jmap -> spawn (return (shortmap i jmap))) g
    traverse get m

Since the spawn function uses deepseq, I initially though it had something to do with strictness but the use of force didn't change the performance of the sequential program.

Finally, I managed to get the sequential one work faster:

$ cabal run fwsparse -O2 --ghc-options="-rtsopts" -- 1000 800 +RTS -s
...
Total   time    3.891s  (  3.901s elapsed)

by changing the update function to this:

update g k = runIdentity $ Map.traverseWithKey (\i jmap -> pure $ shortmap i jmap) g

Why does using traverseWithKey in the Identity monad speed up performance? I checked the IntMap source code but couldn't figure out the reason.

Is this a bug or the expected behaviour?

(Also, this is my first question on StackOverflow so please tell me if I'm doing anything wrong.)

EDIT:
As per Ismor's comment, I turned off optimization (using -O0) and the sequential program runs in 19.5 seconds with either traverseWithKey or mapWithKey, while the parallel one runs in 21.5 seconds.

  • Why is traverseWithKey optimized to be so much faster than mapWithKey?
  • Which function should I use in practice?
0 Answers
Related