<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="/stylesheets/rss.css"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
  <channel>
    <title>The Rubylution: Tag benchmarking</title>
    <link>http://rubylution.ping.de/articles/tag/benchmarking?tag=benchmarking</link>
    <language>en-us</language>
    <ttl>40</ttl>
    <description>Starts&amp;hellip; Now.</description>
    <item>
      <title>Benchmarking: Sorting Arrays in Descending Order</title>
      <description>&lt;p&gt;Recently i wanted to sort an array of array pairs (array.size == 2)
in descending (highest value first) order in Ruby. I wanted to avoid creating a temporary array for this, so at first I used Array#sort with a block.&lt;/p&gt;

&lt;p&gt;Because the sorting would often be called in my algorithm, I wanted to optimize this part of the code a bit.  I started to benchmark in order to verify, that my assumptions are correct.&lt;/p&gt;

&lt;p&gt;This is the setup data for the benchmarks:&lt;/p&gt;

&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;&lt;span class="ident"&gt;n&lt;/span&gt;   &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="number"&gt;1000&lt;/span&gt;
&lt;span class="ident"&gt;a&lt;/span&gt;   &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;Array&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;n&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt; &lt;span class="punct"&gt;[&lt;/span&gt; &lt;span class="ident"&gt;rand&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="number"&gt;1000&lt;/span&gt;&lt;span class="punct"&gt;),&lt;/span&gt; &lt;span class="ident"&gt;rand&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="number"&gt;1000&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="punct"&gt;]&lt;/span&gt; &lt;span class="punct"&gt;}&lt;/span&gt;
&lt;span class="ident"&gt;a2&lt;/span&gt;  &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;Array&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;new&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;n&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt; &lt;span class="ident"&gt;rand&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="number"&gt;1000&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="punct"&gt;}&lt;/span&gt;
&lt;span class="ident"&gt;m&lt;/span&gt;   &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="number"&gt;1000&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;array sizes are &lt;em&gt;n&lt;/em&gt;, &lt;em&gt;a&lt;/em&gt; is my array of pairs, &lt;em&gt;a&lt;/em&gt;2 is for comparison only, and I repeat the benchmarked code &lt;em&gt;m&lt;/em&gt;-times. (I also do a full warmup run before that.)&lt;/p&gt;

&lt;p&gt;This was my first stab at the sorting (I don't care about the first value in the pair, BTW):&lt;/p&gt;

&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;&lt;span class="ident"&gt;a&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;sort&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;x&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;y&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt; &lt;span class="ident"&gt;y&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="ident"&gt;x&lt;/span&gt; &lt;span class="punct"&gt;}&lt;/span&gt; &lt;span class="comment"&gt;# =&amp;gt; &amp;quot; 11.62s 0.011618c/s&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This took quite some time. (The first time value is the duration in seconds, the second the calls per second.) I guessed that calling the block ca. &lt;em&gt;n log(n)&lt;/em&gt; times, was the reason for this, so I tried:&lt;/p&gt;

&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;&lt;span class="ident"&gt;b&lt;/span&gt; &lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="ident"&gt;a&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;sort&lt;/span&gt;&lt;span class="punct"&gt;;&lt;/span&gt; &lt;span class="ident"&gt;b&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;reverse!&lt;/span&gt; &lt;span class="comment"&gt;# =&amp;gt; &amp;quot;  3.29s 0.003290c/s&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Wow! Much better.  This takes ca. &lt;em&gt;k n log(n) + l n&lt;/em&gt; "machine instructions", while &lt;em&gt;l &amp;lt;&amp;lt; k&lt;/em&gt;, so the reverting doesn't matter neither in theory or praxis. This can be shown, by just benchmarking Array#sort:&lt;/p&gt;

&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;&lt;span class="ident"&gt;a&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;sort&lt;/span&gt; &lt;span class="comment"&gt;# =&amp;gt; &amp;quot;  3.48s 0.003484c/s&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This has essentially the same speed as the benchmark with reverse. Actually it's slower, which means, that I cannot even measure Array#reverse's speed in this benchmark. &lt;/p&gt;

&lt;p&gt;So let's test a2 to find out, how fast comparing Fixnums is compared to the more complex pair Arrays:&lt;/p&gt;

&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;&lt;span class="ident"&gt;a2&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;sort&lt;/span&gt; &lt;span class="comment"&gt;# =&amp;gt; &amp;quot;  0.27s 0.000265c/s&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's quite a speedup, so let's try to profit from that:&lt;/p&gt;

&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_Ruby "&gt;a.sort { |x, y| y.last &amp;lt;=&amp;gt; x.last } # =&amp;gt; &amp;quot; 13.97s 0.013968c/s&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hmm, this didn't work out. But why? I remembered that Ruby's sort cheats a bit for immediate values like Fixnum, that's most likely the reason. (But I am to lazy to look it up just now.) Making the sort block more complex than in the first try didn't help either.&lt;/p&gt;

&lt;p&gt;But what can be gained from these examples is that&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;using a complex compare block, leads to a huge slowdown in sorting time and&lt;/li&gt;
&lt;li&gt;comparing more primitive values can be much faster (especially if all of this happens in C).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This convinced me to try Array#sort_by, which I dismissed from the beginning as being too wasteful of memory (and maybe too slow because of this anyway):&lt;/p&gt;

&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_Ruby "&gt;a.sort_by { |x| -x.last } # =&amp;gt; &amp;quot;  2.77s 0.002766c/s&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;LOL!&lt;/strong&gt; I really should stop making predictions without benchmarking: I almost always guess wrong, if I do it. This is the fastest way to sort &lt;em&gt;a&lt;/em&gt; in descending OR ascending order. Because it combines Ruby's cheating with Fixnums (-x.last) and only calling the { |x| -x.last }-block &lt;em&gt;n&lt;/em&gt;-times instead of calling the more complex one ca. &lt;em&gt;n * log(n)&lt;/em&gt; times.&lt;/p&gt;

&lt;p&gt;On the other hand it's only a good idea to invest much time into figuring out and exploiting implementation details like this, if you need the speed, need it &lt;strong&gt;now&lt;/strong&gt; and on this Ruby implementation. The used implementation could change in the future or the advantages could turn into drawbacks if you run the algorithm on JRuby for example.&lt;/p&gt;

&lt;p&gt;P.S.: &lt;a href="http://rubylution.ping.de/files/pair_sort_bm.rb"&gt;Benchmark code&lt;/a&gt; can be downloaded.&lt;/p&gt;</description>
      <pubDate>Tue, 29 May 2007 18:13:00 +0200</pubDate>
      <guid isPermaLink="false">urn:uuid:6ff12d31-fe69-4974-88c0-f93c754c6aa9</guid>
      <author>flori@ping.de (flori)</author>
      <link>http://rubylution.ping.de/articles/2007/05/29/benchmarking-sorting-arrays-in-descending-order</link>
      <category>ruby</category>
      <category>optimization</category>
      <category>benchmarking</category>
      <enclosure type="application/x-ruby" length="1189" url="http://rubylution.ping.de/files/pair_sort_bm.rb"/>
    </item>
  </channel>
</rss>
