<div dir="ltr">Thanks Dan, that helped. I did notice and suspect the update frame and the unboxed tuple but given my limited knowledge about ghc/core/stg/cmm I was not sure what is going on. In fact I thought that the intermediate tuple should get optimized out since it is required only because of the realworld token which is not real. But it might be difficult to see that at this level?<div><br></div><div>What you are saying may be true for the current implementation but in theory can we eliminate the intermediate closure?</div><div><br></div><div>I observed that the same unboxed tuple results in an inline code for (:) constructor. I rewrote the source a bit differently which resulted in this core:</div><div><br></div><div><div>              case ipv1_amWI of _ [Occ=Dead] {</div><div>                False -></div><div>                  case a1_smWm xs_aloV ipv_amWH</div><div>                  of _ [Occ=Dead] { (# ipv2_XmXf, ipv3_XmXh #) -></div><div>                  (# ipv2_XmXf, GHC.Types.: @ Char x_aloU ipv3_XmXh #)</div><div>                  };</div><div>                True -></div><div>                  case a1_smWm (NFD.decomposeChar x_aloU) ipv_amWH</div><div>                  of _ [Occ=Dead] { (# ipv2_XmXf, ipv3_XmXh #) -></div><div>                  case a1_smWm xs_aloV ipv2_XmXf</div><div>                  of _ [Occ=Dead] { (# ipv4_XmXk, ipv5_XmXm #) -></div><div>                  (# ipv4_XmXk, ++ @ Char ipv3_XmXh ipv5_XmXm #)</div><div>                  }</div><div>                  }</div><div>              }</div></div><div><br></div><div>We can see that both True and the False case are returning an unboxed tuple. The only difference is (:) vs (++). But the generated assembly is different for both cases:</div><div><br></div><div>(++) has a closure as before:</div><div><br></div><div><div>sat_sn0Z_info:</div><div>_cn2c:</div><div><span class="" style="white-space:pre">  </span>leaq -16(%rbp),%rax</div><div><span class="" style="white-space:pre">        </span>cmpq %r15,%rax</div><div><span class="" style="white-space:pre">     </span>jb _cn2d</div><div>_cn2e:</div><div><span class="" style="white-space:pre">      </span>movq $stg_upd_frame_info,-16(%rbp)</div><div><span class="" style="white-space:pre"> </span>movq %rbx,-8(%rbp)</div><div><span class="" style="white-space:pre"> </span>movq 24(%rbx),%rsi</div><div><span class="" style="white-space:pre"> </span>movq 16(%rbx),%r14</div><div><span class="" style="white-space:pre"> </span>addq $-16,%rbp</div><div><span class="" style="white-space:pre">     </span>jmp GHC.Base.++_info</div></div><div><br></div><div>(:) is treated differently and generated inline as you can see below:<br></div><div><br></div><div><div>_cn2I:</div><div><span class="" style="white-space:pre">   </span>movq $sat_sn0Z_info,-24(%r12)</div><div><span class="" style="white-space:pre">      </span>movq 8(%rbp),%rax</div><div><span class="" style="white-space:pre">  </span>movq %rax,-8(%r12)</div><div><span class="" style="white-space:pre"> </span>movq %rbx,(%r12)</div><div><span class="" style="white-space:pre">   </span>leaq -24(%r12),%rbx</div><div><span class="" style="white-space:pre">        </span>addq $16,%rbp</div><div><span class="" style="white-space:pre">      </span>jmp *(%rbp)</div></div><div>.</div><div>.</div><div>.</div><div><div>block_cn2u_info:</div><div>_cn2u:</div><div><span class="" style="white-space:pre">     </span>addq $24,%r12</div><div><span class="" style="white-space:pre">      </span>cmpq 856(%r13),%r12</div><div><span class="" style="white-space:pre">        </span>ja _cn2C</div><div>_cn2B:</div><div><span class="" style="white-space:pre">      </span>movq $:_con_info,-16(%r12)</div><div><span class="" style="white-space:pre"> </span>movq 8(%rbp),%rax</div><div><span class="" style="white-space:pre">  </span>movq %rax,-8(%r12)</div><div><span class="" style="white-space:pre"> </span>movq %rbx,(%r12)</div><div><span class="" style="white-space:pre">   </span>leaq -14(%r12),%rbx</div><div><span class="" style="white-space:pre">        </span>addq $24,%rbp</div><div><span class="" style="white-space:pre">      </span>jmp *(%rbp)</div></div><div><br></div><div>So why is that? Why can't we generate (++) inline similar to (:)? How do we make this decision? Is there a theoretical reason for this or this is just an implementation artifact?</div><div><br></div><div>Since the tuple is required only for passing the realworld token, ideally I wouldn't want it to make a difference to the generated code. Otherwise I would always have to worry about such performance side effects when using ST/IO.</div><div><br></div><div>-harendra</div><div><br></div></div><div class="gmail_extra"><br><div class="gmail_quote">On 10 May 2016 at 06:51, Dan Doel <span dir="ltr"><<a href="mailto:dan.doel@gmail.com" target="_blank">dan.doel@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">I'm no expert on reading GHC's generated assembly. However, there may<br>
be a line you've overlooked in explaining the difference, namely:<br>
<br>
    movq $stg_upd_frame_info,-16(%rbp)<br>
<br>
This appears only in the IO code, according to what you've pasted, and<br>
it appears to be pushing an update frame (I think). Update frames are<br>
used as part of lazy evaluation, to ensure that work only happens<br>
once, barring very short race conditions. So, at a guess, I would say<br>
that your IO-based code is creating extra updatable closures, which<br>
isn't free.<br>
<br>
It's sometimes difficult to see the difference at the core level. It<br>
will probably be clearer at the STG level, because the expression<br>
language is more disciplined there. But, for instance, your pure code<br>
tail calls (++), whereas your IO code returns an unboxed tuple with<br>
the same sort of expression that is in the pure tail call. However,<br>
complex expressions like that can't really be put in an unboxed tuple<br>
at the STG level, so what will happen is that the complex expression<br>
will be let (closure allocation), and the closure will be returned in<br>
the unboxed tuple. So that is the source of the difference. A more<br>
perspicuous picture would be something like:<br>
<br>
  Pure:<br>
    False -><br>
      let {<br>
        l1 = : ww_amuh []<br>
        l2 = Data.Unicode.Internal.Normalization.decompose_$sdecompose<br>
ipv_smuv ipv1_smuD<br>
      } in ++ l1 l2<br>
<br>
<br>
  IO:<br>
<span class="">    False -><br>
      case $sa1_sn0g ipv_smUT ipv1_smV6 ipv2_imWU<br>
</span>      of _ { (# ipv4_XmXv, ipv5_XmXx #) -><br>
      let {<br>
        l1 = : sc_sn0b []<br>
        l3 = ++ l1 ipv5_XmXx<br>
      } in (# ipv4_XmXv, l3 #)<br>
<br>
I can't say for certain that that's the only thing making a<br>
difference, but it might be one thing.<br>
<br>
-- Dan<br>
<div><div class="h5"><br>
<br>
On Mon, May 9, 2016 at 10:23 AM, Harendra Kumar<br>
<<a href="mailto:harendra.kumar@gmail.com">harendra.kumar@gmail.com</a>> wrote:<br>
> I have a loop which runs millions of times. For some reason I have to run it<br>
> in the IO monad. I noticed that when I convert the code from pure to IO<br>
> monad the generated assembly code in essence is almost identical except one<br>
> difference where it puts a piece of code in a separate block which is making<br>
> a huge difference in performance (4-6x slower).<br>
><br>
> I want to understand what makes GHC to generate code in this way and if<br>
> there is anything that can be done at source level (or ghc option)  to<br>
> control that.<br>
><br>
> The pure code looks like this:<br>
><br>
>         decomposeChars :: [Char] -> [Char]<br>
><br>
>         decomposeChars [] = []<br>
>         decomposeChars [x] =<br>
>             case NFD.isDecomposable x of<br>
>                 True -> decomposeChars (NFD.decomposeChar x)<br>
>                 False -> [x]<br>
>         decomposeChars (x : xs) = decomposeChars [x] ++ decomposeChars xs<br>
><br>
> The equivalent IO code is this:<br>
><br>
>         decomposeStrIO :: [Char] -> IO [Char]<br>
><br>
>         decomposeStrPtr !p = decomposeStrIO<br>
>             where<br>
>                 decomposeStrIO [] = return []<br>
>                 decomposeStrIO [x] = do<br>
>                     res <- NFD.isDecomposable p x<br>
>                     case res of<br>
>                         True -> decomposeStrIO (NFD.decomposeChar x)<br>
>                         False -> return [x]<br>
>                 decomposeStrIO (x : xs) = do<br>
>                     s1 <- decomposeStrIO [x]<br>
>                     s2 <- decomposeStrIO xs<br>
>                     return (s1 ++ s2)<br>
><br>
> The difference is in how the code corresponding to the call to the (++)<br>
> operation is generated. In the pure case the (++) operation is inline in the<br>
> main loop:<br>
><br>
> _cn5N:<br>
> movq $sat_sn2P_info,-48(%r12)<br>
> movq %rax,-32(%r12)<br>
> movq %rcx,-24(%r12)<br>
> movq $:_con_info,-16(%r12)<br>
> movq 16(%rbp),%rax<br>
> movq %rax,-8(%r12)<br>
> movq $GHC.Types.[]_closure+1,(%r12)<br>
> leaq -48(%r12),%rsi<br>
> leaq -14(%r12),%r14<br>
> addq $40,%rbp<br>
> jmp GHC.Base.++_info<br>
><br>
> In the IO monad version this code is placed in a separate block and a call<br>
> is placed in the main loop:<br>
><br>
> the main loop call site:<br>
><br>
> _cn6A:<br>
> movq $sat_sn3w_info,-24(%r12)<br>
> movq 8(%rbp),%rax<br>
> movq %rax,-8(%r12)<br>
> movq %rbx,(%r12)<br>
> leaq -24(%r12),%rbx<br>
> addq $40,%rbp<br>
> jmp *(%rbp)<br>
><br>
> out of the line block - the code that was in the main loop in the previous<br>
> case is now moved to this block (see label _cn5s below):<br>
><br>
> sat_sn3w_info:<br>
> _cn5p:<br>
> leaq -16(%rbp),%rax<br>
> cmpq %r15,%rax<br>
> jb _cn5q<br>
> _cn5r:<br>
> addq $24,%r12<br>
> cmpq 856(%r13),%r12<br>
> ja _cn5t<br>
> _cn5s:<br>
> movq $stg_upd_frame_info,-16(%rbp)<br>
> movq %rbx,-8(%rbp)<br>
> movq 16(%rbx),%rax<br>
> movq 24(%rbx),%rbx<br>
> movq $:_con_info,-16(%r12)<br>
> movq %rax,-8(%r12)<br>
> movq $GHC.Types.[]_closure+1,(%r12)<br>
> movq %rbx,%rsi<br>
> leaq -14(%r12),%r14<br>
> addq $-16,%rbp<br>
> jmp GHC.Base.++_info<br>
> _cn5t:<br>
> movq $24,904(%r13)<br>
> _cn5q:<br>
> jmp *-16(%r13)<br>
><br>
> Except this difference the rest of the assembly looks pretty similar in both<br>
> the cases. The corresponding dump-simpl output for the pure case:<br>
><br>
>           False -><br>
>             ++<br>
>               @ Char<br>
>               (GHC.Types.: @ Char ww_amuh (GHC.Types.[] @ Char))<br>
>               (Data.Unicode.Internal.Normalization.decompose_$sdecompose<br>
>                  ipv_smuv ipv1_smuD);<br>
><br>
> And for the IO monad version:<br>
><br>
>                 False -><br>
>                   case $sa1_sn0g ipv_smUT ipv1_smV6 ipv2_imWU<br>
>                   of _ [Occ=Dead] { (# ipv4_XmXv, ipv5_XmXx #) -><br>
>                   (# ipv4_XmXv,<br>
>                      ++<br>
>                        @ Char<br>
>                        (GHC.Types.: @ Char sc_sn0b (GHC.Types.[] @ Char))<br>
>                        ipv5_XmXx #)<br>
>                   };<br>
><br>
> The dump-simpl output is essentially the same except the difference due to<br>
> the realworld token in the IO case. Why is the generated code different? I<br>
> will appreciate if someone can throw some light on the reason or can point<br>
> to the relevant ghc source to look at where this happens.<br>
><br>
> I am using ghc-7.10.3 in native code generation mode (no llvm).<br>
><br>
> Thanks,<br>
> Harendra<br>
><br>
</div></div>> _______________________________________________<br>
> Glasgow-haskell-users mailing list<br>
> <a href="mailto:Glasgow-haskell-users@haskell.org">Glasgow-haskell-users@haskell.org</a><br>
> <a href="http://mail.haskell.org/cgi-bin/mailman/listinfo/glasgow-haskell-users" rel="noreferrer" target="_blank">http://mail.haskell.org/cgi-bin/mailman/listinfo/glasgow-haskell-users</a><br>
><br>
</blockquote></div><br></div>