Boost C++ Libraries: Ticket #2810: Optimization for list::merge performance https://svn.boost.org/trac10/ticket/2810 <p> The current implementation of the list::merge method is quite inefficient since it always iterates through the *this sequence, even if there are no more elements to insert. It also inserts elements from the other sequence one by one, which may generate a lot of overhead for node pointers adjustments in case if there are equivalent nodes in either of the sequences. </p> <p> I suggest the following optimized version of the merge method (see the my_merge function): </p> <pre class="wiki">#include &lt;cstdlib&gt; #include &lt;iostream&gt; #include &lt;boost/checked_delete.hpp&gt; #include &lt;boost/date_time/microsec_time_clock.hpp&gt; #include &lt;boost/date_time/posix_time/posix_time_types.hpp&gt; #include &lt;boost/intrusive/options.hpp&gt; #include &lt;boost/intrusive/list.hpp&gt; #include &lt;boost/intrusive/list_hook.hpp&gt; namespace bi = boost::intrusive; using boost::posix_time::ptime; typedef bi::list_base_hook&lt; bi::link_mode&lt; bi::auto_unlink &gt; &gt; BaseHook_t; struct MyData : public BaseHook_t { struct OrderByKey { typedef bool result_type; result_type operator() (MyData const&amp; left, MyData const&amp; right) const { return left.m_Key &lt; right.m_Key; } }; struct Cloner { typedef MyData* result_type; result_type operator() (MyData const&amp; that) const { return new MyData(that); } }; int m_Key; explicit MyData(int k) : m_Key(k) {} }; typedef bi::list&lt; MyData, bi::base_hook&lt; BaseHook_t &gt;, bi::constant_time_size&lt; false &gt; &gt; List_t; template&lt; typename PredicateT &gt; void my_merge(List_t&amp; this_, List_t&amp; x, PredicateT p) { typedef List_t::const_iterator const_iterator; typedef List_t::size_type size_type; const_iterator b = this_.cbegin(), e = this_.cend(); const_iterator ex = x.cend(); while (!x.empty()) { const_iterator bx = x.cbegin(); while (b != e &amp;&amp; p(*bx, *b)) { ++b; } if (b != e) { // determine the end of the range of x to inject at b size_type n = 0; do { ++bx; ++n; } while (bx != ex &amp;&amp; p(*bx, *b)); this_.splice(b, x, x.cbegin(), bx, n); } else { // the rest of the list is to be appended to the end this_.splice(e, x); break; } } } int main(int, char*[]) { enum { LIST1_SIZE = 1000000, LIST2_SIZE = LIST1_SIZE }; List_t l11, l12; for (unsigned int i = 0; i &lt; LIST1_SIZE; ++i) { MyData* p = new MyData(std::rand()); l11.push_back(*p); } l11.sort(MyData::OrderByKey()); for (unsigned int i = 0; i &lt; LIST2_SIZE; ++i) { MyData* p = new MyData(std::rand()); l12.push_back(*p); } l12.sort(MyData::OrderByKey()); List_t l21, l22; l21.clone_from(l11, MyData::Cloner(), boost::checked_deleter&lt; MyData &gt;()); l22.clone_from(l12, MyData::Cloner(), boost::checked_deleter&lt; MyData &gt;()); ptime start1, end1, start2, end2; start1 = boost::date_time::microsec_clock&lt; ptime &gt;::universal_time(); l11.merge(l12, MyData::OrderByKey()); end1 = boost::date_time::microsec_clock&lt; ptime &gt;::universal_time(); start2 = boost::date_time::microsec_clock&lt; ptime &gt;::universal_time(); my_merge(l21, l22, MyData::OrderByKey()); end2 = boost::date_time::microsec_clock&lt; ptime &gt;::universal_time(); unsigned int duration1 = end1.time_of_day().total_microseconds() - start1.time_of_day().total_microseconds(); unsigned int duration2 = end2.time_of_day().total_microseconds() - start2.time_of_day().total_microseconds(); std::cout &lt;&lt; "Boost.Intrusive version: " &lt;&lt; duration1 &lt;&lt; " us, optimized: " &lt;&lt; duration2 &lt;&lt; " us" &lt;&lt; std::endl; l11.clear_and_dispose(boost::checked_deleter&lt; MyData &gt;()); l12.clear_and_dispose(boost::checked_deleter&lt; MyData &gt;()); l21.clear_and_dispose(boost::checked_deleter&lt; MyData &gt;()); l22.clear_and_dispose(boost::checked_deleter&lt; MyData &gt;()); return 0; } </pre><p> This test case shows that my_merge is faster than list::merge by an order of magnitude and even more if LIST1_SIZE != LIST2_SIZE. </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/2810 Trac 1.4.3 Andrey Semashev Fri, 27 Feb 2009 20:20:38 GMT attachment set https://svn.boost.org/trac10/ticket/2810 https://svn.boost.org/trac10/ticket/2810 <ul> <li><strong>attachment</strong> → <span class="trac-field-new">list.hpp.patch</span> </li> </ul> <p> The patch that applies the optimization to list::merge (against branches/release) </p> Ticket Ion Gaztañaga Sat, 28 Feb 2009 17:49:34 GMT <link>https://svn.boost.org/trac10/ticket/2810#comment:1 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/2810#comment:1</guid> <description> <p> I think your proposal has a bug in the first loop (it shouldn't be "!p(*bx, b)"?). I've tested the following: </p> <blockquote> <p> template&lt;class Predicate&gt; void merge(list_impl&amp; x, Predicate p) { </p> <blockquote> <p> const_iterator e(this-&gt;cend()), ex(x.cend()); const_iterator b(this-&gt;cbegin()); while(!x.empty()){ </p> <blockquote> <p> const_iterator ix(x.cbegin()); while (b != e &amp;&amp; !p(*ix, *b)){ </p> <blockquote> <p> ++b; </p> </blockquote> <p> } if(b == e){ </p> <blockquote> <p> <em>Now transfer the rest to the end of the container this-&gt;splice(e, x); break; </em></p> </blockquote> <p> } else{ </p> <blockquote> <p> size_type n(0); do{ </p> <blockquote> <p> ++ix; ++n; </p> </blockquote> <p> } while(ix != ex &amp;&amp; p(*ix, *b)); this-&gt;splice(b, x, x.begin(), ix, n); </p> </blockquote> <p> } </p> </blockquote> <p> } </p> </blockquote> <p> } </p> </blockquote> <p> And similarly for slist: </p> <blockquote> <p> template&lt;class Predicate&gt; iterator merge(slist_impl&amp; x, Predicate p) { </p> <blockquote> <p> const_iterator e(this-&gt;cend()), ex(x.cend()), bb(this-&gt;cbefore_begin()), </p> <blockquote> <p> bb_next, last_inserted(e); </p> </blockquote> <p> while(!x.empty()){ </p> <blockquote> <p> const_iterator ibx_next(x.cbefore_begin()), ibx(ibx_next++); while (++(bb_next = bb) != e &amp;&amp; !p(*ibx_next, *bb_next)){ </p> <blockquote> <p> bb = bb_next; </p> </blockquote> <p> } if(bb_next == e){ </p> <blockquote> <p> <em>Now transfer the rest to the end of the container last_inserted = this-&gt;splice_after(bb, x); break; </em></p> </blockquote> <p> } else{ </p> <blockquote> <p> size_type n(0); do{ </p> <blockquote> <p> ibx = ibx_next; ++n; </p> </blockquote> <p> } while(++(ibx_next = ibx) != ex &amp;&amp; p(*ibx_next, *bb_next)); this-&gt;splice_after(bb, x, x.before_begin(), ibx, n); last_inserted = ibx; </p> </blockquote> <p> } </p> </blockquote> <p> } return last_inserted.unconst(); </p> </blockquote> <p> } </p> </blockquote> <p> Those are not thorougly tested so let me know if you find any error. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Ion Gaztañaga</dc:creator> <pubDate>Sat, 28 Feb 2009 17:53:12 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/2810#comment:2 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/2810#comment:2</guid> <description> <p> Sorry, here's the same with preformatted text: </p> <pre class="wiki"> template&lt;class Predicate&gt; void merge(list_impl&amp; x, Predicate p) { const_iterator e(this-&gt;cend()), ex(x.cend()); const_iterator b(this-&gt;cbegin()); while(!x.empty()){ const_iterator ix(x.cbegin()); while (b != e &amp;&amp; !p(*ix, *b)){ ++b; } if(b == e){ //Now transfer the rest to the end of the container this-&gt;splice(e, x); break; } else{ size_type n(0); do{ ++ix; ++n; } while(ix != ex &amp;&amp; p(*ix, *b)); this-&gt;splice(b, x, x.begin(), ix, n); } } } template&lt;class Predicate&gt; iterator merge(slist_impl&amp; x, Predicate p) { const_iterator e(this-&gt;cend()), ex(x.cend()), bb(this-&gt;cbefore_begin()), bb_next, last_inserted(e); while(!x.empty()){ const_iterator ibx_next(x.cbefore_begin()), ibx(ibx_next++); while (++(bb_next = bb) != e &amp;&amp; !p(*ibx_next, *bb_next)){ bb = bb_next; } if(bb_next == e){ //Now transfer the rest to the end of the container last_inserted = this-&gt;splice_after(bb, x); break; } else{ size_type n(0); do{ ibx = ibx_next; ++n; } while(++(ibx_next = ibx) != ex &amp;&amp; p(*ibx_next, *bb_next)); this-&gt;splice_after(bb, x, x.before_begin(), ibx, n); last_inserted = ibx; } } return last_inserted.unconst(); } </pre> </description> <category>Ticket</category> </item> <item> <dc:creator>Andrey Semashev</dc:creator> <pubDate>Mon, 02 Mar 2009 06:58:49 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/2810#comment:3 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/2810#comment:3</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/2810#comment:1" title="Comment 1">igaztanaga</a>: </p> <blockquote class="citation"> <p> I think your proposal has a bug in the first loop (it shouldn't be "!p(*bx, b)"?). </p> </blockquote> <p> Yeah, looks like you're right. Thanks for spotting this. </p> <p> Your corrected version seems to be working flawlessly in my application. At least, the list::merge method. I hope, it will be released in 1.39. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Ion Gaztañaga</dc:creator> <pubDate>Wed, 25 Mar 2009 17:56:42 GMT</pubDate> <title>status changed; resolution set https://svn.boost.org/trac10/ticket/2810#comment:4 https://svn.boost.org/trac10/ticket/2810#comment:4 <ul> <li><strong>status</strong> <span class="trac-field-old">new</span> → <span class="trac-field-new">closed</span> </li> <li><strong>resolution</strong> → <span class="trac-field-new">fixed</span> </li> </ul> <p> Applied modified patch. Revision 51971. </p> Ticket