Gray Soft / Tags / Performancetag:graysoftinc.com,2014-03-20:/tags/Performance2014-09-05T19:11:03ZJames Edward Gray IISleepy Programstag:graysoftinc.com,2014-08-22:/posts/1252014-09-05T19:11:03ZCan we do modern message passing multiprocessing in Ruby across processes and thread? Sure can.<p>When we think of real multiprocessing, our thoughts probably drift more towards languages like Erlang, Go, Clojure, or Rust. Such languages really focus on getting separate "processes" to communicate via messages. This makes it a lot easier to know when one process is waiting on another, because calls to receive messages typically block until one is available.</p>
<p>But what about Ruby? Can we do intelligent process coordination in Ruby?</p>
<p>Yes, we can. The tools for it are more awkward though. It's easy to run into tricky edge cases and hard to code your way out of them correctly.</p>
<p>Let's play with an example to see how good we can make things. Here's what we will do:</p>
<ol>
<li>We will start one parent process that will <code>fork()</code> a single child process</li>
<li>The child will push three messages onto a RabbitMQ queue and <code>exit()</code>
</li>
<li>The parent will listen for three messages to arrive, then <code>exit()</code>
</li>
</ol><p>Here's a somewhat sloppy first attempt at solving this:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/bin/env ruby</span>
<span class="nb">require</span> <span class="s2">"benchmark"</span>
<span class="nb">require</span> <span class="s2">"bunny"</span>
<span class="no">QUEUE_NAME</span> <span class="o">=</span> <span class="s2">"example"</span>
<span class="no">MESSAGES</span> <span class="o">=</span> <span class="sx">%w[first second third]</span>
<span class="k">def</span> <span class="nf">send_messages</span><span class="p">(</span><span class="o">*</span><span class="n">messages</span><span class="p">)</span>
<span class="n">connection</span> <span class="o">=</span> <span class="no">Bunny</span><span class="o">.</span><span class="n">new</span><span class="o">.</span><span class="n">tap</span><span class="p">(</span><span class="o">&</span><span class="ss">:start</span><span class="p">)</span>
<span class="n">exchange</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">create_channel</span><span class="o">.</span><span class="n">default_exchange</span>
<span class="n">messages</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">message</span><span class="o">|</span>
<span class="n">exchange</span><span class="o">.</span><span class="n">publish</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">routing_key</span><span class="p">:</span> <span class="no">QUEUE_NAME</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">connection</span><span class="o">.</span><span class="n">close</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">listen_for_messages</span><span class="p">(</span><span class="n">received_messages</span><span class="p">)</span>
<span class="n">connection</span> <span class="o">=</span> <span class="no">Bunny</span><span class="o">.</span><span class="n">new</span><span class="o">.</span><span class="n">tap</span><span class="p">(</span><span class="o">&</span><span class="ss">:start</span><span class="p">)</span>
<span class="n">queue</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">create_channel</span><span class="o">.</span><span class="n">queue</span><span class="p">(</span><span class="no">QUEUE_NAME</span><span class="p">,</span> <span class="n">auto_delete</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span>
<span class="n">queue</span><span class="o">.</span><span class="n">subscribe</span> <span class="k">do</span> <span class="o">|</span><span class="n">delivery_info</span><span class="p">,</span> <span class="n">metadata</span><span class="p">,</span> <span class="n">payload</span><span class="o">|</span>
<span class="n">received_messages</span> <span class="o"><<</span> <span class="n">payload</span>
<span class="k">end</span>
<span class="n">time_it</span><span class="p">(</span><span class="s2">"Received </span><span class="si">#{</span><span class="no">MESSAGES</span><span class="o">.</span><span class="n">size</span><span class="si">}</span><span class="s2"> messages"</span><span class="p">)</span> <span class="k">do</span>
<span class="k">yield</span>
<span class="k">end</span>
<span class="n">connection</span><span class="o">.</span><span class="n">close</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">time_it</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
<span class="n">elapsed</span> <span class="o">=</span> <span class="no">Benchmark</span><span class="o">.</span><span class="n">realtime</span> <span class="k">do</span>
<span class="k">yield</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="s2">"%s: %.2fs"</span> <span class="o">%</span> <span class="o">[</span><span class="nb">name</span><span class="p">,</span> <span class="n">elapsed</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">wait_for_messages</span><span class="p">(</span><span class="n">received_messages</span><span class="p">)</span>
<span class="k">until</span> <span class="n">received_messages</span> <span class="o">==</span> <span class="no">MESSAGES</span>
<span class="nb">sleep</span> <span class="mi">0</span><span class="o">.</span><span class="mi">1</span> <span class="c1"># don't peg the CPU while we wait</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">send_and_receive</span>
<span class="n">pid</span> <span class="o">=</span> <span class="nb">fork</span> <span class="k">do</span>
<span class="nb">sleep</span> <span class="mi">3</span> <span class="c1"># make sure we're receiving before they are sent</span>
<span class="n">send_messages</span><span class="p">(</span><span class="o">*</span><span class="no">MESSAGES</span><span class="p">)</span>
<span class="k">end</span>
<span class="no">Process</span><span class="o">.</span><span class="n">detach</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span>
<span class="n">received_messages</span> <span class="o">=</span> <span class="o">[</span> <span class="o">]</span>
<span class="n">listen_for_messages</span><span class="p">(</span><span class="n">received_messages</span><span class="p">)</span> <span class="k">do</span>
<span class="n">wait_for_messages</span><span class="p">(</span><span class="n">received_messages</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">send_and_receive</span>
</pre></div>
<p>Let's talk about each piece of this code real quick. You can mostly ignore the first two methods, <code>send_messages()</code> and <code>listen_for_messages()</code>. These are just wrappers over RabbitMQ's publish and subscribe process. The only tricky bit is that <code>listen_for_messages()</code> does a <code>yield</code> after subscribing to the queue. The reason for this is that subscribing just spins up a separate <code>Thread</code> which will call the passed block as messages arrive. That's happening in the background, which means the main <code>Thread</code> needs to find some way to wait until we have received the expected messages. The <code>yield</code> gives us a place to insert this waiting code.</p>
<p>The next two methods, <code>time_it()</code> and <code>wait_for_messages()</code>, are simple helpers. I added the first mainly to give us some noticeable output. The latter performs the waiting and checking discussed above.</p>
<p>The real action happens in <code>send_and_receive()</code>. This method should look a lot like the steps we defined earlier: <code>fork()</code> off a child, <code>send_messages()</code>, then <code>listen_for_messages()</code>.</p>
<p>Now this code has a couple of problems. One way to see them is to run it:</p>
<pre><code>$ ruby sleepy.rb
Received 3 messages: 3.03s
</code></pre>
<p>Doesn't three seconds sound a little slow for modern hardware communicating via a super efficient queuing system? Yeah, it is.</p>
<p>I actually put the sleeps in the code manually. Look for these two lines:</p>
<pre><code># ...
sleep 0.1 # don't peg the CPU while we wait
# ...
sleep 3 # make sure we're receiving before they are sent
# ...
</code></pre>
<p>Now it's obvious where the three second delay is coming from, eh? Let's talk about why I added that second <code>sleep()</code>.</p>
<p>The issue is that once we <code>fork()</code> that child process, it's off to the races. The parent process will continue running too, but we don't know who will get to what first. If the child fires off messages before the parent is listening for them, they will be missed. Instead we need the child to wail until the parent is ready to begin the experiment.</p>
<p>My three second sleep is one crude way to sort of handle this. I just delay the child for a significant period of time in computerland. Odds are that the parent will be setup by the time it starts sending. It could still fail though, if my machine was under heavy load at the time and it didn't give my parent process enough attention before the child woke up. Plus, it's slowing our experiment way down. In other words, this is a bad idea all around.</p>
<p>The good news is that we can fix it by making some semi-cryptic changes to just one method:</p>
<div class="highlight highlight-ruby"><pre><span class="k">def</span> <span class="nf">send_and_receive</span>
<span class="n">reader</span><span class="p">,</span> <span class="n">writer</span> <span class="o">=</span> <span class="no">IO</span><span class="o">.</span><span class="n">pipe</span>
<span class="n">pid</span> <span class="o">=</span> <span class="nb">fork</span> <span class="k">do</span>
<span class="n">writer</span><span class="o">.</span><span class="n">close</span>
<span class="n">reader</span><span class="o">.</span><span class="n">read</span>
<span class="n">reader</span><span class="o">.</span><span class="n">close</span>
<span class="n">send_messages</span><span class="p">(</span><span class="o">*</span><span class="no">MESSAGES</span><span class="p">)</span>
<span class="k">end</span>
<span class="no">Process</span><span class="o">.</span><span class="n">detach</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span>
<span class="n">reader</span><span class="o">.</span><span class="n">close</span>
<span class="n">received_messages</span> <span class="o">=</span> <span class="o">[</span> <span class="o">]</span>
<span class="n">listen_for_messages</span><span class="p">(</span><span class="n">received_messages</span><span class="p">)</span> <span class="k">do</span>
<span class="n">writer</span><span class="o">.</span><span class="n">puts</span> <span class="s2">"ready"</span>
<span class="n">writer</span><span class="o">.</span><span class="n">close</span>
<span class="n">wait_for_messages</span><span class="p">(</span><span class="n">received_messages</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>As you can see, I've introduced a pipe. A pipe is a one-way communication channel between processes. You get an endpoint to write to and another to read from. After you <code>fork()</code>, it's good practice to have each side <code>close()</code> the end they're not using. Then I just have the child call <code>read()</code> on the pipe. This will block until the parent sends some content that can be read. The parent completes its setup, including subscribing to the queue, and then it pushes a simple <code>"ready"</code> message down the pipe. That will get the child unblocked and sending messages.</p>
<p>Does this change help? Yes, a lot:</p>
<pre><code>$ ruby sleepy.rb
Received 3 messages: 0.10s
</code></pre>
<p>We're three seconds faster.</p>
<p>Unfortunately, the remaining delay looks suspiciously like my other call to <code>sleep()</code>. Here's that code to refresh your memory:</p>
<div class="highlight highlight-ruby"><pre><span class="k">def</span> <span class="nf">wait_for_messages</span><span class="p">(</span><span class="n">received_messages</span><span class="p">)</span>
<span class="k">until</span> <span class="n">received_messages</span> <span class="o">==</span> <span class="no">MESSAGES</span>
<span class="nb">sleep</span> <span class="mi">0</span><span class="o">.</span><span class="mi">1</span> <span class="c1"># don't peg the CPU while we wait</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>This loop just periodically checks to see if we have our three messages yet. We could technically remove the call to <code>sleep()</code> here and it would run. However, it would waste a lot of CPU time just checking these messages over and over again as fast as possible. Ironically, that bid for speed might starve the child process of resources and slow things down. So we kind of need the <code>sleep()</code>, or something like it.</p>
<p>But the problem remains that we're likely getting our messages very quickly and then just waiting for a <code>sleep()</code> call to run out so we notice they have arrived. We can do better with one simple change:</p>
<div class="highlight highlight-ruby"><pre><span class="k">def</span> <span class="nf">listen_for_messages</span><span class="p">(</span><span class="n">received_messages</span><span class="p">)</span>
<span class="n">connection</span> <span class="o">=</span> <span class="no">Bunny</span><span class="o">.</span><span class="n">new</span><span class="o">.</span><span class="n">tap</span><span class="p">(</span><span class="o">&</span><span class="ss">:start</span><span class="p">)</span>
<span class="n">queue</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">create_channel</span><span class="o">.</span><span class="n">queue</span><span class="p">(</span><span class="no">QUEUE_NAME</span><span class="p">,</span> <span class="n">auto_delete</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span>
<span class="n">main_thread</span> <span class="o">=</span> <span class="no">Thread</span><span class="o">.</span><span class="n">current</span>
<span class="n">queue</span><span class="o">.</span><span class="n">subscribe</span> <span class="k">do</span> <span class="o">|</span><span class="n">delivery_info</span><span class="p">,</span> <span class="n">metadata</span><span class="p">,</span> <span class="n">payload</span><span class="o">|</span>
<span class="n">received_messages</span> <span class="o"><<</span> <span class="n">payload</span>
<span class="n">main_thread</span><span class="o">.</span><span class="n">wakeup</span>
<span class="k">end</span>
<span class="n">time_it</span><span class="p">(</span><span class="s2">"Received </span><span class="si">#{</span><span class="no">MESSAGES</span><span class="o">.</span><span class="n">size</span><span class="si">}</span><span class="s2"> messages"</span><span class="p">)</span> <span class="k">do</span>
<span class="k">yield</span>
<span class="k">end</span>
<span class="n">connection</span><span class="o">.</span><span class="n">close</span>
<span class="k">end</span>
</pre></div>
<p>The difference here is that I capture the <code>main_thread</code> before I setup my subscription. Remember, that block will be called in a different <code>Thread</code>. Then, each time I receive a message, I cancel any <code>sleep()</code> the <code>main_thread</code> is currently doing with a call to <code>wakeup()</code>. This means it will recheck, when it should, as new messages arrive.</p>
<p>That gives us another significant speed boost:</p>
<pre><code>$ ruby sleepy.rb
Received 3 messages: 0.01s
</code></pre>
<p>I would probably stop here, but I should warn you that my solution isn't perfect. Some might be tempted to take this final step:</p>
<div class="highlight highlight-ruby"><pre><span class="k">def</span> <span class="nf">wait_for_messages</span><span class="p">(</span><span class="n">received_messages</span><span class="p">)</span>
<span class="k">until</span> <span class="n">received_messages</span> <span class="o">==</span> <span class="no">MESSAGES</span>
<span class="nb">sleep</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>Here the short <code>sleep()</code> has been changed into an indefinite one. You would think this is OK, because the other <code>Thread</code> will wake us when the time comes. Sadly, it's not because my last fix added a race condition. Consider what would happen if the <code>Thread</code>s executed code in this order:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="c1"># first the main thread checks, but finds only two of the three messages:</span>
<span class="k">until</span> <span class="n">received_messages</span> <span class="o">==</span> <span class="no">MESSAGES</span>
<span class="c1"># ...</span>
<span class="c1"># then the listening thread queues the final message and wakes the main</span>
<span class="c1"># thread (this has no effect since it isn't currently sleeping):</span>
<span class="n">received_messages</span> <span class="o"><<</span> <span class="n">payload</span>
<span class="n">main_thread</span><span class="o">.</span><span class="n">wakeup</span>
<span class="c1"># ...</span>
<span class="c1"># finally the main thread goes back to sleep, forever:</span>
<span class="nb">sleep</span>
</pre></div>
<p>As long as you leave my short <code>sleep</code>, you'll only pay a small penalty if this edge case does kick in.</p>
<p>Could we ensure it didn't happen though? Yes, with more message passing! Here's the final code:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/bin/env ruby</span>
<span class="nb">require</span> <span class="s2">"benchmark"</span>
<span class="nb">require</span> <span class="s2">"thread"</span>
<span class="nb">require</span> <span class="s2">"bunny"</span>
<span class="no">QUEUE_NAME</span> <span class="o">=</span> <span class="s2">"example"</span>
<span class="no">MESSAGES</span> <span class="o">=</span> <span class="sx">%w[first second third]</span>
<span class="k">def</span> <span class="nf">send_messages</span><span class="p">(</span><span class="o">*</span><span class="n">messages</span><span class="p">)</span>
<span class="n">connection</span> <span class="o">=</span> <span class="no">Bunny</span><span class="o">.</span><span class="n">new</span><span class="o">.</span><span class="n">tap</span><span class="p">(</span><span class="o">&</span><span class="ss">:start</span><span class="p">)</span>
<span class="n">exchange</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">create_channel</span><span class="o">.</span><span class="n">default_exchange</span>
<span class="n">messages</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">message</span><span class="o">|</span>
<span class="n">exchange</span><span class="o">.</span><span class="n">publish</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">routing_key</span><span class="p">:</span> <span class="no">QUEUE_NAME</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">connection</span><span class="o">.</span><span class="n">close</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">listen_for_messages</span><span class="p">(</span><span class="n">received_messages</span><span class="p">,</span> <span class="n">check_queue</span><span class="p">,</span> <span class="n">listen_queue</span><span class="p">)</span>
<span class="n">connection</span> <span class="o">=</span> <span class="no">Bunny</span><span class="o">.</span><span class="n">new</span><span class="o">.</span><span class="n">tap</span><span class="p">(</span><span class="o">&</span><span class="ss">:start</span><span class="p">)</span>
<span class="n">queue</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">create_channel</span><span class="o">.</span><span class="n">queue</span><span class="p">(</span><span class="no">QUEUE_NAME</span><span class="p">,</span> <span class="n">auto_delete</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span>
<span class="n">queue</span><span class="o">.</span><span class="n">subscribe</span> <span class="k">do</span> <span class="o">|</span><span class="n">delivery_info</span><span class="p">,</span> <span class="n">metadata</span><span class="p">,</span> <span class="n">payload</span><span class="o">|</span>
<span class="n">received_messages</span> <span class="o"><<</span> <span class="n">payload</span>
<span class="n">check_queue</span> <span class="o"><<</span> <span class="ss">:check</span>
<span class="n">listen_queue</span><span class="o">.</span><span class="n">pop</span>
<span class="k">end</span>
<span class="n">time_it</span><span class="p">(</span><span class="s2">"Received </span><span class="si">#{</span><span class="no">MESSAGES</span><span class="o">.</span><span class="n">size</span><span class="si">}</span><span class="s2"> messages"</span><span class="p">)</span> <span class="k">do</span>
<span class="k">yield</span>
<span class="k">end</span>
<span class="n">connection</span><span class="o">.</span><span class="n">close</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">time_it</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
<span class="n">elapsed</span> <span class="o">=</span> <span class="no">Benchmark</span><span class="o">.</span><span class="n">realtime</span> <span class="k">do</span>
<span class="k">yield</span>
<span class="k">end</span>
<span class="nb">puts</span> <span class="s2">"%s: %.2fs"</span> <span class="o">%</span> <span class="o">[</span><span class="nb">name</span><span class="p">,</span> <span class="n">elapsed</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">wait_for_messages</span><span class="p">(</span><span class="n">received_messages</span><span class="p">,</span> <span class="n">check_queue</span><span class="p">,</span> <span class="n">listen_queue</span><span class="p">)</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="n">check_queue</span><span class="o">.</span><span class="n">pop</span>
<span class="k">break</span> <span class="k">if</span> <span class="n">received_messages</span> <span class="o">==</span> <span class="no">MESSAGES</span>
<span class="n">listen_queue</span> <span class="o"><<</span> <span class="ss">:listen</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">send_and_receive</span>
<span class="n">reader</span><span class="p">,</span> <span class="n">writer</span> <span class="o">=</span> <span class="no">IO</span><span class="o">.</span><span class="n">pipe</span>
<span class="n">pid</span> <span class="o">=</span> <span class="nb">fork</span> <span class="k">do</span>
<span class="n">writer</span><span class="o">.</span><span class="n">close</span>
<span class="n">reader</span><span class="o">.</span><span class="n">read</span>
<span class="n">reader</span><span class="o">.</span><span class="n">close</span>
<span class="n">send_messages</span><span class="p">(</span><span class="o">*</span><span class="no">MESSAGES</span><span class="p">)</span>
<span class="k">end</span>
<span class="no">Process</span><span class="o">.</span><span class="n">detach</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span>
<span class="n">reader</span><span class="o">.</span><span class="n">close</span>
<span class="n">received_messages</span> <span class="o">=</span> <span class="o">[</span> <span class="o">]</span>
<span class="n">check_queue</span> <span class="o">=</span> <span class="no">Queue</span><span class="o">.</span><span class="n">new</span>
<span class="n">listen_queue</span> <span class="o">=</span> <span class="no">Queue</span><span class="o">.</span><span class="n">new</span>
<span class="n">listen_for_messages</span><span class="p">(</span><span class="n">received_messages</span><span class="p">,</span> <span class="n">check_queue</span><span class="p">,</span> <span class="n">listen_queue</span><span class="p">)</span> <span class="k">do</span>
<span class="n">writer</span><span class="o">.</span><span class="n">puts</span> <span class="s2">"ready"</span>
<span class="n">writer</span><span class="o">.</span><span class="n">close</span>
<span class="n">wait_for_messages</span><span class="p">(</span><span class="n">received_messages</span><span class="p">,</span> <span class="n">check_queue</span><span class="p">,</span> <span class="n">listen_queue</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">send_and_receive</span>
</pre></div>
<p>Look Ma, no <code>sleep()</code>!</p>
<p>My changes here are very similar to the earlier pipe trick, only I used a <code>Thread</code>-safe <code>Queue</code>. The <code>pop()</code> method of a <code>Queue</code> will block waiting just like <code>IO</code>'s <code>read()</code> did. I also had to introduce two <code>Queue</code>s, because I needed two-way communication. The listening <code>Thread</code> now tells the main <code>Thread</code> when it's time to check and it won't resume listening again until the main <code>Thread</code> gives approval.</p>
<p>I think this version is safe from race conditions and it doesn't wake up periodically to check things that haven't changed. It's also still as fast as the unsafe version.</p>
<p>If you must do safe multiprocessing, in any language, just pass messages.</p>James Edward Gray IIThe Ruby VM: Episode Vtag:graysoftinc.com,2007-08-03:/posts/352014-04-04T21:36:08ZIn this interview, Koichi takes us down the rabbit hole of Ruby optimizations. He gives us quite a few things to look forward to in future releases.<p><strong>You have told us before that one of the big reasons to move to a new Ruby VM was to provide new options for optimization. Can you talk a little about the optimizations you have added to the new Ruby VM thus far and what operations will likely be faster because of them?</strong></p>
<dl>
<dt>
<strong>ko1</strong>:</dt>
<dd>
<p>
OK. At first, I write about basic of YARV instruction. YARV
has two type instructions. First is primitive instruction.
It's as written, <strong>primitive</strong>. Ruby code can be
represented in these primitive instruction. Second is
instructions for optimization. It's not needed to represent
Ruby scripts, but they are added for optimization. Primitive
instructions doesn't include <code>_</code> in their name (like
<code>putobject</code>), and optimize instructions do (like
<code>opt_plus</code>). This policy helps you if you want to
see VM instructions. Initially, you need to read primitive
instructions.
</p>
<p>
The most easy and effective optimization is Specialized
Instructions. This optimization replace method call with
another VM instruction, such as <code>Fixnum#+</code> to
<code>opt_plus</code>. Current Ruby's numeric calculation is slow
because all operations are method call. For example, <code>1 + 2</code>
means <code>1.+(2)</code>. But numeric operations are more lightweight
than Ruby's method invocation. So method call is only overhead for
numeric operation. Specialized Instructions allow the VM to skip
method call overhead.
</p>
<p>
But we can't know which expression is numeric operation or not
at compile time. See this expression: <code>a = c ? 1 : [:elem]</code>,
<code>a</code> will be <code>Fixnum</code> or <code>Array</code>
at runtime.
</p>
<p>
So, we can't replace <code>+</code> expression with numeric operation
instruction. Specialized Instruction, for example <code>opt_plus</code>
which is replaced with <code>+</code> method invocation will do
following code:
</p>
<div class="highlight highlight-ruby"><pre><span class="k">def</span> <span class="nf">opt_plus</span><span class="p">(</span><span class="n">recv</span><span class="p">,</span> <span class="n">val</span><span class="p">)</span> <span class="c1"># simple version</span>
<span class="k">if</span> <span class="n">recv</span><span class="o">.</span><span class="n">class</span> <span class="o">==</span> <span class="no">Fixnum</span> <span class="o">&&</span> <span class="n">val</span><span class="o">.</span><span class="n">class</span> <span class="o">==</span> <span class="no">Fixnum</span>
<span class="k">if</span> <span class="no">Fixnum</span><span class="c1">#+ is not redefined</span>
<span class="k">return</span> <span class="n">calculate</span> <span class="s2">"recv + val"</span> <span class="n">without</span> <span class="nb">method</span> <span class="n">call</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># normal method invocation</span>
<span class="n">recv</span><span class="o">.</span><span class="n">+</span><span class="p">(</span><span class="n">val</span><span class="p">)</span>
<span class="k">end</span>
</pre></div>
<p>
Check receiver and value are <code>Fixnum</code> or not, and check
<code>Fixnum#+</code> are not redefined. After these check, calculate
them without method invocation. In fact, Float#+ are also checked.
There are other specialized instructions.
</p>
<p>
YARV eases to implement such instructions with VM generator.
You shouldn't write bothersome code such as stack
manipulation. If you write VM instruction such as <code>opt_plus</code>
in simple VM DSL, VM generator will translate it to C code.
</p>
<p>
Specialized Instruction is very simple, but effective for
simple benchmark such as <code>fib()</code> or <code>tak()</code>
and some calculate bound program.
</p>
<dd>
</dd>
</dd>
</dl><p><strong>One question I thought of while reading your previous answer was: will Ruby scripts be able to access these VM instructions, if desired?</strong></p>
<dl>
<dt>
<strong>ko1</strong>:</dt>
<dd>
<p>
Simple answer is "yes".
</p>
<p>
On YARV, bytecode and other information are represented as the
<code>VM::InstructionSequence</code> class. I often use the name
"ISeq" to point that class. ISeq object
contains a bytecode sequence, a catch table (to retrieve exception
and other global escape such as <code>break</code>), a local variable
name table and others.
</p>
<p>
ISeq object can be dumped in Ruby's primitive objects
such as <code>Array</code>, <code>Hash</code>, <code>Fixnum</code>
and so on. In the same way, ISeq can be
built with such data with primitive objects. This means that
you can built YARV bytecode without YARV compiler. Of course,
this feature can be used for other purpose such as ruby script
obfuscation (this is like Java class file).
</p>
<p>
(BTW, I use this feature on Ruby2C compiler. It is hard to
translate Ruby program to C program directly. But from YARV
instruction, translation is easy. If I finished it, I want to
bundle this with Ruby.)
</p>
<p>
Therefore it is hard to write ISeq dumped data. So I had
prepared <code>lib/yasm.rb</code> as YARV Assembler (this is not
committed on current trunk). With <code>YASM</code>, you can write YARV
bytecode sequence on Ruby program. Note that YARV/ISeq loader doesn't
have the byte code verifier. So illegal bytecode sequence is
loaded, YARV/Ruby will dumps core.
</p>
<p>
If I commit <code>lib/yasm.rb</code>, I'll write tutorial to use that.
</p>
<dd>
</dd>
</dd>
</dl><p><strong>Does the new Ruby VM optimize tail recursive methods? If no, are there any plans to add this optimization?</strong></p>
<dl>
<dt>
<strong>ko1</strong>:</dt>
<dd>
<p>
YARV doesn't support "tail recursion optimization", but
supports "tail call optimization".
</p>
<p>
See this program:
</p>
<div class="highlight highlight-ruby"><pre><span class="k">class</span> <span class="nc">C</span>
<span class="k">def</span> <span class="nf">foo</span>
<span class="n">foo</span> <span class="c1"># (A) tail recursive call</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">D</span> <span class="o"><</span> <span class="n">C</span>
<span class="k">def</span> <span class="nf">foo</span>
<span class="k">super</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">D</span><span class="o">.</span><span class="n">new</span><span class="o">.</span><span class="n">foo</span>
</pre></div>
<p>
Can you replace <code>goto</code> with (A)? (A) should call
<code>D#foo</code> so we eliminate tail method call. Yes, we
can implement this optimization with following trick.
</p>
<div class="highlight highlight-ruby"><pre><span class="k">class</span> <span class="nc">C</span>
<span class="k">def</span> <span class="nf">foo</span>
<span class="k">if</span> <span class="n">search_method</span><span class="p">(</span><span class="ss">:foo</span><span class="p">)</span> <span class="o">==</span> <span class="n">C</span><span class="c1">#foo</span>
<span class="n">goto</span> <span class="n">first_of_foo</span>
<span class="k">else</span>
<span class="n">foo</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>
But we must think of inter block tail recursion or so (inter
block goto is not permitted) if implement tail recursion
optimization.
</p>
<p>
BTW, YARV support tail call optimization, eliminate stack
frame of caller. You can call method which at tail position
without consuming VM stack like scheme language. So you can
use method call to loop something. You can make state
transition with method call.
</p>
<p>
Note that tail call optimization has some caution. First is
backtrace elimination. You can't see caller method of tail
method with backtrace. Second, this optimization does
<strong>not</strong> speedup method call. Tail call process is almost
same as process of normal method call. At end of normal method call
process, check if tail call or not. If that method call is
tail call, use current method frame to setup method frame
instead of pushing new stack frame.
</p>
<p>
Current Ruby 1.9 (trunk) is not enabled this optimization. If
you want to try this, please re-write that option in
<code>vm_opts.h</code> (<code>OPT_TAILCALL_OPTIMIZATION</code>) and
re-compile that. I think release version of Ruby 1.9 is enabled this
optimization. I need more comments of it. Please teach me if
you find out some critical problem.
</p>
<dd>
</dd>
</dd>
</dl><p><strong>Can you talk a little about some optimizations you would like to add to the new Ruby VM in the future?</strong></p>
<dl>
<dt>
<strong>ko1</strong>:</dt>
<dd>
<p>
In near future, I'll release AOT, Ruby to C compiler. This
translator will support all Ruby specification,
so it's shouldn't be silver bullet for performance.
</p>
<p>
Keeping all Ruby spec means "can't achieve high
performance". If I ignore some spec, I'll be able to do more
drastic optimization. So C code translated from Ruby script
will be slow (of course, faster than normal interpretation).
</p>
<p>
Ruby specification is enemy for compiler/VM developer. So I
want to add a "pragma" syntax to add programer's knowledge.
For example, "<code>eval</code> is not appear in this file" or
"<code>Fixnum</code> methods are not re-defined". These information
will help compiler to do more effective optimization.
</p>
<p>
And I'm planning to implement block inlining. I think it is
very effective for Ruby. An experimental, incomplete version
has been made. I need more research to realize it.
</p>
<p>
BTW, I will not touch JIT compilation. I think it is not
reasonable (not worth the cost of implementation). Everyone
love "JIT" words, but I think it's not effective on Ruby spec.
</p>
<dd>
</dd>
</dd>
</dl>James Edward Gray IIThe Ruby VM: Episode IIItag:graysoftinc.com,2007-04-27:/posts/332014-04-04T21:03:39ZIn this interview, we get the scoop on the past and future of Ruby's threading support.<p><strong>Let's talk a little about threading, since that's a significant change in the new VM. First, can you please explain the old threading model used in Ruby 1.8 and also the new threading model now used in Ruby 1.9?</strong></p>
<dl>
<dt>
<strong>Matz</strong>:</dt>
<dd>
<p>
Old threading model is the green thread, to provide universal
threading on every platform that Ruby runs. I think it was reasonable
decision 14 years ago, when I started developing Ruby. Time goes by
situation has changed. pthread or similar threading libraries are now
available on almost every platform. Even on old platforms, pth
library (a thread library which implements pthread API using setjmp
etc.) can provide green thread implementation.
</p>
<p>
Koichi decided to use native thread for YARV. I honor his decision.
Only regret I have is we couldn't have continuation support that used
our green thread internal structure. Koichi once told me it's not
impossible to implement continuation on YARV (with some restriction),
so I expect to have it again in the future. Although it certainly has
lower priority in 1.9 implementation.
</p>
<dd>
</dd>
</dd>
<dt>
<strong>ko1</strong>:</dt>
<dd>
<p>
Matz explained old one, so I show you YARV's thread model.
</p>
<p>
As you know, YARV support native thread. It means that you can run each
Ruby thread on each native thread concurrently.
</p>
<p>
It doesn't mean that every Ruby thread runs in parallel. YARV has
global VM lock (global interpreter lock) which only one running Ruby
thread has. This decision maybe makes us happy because we can run most
of the extensions written in C without any modifications.
</p>
<dd>
</dd>
</dd>
</dl><p><strong>Why was this change made? What's wrong with green threads?</strong></p>
<dl>
<dt>
<strong>Matz</strong>:</dt>
<dd>
<p>
Because green threads does not work well with libraries using native
threads. For example, Ruby/Tk has made huge effort to live along with
pthread.
</p>
<dd>
</dd>
</dd>
<dt>
<strong>ko1</strong>:</dt>
<dd>
<p>
Ruby's green (userlevel) thread implementation was too naive to run
fast. All machine stacks are copied when thread context switches. And
more important point is it's not easy to re-implement green thread on
YARV :)
</p>
<dd>
</dd>
</dd>
</dl><p><strong>What are the downsides to the native threads approach?</strong></p>
<dl>
<dt>
<strong>Matz</strong>:</dt>
<dd>
<p>
It is pretty difficult to implement continuation. Besides that, even
with native thread approach, no real concurrency can not be made due
to the global interpreter lock. Koichi is going to address this issue
by Multi-VM approach in the (near) future.
</p>
<dd>
</dd>
</dd>
<dt>
<strong>ko1</strong>:</dt>
<dd>
<p>
Yes, it has several problems. First is Performance problem (as you
know, I love to discuss about performance). Too create native thread is
too pricey. So you may use thread pool or so. And current trunk (YARV)
is not tuned on native thread, so I believe some unknown problems
around threads.
</p>
<p>
Second problem is portability. If your environment has pthread library,
but there are some difference from other pthread system in detail.
</p>
<p>
Third problem is absence of callcc (which is implemented with green
thread scheme) ... for some people :)
</p>
<p>
Programming on native thread has own difficulty. For example, on MacOS
X, exec() doesn't work (cause exception) if other threads are running
(one of portability problem). If we find critical problems on native
thread, I will make green thread version on trunk (YARV).
</p>
<dd>
</dd>
</dd>
</dl><p><strong>Are there plans to support other threading models in the future?</strong></p>
<dl>
<dt>
<strong>Matz</strong>:</dt>
<dd>
<p>
Other threading model, no. Win32 threads and pthreads are enough
burden for us to support. There might be other features to support
parallelism in the future, for example light-weight process a la
Erlang.
</p>
<p>
Koichi may have other idea(s) about supporting concurrency, such as
Multi-VM since he is the expert on it.
</p>
<dd>
</dd>
</dd>
<dt>
<strong>ko1</strong>:</dt>
<dd>
<p>
Parallel computing with Ruby is one of my main concern. There are some
way to do it, but running Ruby threads in parallel (without Giant VM
Lock) on a process is too difficult to support current C extension
libraries because of their synchronization problems.
</p>
<p>
As matz say, if we have multiple VM instance on a process, these VMs can
be run in parallel. I'll work on that theme in the near future (as my
research topic).
</p>
<p>
BTW, I wrote on last question, if there are many many problems on native
threads, I'll implement green thread. As you know, it's has some
benefit against native thread (lightweight thread creation, etc). It
will be lovely hack (FYI. my graduation thesis is to implement userlevel
thread library on our specific SMT CPU).
</p>
<p>
... Does anyone have interest to implement it?
</p>
<dd>
</dd>
</dd>
</dl>James Edward Gray IINo Longer the Fastest Game in Towntag:graysoftinc.com,2007-04-16:/posts/322019-09-10T09:53:35ZSome speed demons are faster than me. Let's take a look at the new efforts in CSV parsing.<p>If your number one concern when working with CSV data in Ruby is raw speed, you might want to know that FasterCSV is no longer the fastest option.</p>
<p>There are a couple of new contenders for Ruby CSV processing including a C extension called <a href="https://rubygems.org/gems/simplecsv">SimpleCSV</a> and a pure Ruby library called <a href="https://github.com/smulube/lightcsv">LightCsv</a>. I haven't been able to test <code>SimpleCSV</code> locally, because I can't get it to build on my box, but users do tell me it's faster. I have run some trivial benchmarks for <code>LightCsv</code> though and it too is pretty quick:</p>
<pre><code>$ rake benchmark
(in /Users/james/Documents/faster_csv)
time ruby -r csv -e '6.times { CSV.foreach("test/test_data.csv") { |row| } }'
real 0m5.481s
user 0m5.468s
sys 0m0.010s
time ruby -r lightcsv -e \
'6.times { LightCsv.foreach("test/test_data.csv") { |row| } }'
real 0m0.358s
user 0m0.349s
sys 0m0.008s
time ruby -r lib/faster_csv -e \
'6.times { FasterCSV.foreach("test/test_data.csv") { |row| } }'
real 0m0.742s
user 0m0.732s
sys 0m0.009s
</code></pre>
<p>It's important to note that <code>LightCsv</code> is indeed very "light." <code>FasterCSV</code> has grown up into a feature rich library that provides many different ways to look at your data. In contrast, <code>LightCsv</code> doesn't yet allow you to set column or row separators. Given that, it's only an option for vanilla CSV you just need to iterate over. If that's what you have though, and speed counts, it might just be the right choice.</p>
<p>For the curious, <code>LightCsv</code> achieves its speed advantage in two ways. First, it uses <code>StringScanner</code> to manage the parsing. <code>StringScanner</code> is a C extension, though it is a standard library installed with Ruby.</p>
<p>More importantly, I suspect, <code>LightCsv</code> uses an input buffer for reading while <code>FasterCSV</code> works line by line. I suspect this second difference accounts for the majority of the speed increase since the buffered code will hit the hard drive quite a bit less for the average CSV file. This does require more memory though, of course.</p>
<p>Aside from these differences, <code>FasterCSV</code> and <code>LightCsv</code> have very similar parsers.</p>James Edward Gray IIThe Ruby VM: Episode IItag:graysoftinc.com,2007-03-16:/posts/312014-04-30T02:30:03ZIn this interview, we discuss all of the alternate Ruby implementations that have sprung up and what they mean for language and developers.<p><strong>We started these talks because of the excitement around the alternate implementations, like JRuby and Rubinius. How do you feel about all of these new interpreters and how do you see them affecting the official development of Ruby?</strong></p>
<dl>
<dt>
<strong>Matz</strong>:</dt>
<dd>
<p>
Alternate implementations mean maturity of Ruby language. I'm glad
for the fact. But we have never had enough number of developers for
core, so I think we need more cooperation between implementations. I
had a good talk about future Ruby spec. with Charles Nutter recently.
I expect occasion like this more often.
</p>
<dd>
</dd>
</dd>
<dt>
<strong>ko1</strong>:</dt>
<dd>
<p>
I think having alternatives is very important. I want to know how to
implement Ruby and apply these techniques to YARV.
</p>
<p>
In fact, implementing from scratch is very fun. YARV (official Ruby
implementation) has many problems resulted from historical reasons (a
biggest problem is compatibility to extension libraries).
</p>
<dd>
</dd>
</dd>
</dl><p><strong>Have you downloaded and installed any of the other interpreters?</strong></p>
<dl>
<dt>
<strong>Matz</strong>:</dt>
<dd>
<p>
No, I just skimmed a few files from Rubinius, but not others. Mostly
because I am not familiar with neither Java nor Parrot.
</p>
<dd>
</dd>
</dd>
<dt>
<strong>ko1</strong>:</dt>
<dd>
<p>
I wanted to try these alternatives, but no time to do it (and no time to
hack YARV ...).
</p>
<p>
Answer is: No. I'll try.
</p>
<dd>
</dd>
</dd>
</dl><p><strong>Is there a good exchange of ideas between the various implementation teams? Do you talk to the other teams, read their code, and/or discuss implementation details with them?</strong></p>
<dl>
<dt>
<strong>Matz</strong>:</dt>
<dd>
<p>
Besides Koichi who works on YARV with me, Last month I met with
Charles Nutter and exchanged very interesting idea about 2.0 behavior.
Evan Phoenix also gave me inspiration. I am very glad to see more
programmers with interest and knowledge in language implementation.
</p>
<dd>
</dd>
</dd>
<dt>
<strong>ko1</strong>:</dt>
<dd>
<p>
Sometimes I talked with JRuby team on IRC. I want to discuss every Ruby
implementation developers, especially performance of it.
</p>
<p>
BTW we need 3 things on this context:
</p>
<ol>
<li>Documents of specification</li>
<li>Good tests</li>
<li>Good benchmarks</li>
</ol>
<p>
<em>Tests:</em> Ruby trunk and 1.8 have test suits. But it's too difficult
to test with it on early stage of implementation, because test/unit use
many ruby's functions (RSpec has a same problem). Now, trunk has
"bootstraptest" to solve it. I think it is good solution for this problem.
And it's show a minimum ruby's specification.
</p>
<p>
<em>Benchmark tests:</em> Some people using YARV's benchmarks I wrote. But
I didn't write these codes to measure "Ruby's general benchmark test", but
to measure speed-up ratio on YARV. It's means that I wrote codes what YARV
optimizes. We must prepare more suitable benchmarks for "Ruby
implementations".
</p>
<dd>
</dd>
</dd>
</dl>James Edward Gray IIThe Ruby VM: Episode Itag:graysoftinc.com,2007-02-16:/posts/292014-04-04T20:29:56ZThis first interview covers who I'm talking with, what we are talking about, and when we will see the results of their labor.<p><strong>Hello and thank you both for agreeing to answer my questions. To begin, would you please introduce yourselves and tell us about your role in Ruby's development?</strong></p>
<dl>
<dt>
<strong>Matz</strong>:</dt>
<dd>
<p>
I am the designer and the first implementer of the Ruby language. My
real name is Yukihiro Matsumoto, that sounds something like
You-Key-Hero Matz-Motor in English. But it's too long to remember and
pronounce, so just call me Matz.
</p>
<p>
I have been developing Ruby since 1993. It is now quite complicated
and has performance problem. I have had vague plan of rewriting the
interpreter for long time, but I have never been motivated enough to
throw out the current interpreter and start developing new one.
</p>
<p>
Then Koichi came in with YARV that seemed to have much brighter future
than my vaporware - it runs - so I asked him to take a role of the
official implementer of the core. Although I enjoy both designing and
implementation of the language, I don't think I am gifted for language
implementation. So I thought that it might be the time to focus on
designing when I saw YARV.
</p>
<dd>
</dd>
</dd>
<dt>
<strong>ko1</strong>:</dt>
<dd>
<p>
Thank you for your interest in YARV and me. BTW, I'm thinking what "YARV"
stand for. Because it is not Yet Another. Someone proposed that "YARV
ain't RubyVM". If YARV means "YARV ain't RubyVM", what is YARV?
</p>
<p>
I'm Koichi Sasada. Koichi is given name, and "ichi" means "one" in
Japanese. So I use "ko1" as my nick. I'm an assistant at Department
(...snip...) of Tokyo. My research interest is systems software,
especially operating system, programming language, parallel systems, and
so on. And I'm a member of Nihon Ruby no Kai (Ruby Association in Japan).
I plan(ed) some Ruby events like
<a href="http://jp.rubyist.net/RubyKaigi2007/english.html">RubyKaigi</a>
and am an editor of
<a href="http://jp.rubyist.net/magazine">Rubyist Magazine</a>. I also
develop(ed) <a href="http://www.atdot.net/nadoka/">Nadoka</a>,
<a href="http://www.namikilab.tuat.ac.jp/~sasada/prog/rava2.html">Rava</a>,
<a href="http://www.namikilab.tuat.ac.jp/~sasada/prog/rucheme.html">Rucheme</a>,
and some projects. Say, I'm a developer of YARV: Yet Another RubyVM.
</p>
<p>
My role in Ruby's development? To steal VM hacking pleasure from Matz?
</p>
<dd>
</dd>
</dd>
</dl><p><strong>The point of this interview is to talk about the future of Ruby's interpreter. To start that, can you please explain what YARV/Rite is? How is it different in design from the old Ruby interpreter?</strong></p>
<dl>
<dt>
<strong>Matz</strong>:</dt>
<dd>
<p>
I have always been more interested in designing the language than
implementing it. So Ruby interpreter is always slower than it should
be. I think I pruned all low-hanging fruits, so that it seemed to
required to re-implement whole core to achieve performance boost. I
planned a new interpreter code-named 'Rite' in 2001 or so, but I have
never motivated enough to start the project. Maybe I had been too
busy, or perhaps too lazy.
</p>
<p>
Then, Koichi came in, and showed us his YARV. Many had tried
implementing Ruby interpreter in the past, but no one but Koichi
reached that level of implemented feature set (at the time; now we
have JRuby and RubyCLR both compatible with Ruby 1.8). So I asked him
to take part in the development of the new core, and he agreed.
</p>
<p>
January 1st 2007, he checked in YARV in to the trunk of our
repository, so it is now official core of the Ruby 1.9. I am still
working on old implementation in matzruby branch. Since it is easier
for me to experiment new language features on the old interpreter, but
I will eventually switch to the new engine.
</p>
<p>
For YARV implementation detail, Koichi will explain.
</p>
<p><strong>Does this mean we are leaving the name Rite behind and keeping YARV? Or will YARV be renamed at some point?</strong></p>
<p>
The name Rite will not be used for this generation of the language,
unless Koichi ask me. I am not sure Koichi is going to keep YARV, or
not, since it already 'the VM' for Ruby.
</p>
<dd>
</dd>
</dd>
<dt>
<strong>ko1</strong>:</dt>
<dd>
<p>
YARV is vanished :)
</p>
<p>
In fact, I'm removing "yarv" words from structure names, function names,
and file names. YARV is only code name that not made by *Matz*. Now,
YARV is not "Yet Another". In this article, I use "YARV" words as
current Ruby trunk on official repository.
</p>
<p>
At first, YARV is simple stack machine which run pseudo sequential
instructions. Old interpreter (matzruby) *traverses* abstract syntax
tree (AST) naively. Obviously it's slow. YARV compile that AST to YARV
bytecode and run it.
</p>
<p>
Secondly, YARV uses native thread (that supported by OS or so) to
implement Ruby thread. It means that you can run *blocking* task in
extension libraries. <em>(On Ruby's spec, blocking task should be
interrupted by <code>Thread#raise</code>. To know details, see
<a href="http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/10252">[ruby-core:10252]</a>.)</em>
Because thread creation is slower than matzruby (green thread), you
shouldn't make many threads at a time. Supporting native thread *does not*
means that you can run Ruby scripts in *parallel* on parallel machine such
as Multi-Core CPUs. Current implementation uses Giant VM Lock to avoid
synchronization problems. <em>(Many extension libraries doesn't care
thread safety. See <code>array.c</code>, <code>string.c</code>,
etc.)</em>
</p>
<p>
Thirdly, I made many optimization like specialized instructions, etc.
These features are my purpose of developing YARV. Toy benchmarks run
fast because of these optimization techniques.
</p>
<p>
YARV doesn't change parser/syntax/specs (matz'
<span style="text-decoration: line-through">hobby</span>task), GC
(memory/object management), and extension libraries like
<code>String</code>/<code>Array</code>/<code>Hash</code>/<code>Regexp</code>/etc.
Therefore your script doesn't run fast on YARV if bottleneck is string
processing, or so.
</p>
<dd>
</dd>
</dd>
</dl><p><strong>Congratulations to you both for completing Ruby/YARV merger recently. That must have been a lot of work, but I know it has the whole Ruby world very excited. Now that the merger has taken place, how do you see this changing the way Ruby is developed?</strong></p>
<dl>
<dt>
<strong>Matz</strong>:</dt>
<dd>
<p>
Congrats should go to Koichi who has done a lot of work. I am moving
my developing from matzruby (a branch for my old interpreter) to
trunk (the yarv). Recently I have implemented some new features on
the trunk, for example, class local instance variables and new local
variable scope. The transition will complete pretty soon.
</p>
<p>
Since the trunk is originally Koichi's work, I need more help from
others especially from Koichi than before. I know everything about
the previous interpreter (well, most of them), but there are still
mysteries in the new one. I am well satisfied with new one. It's
clearer, well-formed, and faster.
</p>
<dd>
</dd>
</dd>
<dt>
<strong>ko1</strong>:</dt>
<dd>
<p>
Thank you. I'm a newbie of Ruby developer (in fact, I didn't have CVS
account to commit any ruby codes). So I can't say how change on ruby
development :)
</p>
<dd>
</dd>
</dd>
</dl><p><strong>When will the first production release of Ruby running on YARV by available for all Rubyists to play with?</strong></p>
<dl>
<dt>
<strong>Matz</strong>:</dt>
<dd>
<p>
Short answer: now.
</p>
<p>
Longer answer: the YARV is already publicly avaiblabe via our
Subversion repository. You can fetch and play with it now. But the
first public "release" from us will be Christmas 2007, if we are as
diligent as we should be. Knowing how lazy I am, I will try not to be
a stumbling block for the release. ;-)
</p>
<dd>
</dd>
</dd>
</dl>James Edward Gray IIYARV Looking Promising, James's C is Nottag:graysoftinc.com,2006-07-29:/posts/192014-04-03T19:46:01ZWhen performance becomes super critical, YARV shows some promise for Ruby's future.<p>I participated in the <a href="http://icfpcontest.org/">ICFP programming contest</a> last weekend with a group of friends. We had a great time with the event and learned a ton. I thought I should share two interesting insights with others that might appreciate them.</p>
<p>First, <a href="http://www.atdot.net/yarv/">YARV</a> looks very promising for some general speed increases in Ruby. If you are not familiar with YARV, that's the virtual machine that will run Ruby 1.9. During the contest, we ran into some performance issues with our Ruby solution and after we had optimized all we could think of, we decided to try running our entry on the experimental YARV VM to see if it was faster there. Good news: it was a lot faster.</p>
<p>Please do not take these numbers as anything more than very non-scientific observations, but we did notice a huge speed increase on YARV. We were reliably waiting around 15 minutes for one section of our program to run on Ruby 1.8.4, but when we introduced YARV the same section generally ran in just under seven minutes. You heard me right there, it was over twice as fast. I think that's very promising news for the future of Ruby.</p>
<p>The not so good news is that it still just wasn't fast enough.</p>
<p>Of course we want to be able to use Ruby for as much as possible, but it is important to admit that it's just not fit for every job. The programming contest involved the creation of a small VM that ran many, many instructions from contest provided data files. In order to get that to a reasonable level of performance, you really needed some C.</p>
<p>The good news is that Ruby will easily allow you to drop down to C and integrate that code with your script. The bad news is that James's C is so rusty, that was a nightmare. Thank goodness one of my partners was more capable. He certainly carried us through.</p>
<p>I don't need C very often any more. I think it has literally been about a year since I last felt the need. However, there are jobs Ruby is a bit too slow to handle and when they come up, C is your best friend. I'm definitely brushing up on my C skills before next year's contest.</p>James Edward Gray II