[GHC] #9345: Data.List.inits is extremely slow

GHC ghc-devs at haskell.org
Mon Sep 1 19:56:26 UTC 2014


#9345: Data.List.inits is extremely slow
-------------------------------------+-------------------------------------
              Reporter:  dfeuer      |            Owner:
                  Type:  bug         |           Status:  new
              Priority:  high        |        Milestone:  7.8.4
             Component:              |          Version:  7.8.3
  libraries/base                     |         Keywords:
            Resolution:              |     Architecture:  Unknown/Multiple
      Operating System:              |       Difficulty:  Easy (less than 1
  Unknown/Multiple                   |  hour)
       Type of failure:  Runtime     |       Blocked By:
  performance bug                    |  Related Tickets:
             Test Case:              |
              Blocking:              |
Differential Revisions:              |
-------------------------------------+-------------------------------------

Comment (by nomeata):

 Thanks. I changed it to `foo = sum $ concat $ initsQ' $ [1..10000::Int]`
 (so that I don’t get any print-related stuff), and `-ddump-call-arity`
 gives me this code:

 {{{
 #!hs
 [LclId,
  Arity=2,
  CallArity=2,
  Str=DmdType,
  Unf=Unf{Src=<vanilla>, TopLvl=True, Arity=2, Value=True,
          ConLike=True, WorkFree=True, Expandable=True,
          Guidance=IF_ARGS [0 0] 150 0}]
 c =
   \ (x :: [Int]) (y [OS=OneShot] :: Int -> Int) ->
     letrec {
       go [Occ=LoopBreaker] :: [Int] -> Int -> Int
       [LclId,
        Arity=1,
        CallArity=1,
        Str=DmdType,
        Unf=Unf{Src=<vanilla>, TopLvl=False, Arity=1, Value=True,
                ConLike=True, WorkFree=True, Expandable=True,
                Guidance=IF_ARGS [30] 110 60}]
       go =
         \ (ds :: [Int]) ->
           case ds of _ [Occ=Dead] {
             [] -> y;
             : y ys ->
               let {
                 ds1 [OS=OneShot] :: Int -> Int
                 [LclId,
                  Str=DmdType,
                  Unf=Unf{Src=<vanilla>, TopLvl=False, Arity=0,
 Value=False,
                          ConLike=False, WorkFree=False, Expandable=False,
                          Guidance=IF_ARGS [] 20 0}]
                 ds1 = go ys } in
               \ (ds2 :: Int) -> ds1 ($fNumInt_$c+ ds2 y)
           }; } in
     go x

 foo :: Int
 [LclIdX,
  Str=DmdType,
  Unf=Unf{Src=<vanilla>, TopLvl=True, Arity=0, Value=False,
          ConLike=False, WorkFree=False, Expandable=False,
          Guidance=IF_ARGS [] 634 0}]
 foo =
   c (++ ([]) (reverse1 ([]) ([])))
     (letrec {
        go [Occ=LoopBreaker] :: Int# -> Queue Int -> Int -> Int
        [LclId,
         Arity=2,
         CallArity=2,
         Str=DmdType,
         Unf=Unf{Src=<vanilla>, TopLvl=False, Arity=2, Value=True,
                 ConLike=True, WorkFree=True, Expandable=True,
                 Guidance=IF_ARGS [80 20] 464 0}]
        go =
          \ (x :: Int#) (eta :: Queue Int) ->
            let {
              b :: Int
              [LclId,
               Str=DmdType,
               Unf=Unf{Src=<vanilla>, TopLvl=False, Arity=0, Value=True,
                       ConLike=True, WorkFree=True, Expandable=True,
                       Guidance=IF_ARGS [] 10 20}]
              b = I# x } in
            case eta of _ [Occ=Dead] { Queue dt f r ->
            let {
              a :: Word#
              [LclId,
               Str=DmdType,
               Unf=Unf{Src=<vanilla>, TopLvl=False, Arity=0, Value=False,
                       ConLike=False, WorkFree=False, Expandable=True,
                       Guidance=IF_ARGS [] 1 0}]
              a = plusWord# dt (__word 1) } in
            let {
              r :: [Int]
              [LclId,
               Str=DmdType,
               Unf=Unf{Src=<vanilla>, TopLvl=False, Arity=0, Value=True,
                       ConLike=True, WorkFree=True, Expandable=True,
                       Guidance=IF_ARGS [] 10 30}]
              r = : b r } in
            case word2Int# (popCnt# a) of _ [Occ=Dead] {
              __DEFAULT ->
                c (++ f (reverse1 r ([])))
                  (case x of wild {
                     __DEFAULT -> go (+# wild 1) (Queue a f r);
                     10000 -> \ (eta :: Int) -> eta
                   });
              1 ->
                let {
                  ipv :: [Int]
                  [LclId,
                   Str=DmdType,
                   Unf=Unf{Src=<vanilla>, TopLvl=False, Arity=0,
 Value=False,
                           ConLike=False, WorkFree=False, Expandable=False,
                           Guidance=IF_ARGS [] 60 0}]
                  ipv = ++ f (reverse1 r ([])) } in
                c (++ ipv (reverse1 ([]) ([])))
                  (case x of wild {
                     __DEFAULT -> go (+# wild 1) (Queue a ipv ([]));
                     10000 -> \ (eta :: Int) -> eta
                   })
            }
            }; } in
      go 1 a)
     (I# 0)
 }}}

 The `go` inside `foo` is the interesting function. Its type has three
 arguments, but its outermost lambda only takes two. This is common when
 fusing a left-fold. Only that Call Arity is not sufficient to see that
 we’d want this to be expanded to thre arguments (`CallArity=3`).

 Would `CallArity=3` be correct? Yes, because the recursive calls to `go`
 call it with two arguments, pass that to `c`, which calls it at most once
 (so no sharing could be lost) with at least one argument.

 Unfortunately, that information is currently not available to the call
 arity analysis (§5.1.1 in the paper linked above). Call Arity is a forward
 analysis, so it is hard to see how it could make use of that. And even if
 it did, it would still have to pass a function (in that case a PAP) to
 `c`... :-(

--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/9345#comment:24>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler


More information about the ghc-tickets mailing list