<div>On Wed, Dec 23, 2009 at 4:31 PM, Neil Mitchell <span dir="ltr">&lt;<a href="mailto:ndmitchell@gmail.com">ndmitchell@gmail.com</a>&gt;</span> wrote:<br><blockquote class="gmail_quote" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0.8ex; border-left-width: 1px; border-left-color: rgb(204, 204, 204); border-left-style: solid; padding-left: 1ex; ">
Hi Tyson,<br><br>Whenever I see the word inefficiency, I look for the benchmark that<br>proves it :-)<br></blockquote></div><div><br></div>Hey Neil,<div><br></div><div>I&#39;ve spent quite a bit of time poring over the code in question trying to eke out performance when I was looking at ways to get it to perform nicely for unboxed data structures, so I&#39;ll try to serve as an independent set of eyes. </div>
<div><br></div><div><blockquote class="gmail_quote" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0.8ex; border-left-width: 1px; border-left-color: rgb(204, 204, 204); border-left-style: solid; padding-left: 1ex; ">
Have you benchmarked these two alternatives? If you can show that</blockquote><blockquote class="gmail_quote" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0.8ex; border-left-width: 1px; border-left-color: rgb(204, 204, 204); border-left-style: solid; padding-left: 1ex; ">
there is a measurable difference I&#39;m sure someone will sit up and take</blockquote><blockquote class="gmail_quote" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0.8ex; border-left-width: 1px; border-left-color: rgb(204, 204, 204); border-left-style: solid; padding-left: 1ex; ">
note. Personally, I know nothing about the code involved, so you&#39;d</blockquote><blockquote class="gmail_quote" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0.8ex; border-left-width: 1px; border-left-color: rgb(204, 204, 204); border-left-style: solid; padding-left: 1ex; ">
need to find someone who did to check your optimisation is valid </blockquote><div><br></div><div>I&#39;ll vouch for the validity of the code change. Basically his code avoids trying to do the top level rotation when gluing 2 trees that are already within 1 level of each other together -- the invariant to be able to call &#39;glue&#39;. Its a good point, and it checks out as correct that the call to balance is superfluous. I even quickchecked about a hundred thousand trees to make sure that the behavior doesn&#39;t change, but you can get there by programmatic transformation to satisfy for more rigorous among us:</div>
<div><div><br></div></div></div><div>A &#39;sufficiently smart compiler&#39; could spot that if it inlines balance in both locations, then CSE&#39;s the comparison between sizes, that the case for the comparison between sizes has already been done, then it could simplify the balance cases to just construct the Bin without rebalancing, and another CSE would then do the rest of the transformation, but it seems like a needlessly complex path, and while I haven&#39;t gone through the resulting STG output to see if it is actually occurring with optimizations enabled on the containers library, I&#39;m somewhat dubious, given the number of phases that would have to happen in just the right order for this to be the case. </div>
<div><br></div><div>More interestingly, if you start playing around with variants on these modules, like the one I was working on that unboxed the key/values using some fancy view machinery, the compiler would have to work ridiculously hard to spot the correct invariants across the inlining of both the call and the particular view, so I&#39;ve already applied this patch to unboxed-containers in my working repository.</div>
<div><br></div><div>I think that relying on the compiler to get that right seems a lot less straightforward than just fixing the algorithm to do the right amount of work. Considering that it is true to the style of the rest of the code in those modules to be explicit about what work is actually necessary and where by calling special case rebalancing combinators only when appropriate.</div>
<div><br></div><div><blockquote class="gmail_quote" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0.8ex; border-left-width: 1px; border-left-color: rgb(204, 204, 204); border-left-style: solid; padding-left: 1ex; ">
(if it makes things go faster.</blockquote><div><br></div></div><div>As for benchmarks, frankly it&#39;ll vanish in the noise, the worst case is a few of extra stack frames getting constructed, a redundant addition, and some unnecessary comparisons in balance once per operation, and not at each level of the tree. I do support patching it just for correctness though. </div>
<div><br></div><div>Glue gets called internally across a very wide array of operations, and in some sense the Data.Map/Data.Set code has become the de facto reference implementation for how to implement trees of bounded balance, so it&#39;d be nice to have them be correct. </div>
<div><br></div><div>-Edward Kmett</div><div><br></div><div><div><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;"><br><br>
Thanks, Neil<br>
<div><div></div><div class="h5"><br>
On Mon, Dec 21, 2009 at 5:38 AM, Tyson Whitehead &lt;<a href="mailto:twhitehead@gmail.com">twhitehead@gmail.com</a>&gt; wrote:<br>
&gt; I was looking at the Data.Map and Data.Set implementations and I think there<br>
&gt; is a slight inefficiency.  Consider the Data.Map code<br>
&gt;<br>
&gt; {--------------------------------------------------------------------<br>
&gt;  [glue l r]: glues two trees together.<br>
&gt;  Assumes that [l] and [r] are already balanced with respect to each other.<br>
&gt; --------------------------------------------------------------------}<br>
&gt; glue :: Map k a -&gt; Map k a -&gt; Map k a<br>
&gt; glue Tip r = r<br>
&gt; glue l Tip = l<br>
&gt; glue l r<br>
&gt;  | size l &gt; size r = let ((km,m),l&#39;) = deleteFindMax l in balance km m l&#39; r<br>
&gt;  | otherwise       = let ((km,m),r&#39;) = deleteFindMin r in balance km m l r&#39;<br>
&gt;<br>
&gt; The call to balance should not be needed as subtracting one item from the non-<br>
&gt; smaller of them cannot thrown them out of balance given they are already in<br>
&gt; balance with one another.  The only way they could be made out of balance<br>
&gt; would be to subtract an item for smaller one.  The replacement code is<br>
&gt;<br>
&gt; glue l r<br>
&gt;  | sizeL &gt; sizeR = let ((km,m),l&#39;) = deleteFindMax l in Bin sizeX km m l&#39; r<br>
&gt;  | otherwise     = let ((km,m),r&#39;) = deleteFindMin r in Bin sizeX km m l r&#39;<br>
&gt;  where<br>
&gt;    sizeL = size l<br>
&gt;    sizeR = size r<br>
&gt;    sizeX = sizeL + sizeR<br>
&gt;<br>
&gt; The Data.Set code is identical except for the key.<br>
&gt;<br>
&gt; Cheers!  -Tyson<br>
&gt; _______________________________________________<br>
&gt; Libraries mailing list<br>
&gt; <a href="mailto:Libraries@haskell.org">Libraries@haskell.org</a><br>
&gt; <a href="http://www.haskell.org/mailman/listinfo/libraries" target="_blank">http://www.haskell.org/mailman/listinfo/libraries</a><br>
&gt;<br>
_______________________________________________<br>
Libraries mailing list<br>
<a href="mailto:Libraries@haskell.org">Libraries@haskell.org</a><br>
<a href="http://www.haskell.org/mailman/listinfo/libraries" target="_blank">http://www.haskell.org/mailman/listinfo/libraries</a><br>
</div></div></blockquote></div><br></div></div>