<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by k0kubun on Medium]]></title>
        <description><![CDATA[Stories by k0kubun on Medium]]></description>
        <link>https://medium.com/@k0kubun?source=rss-c2f58218a6ba------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*hxcS7MXpWP11buoT.png</url>
            <title>Stories by k0kubun on Medium</title>
            <link>https://medium.com/@k0kubun?source=rss-c2f58218a6ba------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 16:54:57 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@k0kubun/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[RJIT: A Pure-Ruby JIT for Ruby]]></title>
            <link>https://k0kubun.medium.com/rjit-a-pure-ruby-jit-for-ruby-f4084f0765?source=rss-c2f58218a6ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/f4084f0765</guid>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[jit]]></category>
            <dc:creator><![CDATA[k0kubun]]></dc:creator>
            <pubDate>Thu, 09 Mar 2023 17:07:49 GMT</pubDate>
            <atom:updated>2023-03-09T17:21:15.057Z</atom:updated>
            <content:encoded><![CDATA[<p>I’m Takashi Kokubun (<a href="https://twitter.com/k0kubun">@k0kubun</a>), a Staff Developer at Shopify who works on optimizing <a href="https://yjit.org/">YJIT</a> full-time, which is Ruby’s production-grade JIT compiler written in Rust. While I work solely on YJIT during my work hours, I’ve also maintained another JIT in Ruby, MJIT, in my spare time.</p><p>This year, I <a href="https://github.com/ruby/ruby/pull/7448">proposed</a> to change the MJIT’s design to simplify the Ruby VM, which will ultimately make the YJIT development easier. Since the new version is no longer a “M”ethod “JIT”, which MJIT was named after, I changed the project name to RJIT. RJIT is a pure-Ruby JIT compiler, so it’s called “R”uby “JIT”.</p><p><a href="https://github.com/ruby/ruby/pull/7448">RJIT by k0kubun · Pull Request #7448 · ruby/ruby</a></p><h3>Why does Ruby have two JITs?</h3><p>It’s not rare that a language supports multiple JITs. For example, the Java HotSpot VM has the JVM Compiler Interface (JVMCI) that allows you to integrate a JIT like the <a href="https://www.graalvm.org/latest/reference-manual/java/compiler/">Graal Compiler</a>.</p><p>In the case of Ruby, the reference implementation CRuby doesn’t have public interface to integrate a third-party JIT. However, while CRuby already has YJIT that <a href="https://twitter.com/tobi/status/1605656985185226753">optimizes production pretty well</a>, people have still developed other CRuby JITs, such as <a href="https://github.com/vnmakarov/ruby">vnmakarov/ruby (sir-mirjit)</a>, <a href="https://github.com/tenderlove/tenderjit">tenderlove/tenderjit</a>, and <a href="https://github.com/jhawthorn/hawthjit">jhawthorn/hawthjit</a>.</p><p>Since Ruby 3.2 MJIT was partly rewritten in Ruby, <a href="https://speakerdeck.com/k0kubun/rubykaigi-2022?slide=22">it became possible</a> to integrate a third-party JIT like those by monkey-patching it. It’s always fun to write your own compiler. If you just remove MJIT, you won’t be able to enjoy that anymore. <a href="https://youtu.be/PBEklhwJcUA?t=519">It’s also a great place to experiment with ideas for JIT optimization that can later be applied to YJIT</a>. So I decided to keep this project while my primary interest remains to be making YJIT the best JIT for Ruby.</p><h3>Which JIT should I use in production?</h3><p>YJIT. Period.</p><p>YJIT is written in Rust while RJIT is in Ruby. While its peak performance could be as fast as YJIT if RJIT generates the same code as YJIT, there will always be a drawback in the warmup speed and memory footprint. I don’t think RJIT will ever beat this trade-off. Fast warmup and low memory footprint is crucial for companies like Shopify where developers deploy Ruby applications quite often to massive-scale clusters.</p><p>Furthermore, Shopify use YJIT in their tier-1 applications. They are business-critical, so if a problem is found in YJIT, the YJIT team fixes it very quickly. You can’t expect the same thing from a hobby project maintained in a single person’s spare time, RJIT. The YJIT team is a team full of kind and talented engineers. I’d like to once again thank the team and Shopify for having me as a teammate.</p><h3>Conclusion</h3><p>RJIT is there for your fun. Enjoy your JIT development in Ruby. If you’re interested, my <a href="https://rubykaigi.org/2023/">RubyKaigi 2023</a> talk will cover how to develop and/or contribute to a Ruby JIT. See you in Matsumoto!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f4084f0765" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ruby 3 JIT can make Rails faster]]></title>
            <link>https://k0kubun.medium.com/ruby-3-jit-can-make-rails-faster-756310f235a?source=rss-c2f58218a6ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/756310f235a</guid>
            <category><![CDATA[jit-compilers]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[ruby]]></category>
            <dc:creator><![CDATA[k0kubun]]></dc:creator>
            <pubDate>Fri, 21 May 2021 06:34:32 GMT</pubDate>
            <atom:updated>2021-05-21T08:28:20.000Z</atom:updated>
            <content:encoded><![CDATA[<p>I’ve wondered <a href="https://speakerdeck.com/k0kubun/the-method-jit-compiler?slide=38">Why Rails becomes slow with JIT</a> for a long time. Today, I’m pleased to share my answer to the question in this article, which I spent three years of my life to figure out.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ictexCreWCzJFHlWehUCSw.png" /><figcaption>RubyKaig 2018 / The Method JIT Compiler for Ruby 2.6</figcaption></figure><h3>&quot;MJIT Does Not Improve Rails Performance&quot;</h3><p>As I wrote in <a href="https://www.ruby-lang.org/en/news/2020/12/25/ruby-3-0-0-released/">Ruby 3.0.0 Release Note</a> and <a href="https://medium.com/@k0kubun/ruby-3-0-jit-and-beyond-4d9404ce33c">my previous post</a>, we thought:</p><blockquote>it is still not ready for optimizing workloads like Rails, which often spend time on so many methods and therefore suffer from i-cache misses exacerbated by JIT.</blockquote><p>We thought Ruby 3’s JIT a.k.a. <a href="https://www.youtube.com/watch?v=WbrSsIoTVsU&amp;t=1132s">MJIT Does Not Improve Rails Performance</a> and <a href="http://codefol.io/posts/is-ruby-3-actually-three-times-faster/">Rails Ruby Bench gets slower, not faster</a>. That’s <a href="https://speakerdeck.com/k0kubun/rubyconf-china-2020?slide=19">what I observed last year</a> too.</p><p>Because of that and to support environments without compilers, Matz is thinking about <a href="https://youtu.be/WbrSsIoTVsU?t=1173">introducing JIT that does not invoke compilers</a>, <a href="https://developers.redhat.com/blog/2021/04/27/the-mir-c-interpreter-and-just-in-time-jit-compiler">MIR</a> or <a href="https://www.youtube.com/watch?v=vucLAqv7qpc">YJIT</a>.</p><h3>Why does Rails become slower on Ruby 3’s JIT?</h3><p>People have thought:</p><ul><li><a href="https://www.youtube.com/watch?v=vucLAqv7qpc&amp;t=672s">They were very focused on this one synthetic benchmark called Optcarrot.</a></li><li><a href="https://www.youtube.com/watch?v=vucLAqv7qpc&amp;t=710s">GCC is not really equipped to optimize dynamically typed code.</a></li><li><a href="https://www.fastruby.io/blog/ruby/performance/why-wasnt-ruby-3-faster.html">There are a lot of useful optimizations that got lost in the shuffle, and a lot of code that hasn’t yet been rewritten to use it.</a></li></ul><p>I mean, I&#39;ve literally thought about optimizing Rails by MJIT for over 3 years, and I&#39;ve introduced several optimizations designed for such workloads. We do rely on GCC to ease our maintenance, but the reason why it optimizes some benchmarks is that it implements various Ruby VM-level optimizations that don’t really depend on GCC, not just that it uses GCC. Most of RTL-MJIT’s optimizations were already ported to or experimented with YARV-MJIT after we shuffled the code to make it work with YARV, and it’s now faster than RTL-MJIT because of that and many other improvements after it.</p><p>Then… why?</p><h4>Is it because we use C compilers?</h4><p>MJIT writes C code and invokes a C compiler to generate native code. Slow compilation time aside, which doesn&#39;t impact the peak performance, I see two performance problems in using C compilers as a JIT backend.</p><p>First, using a C compiler for code generation makes it hard to implement some low-level techniques, such as calculating a VM program counter from a native instruction pointer, retrieving VM stack values from a native stack, patching machine code, etc. One such optimization that YJIT implements and MJIT doesn’t is dispatching JIT-ed code from direct threading just like normal VM instructions. I <a href="https://github.com/k0kubun/ruby/commit/a37fd18bd006024aa3106481f36d406af56a0701">ported the technique to MJIT</a> and <a href="https://github.com/k0kubun/ruby/commit/3c8e951e9fb5e3e8860102dc47abae73c079a404">even tested generating a whole method with an inline assembler</a>, but it didn’t seem to help the situation and MJIT’s dispatch wasn’t really slower than that.</p><p>Second, there’s an overhead caused by using dlopen as a loader. Whenever you call dlopen, it creates multiple memory pages for different ELF sections. More importantly, methods generated by different dlopen calls are on different pages. However, we removed the latter problem by periodically recompiling all methods to a single binary, which I call &quot;JIT compaction&quot;. Also, function calls from a shared object are a bit slow. In fact, Ruby becomes a little slower if you compile Ruby with --enable-shared. The same overhead applies to MJIT. Though it’s usually just a couple of cycles after the first call is made and dynamic resolution is finished. I <a href="https://github.com/k0kubun/ruby/compare/ee611341c9bc547e7d76fa3dd5c8f1452eb04d0f...eb30901c57743f5a5d04e19974247fbe605e4b05">also tested loading .o files</a> using <a href="https://github.com/shinh/objfcn">shinh/objfcn</a>, but compiling all methods in a single file and using dlopen weren’t slower than loading .o files.</p><h4>i-cache misses by compiling many methods?</h4><p>I’ve measured JIT-ed code with Linux perf so many times. I even wrote <a href="https://github.com/k0kubun/perf-profile">a perf plugin</a> for it. Through the investigation, I observed <a href="https://github.com/benchmark-driver/sinatra#stat-more">cycles stalled for filling i-cache</a>. Because the number of i-cache misses generally increases when you compile more methods, we changed the default of --jit-max-cache from 1000 to 100 in Ruby 2.7 to address the problem. And it indeed helped Rails performance. So I’ve believed JIT-ing a lot of methods is a bad thing.</p><p>However, Shopify’s YJIT, which compiles every method called more than once, <a href="https://www.youtube.com/watch?v=vucLAqv7qpc&amp;t=2780s">achieved the same performance as the interpreter</a> after compiling 4000 methods. Which, doesn’t really make sense if you assume the more methods you JIT, the slower the code runs.</p><p>What if we just compile all methods like YJIT? I tested it in older versions and it didn’t go well, which is why I haven’t tried it again until very recently. But in Ruby 3.0, I implemented an optimization that <a href="https://twitter.com/k0kubun/status/1256142302608650244">eliminates most of the code duplication across different methods and significantly reduces code size</a>. With that, could MJIT-generated code perform well just like YJIT?</p><h3>The “compile all” magic</h3><p>Yes, that’s what we needed. If you compile all benchmarked methods instead of just the top 100 methods, the JIT makes Rails faster.</p><p>See <a href="https://gist.github.com/k0kubun/60c5486a26960973f86fdf1c0e9cc120">this gist</a> for benchmark details. Note that some benchmarks are forked by me for reasons explained later. Following results are measured under a single-threaded condition and run at a Rack level.</p><h4>Sinatra Benchmark</h4><p>This benchmark was from an article saying <a href="https://www.rubyguides.com/2018/11/ruby-mjit/">&quot;enabling the JIT makes Sinatra 11% slower!&quot;</a>, which just returns a static string from Sinatra.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zPuLzI6R0YTJqjCC1qtimw.png" /></figure><p>Guess what? JIT makes it <strong>11% faster</strong> instead of slower now.</p><h4>Rails Simpler Bench</h4><p><a href="https://engineering.appfolio.com/appfolio-engineering/2019/4/11/a-simpler-rails-benchmark-puma-and-concurrency">Rails Simpler Bench</a> was authored by Noah Gibbs. The default benchmark endpoint returns a static text. So it&#39;s a Rails version of the above benchmark.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nlSl0fW-AKLdsBMFmOyggg.png" /></figure><p>JIT makes it <strong>1.04x faster</strong>. The difference is not as significant as Sinatra, but this is the first time we see the JIT makes Rails faster, not slower.</p><h4>Railsbench</h4><p>Railsbench measures the performance of HTML #show action of a controller generated by &quot;rails g scaffold&quot;. It was originally <a href="https://github.com/headius/pgrailsbench">headius/pgrailsbench</a> used by the JRuby team, I forked it as <a href="https://github.com/k0kubun/railsbench">k0kubun/railsbench</a> to upgrade Rails, and <a href="https://github.com/Shopify/yjit-bench/blob/main/benchmarks/railsbench/README.md">Shopify/yjit-bench</a> uses that too.</p><p>Despite its simplicity and benchmark convenience, it captures many real-world Rails characteristics. It uses ActiveRecord, for instance.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YrtMagJKLJrqa91AdFhOXA.png" /></figure><p>It&#39;s <strong>1.03x faster</strong>. Not significant, but it&#39;s <em>way</em> better than a slowdown, right?</p><h4>Discourse</h4><p>Discourse is one of the most popular open-source Rails applications in the world. You can see it at <a href="https://twittercommunity.com/">Twitter Developers</a>, <a href="https://github.community/">GitHub Support</a>, <a href="https://discuss.circleci.com/">CircleCI Discuss</a>, and <a href="https://www.discourse.org/customers">more</a>.</p><p>So it&#39;s pretty clear that Discourse is a real-world Ruby workload. The important thing in this context is that it has <a href="https://github.com/discourse/discourse/blob/v2.6.7/script/bench.rb">its own benchmark harness</a>. Noah Gibbs&#39;s <a href="https://github.com/noahgibbs/rails_ruby_bench">Rails Ruby Bench</a> is based on Discourse too.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*r04kiZrxNkv0wanSvptIQg.png" /></figure><p>JIT <em>can</em> make Discourse <strong>1.03x faster</strong>! One complication here, however, is that Discourse enables TracePoint and all JIT-ed code is canceled when TracePoint is enabled. Because Zeitwerk uses TracePoint, I needed to switch its autoloader back to the classic autoloader. So it&#39;s not ready to use the JIT for Discourse yet. But this proves the JIT is useful for real-world application logic.</p><h3>Why does compiling everything make it faster?</h3><p>To spot locations impacting metrics like i-cache misses, I&#39;ve tried tools like perf and cachegrind before, but because the slowness appears everywhere and no single place shows a significant difference, it&#39;s hard to tell the exact reason.</p><p>Let&#39;s take a look at how the number of compiled methods impacts the performance with Railsbench.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7dCdlPue_91yuCAwFbD4Cw.png" /></figure><p>The number of benchmarked methods was roughly 1200. When you have only VM interpretation, it&#39;s faster than most of the VM/JIT-mixed execution. But once you compile everything and use JIT-ed code most of the time, it actually becomes faster than the baseline.</p><p>What Maxime, the author of YJIT, thought was that <a href="https://youtu.be/vucLAqv7qpc?t=2526">thrashing between VM and JIT is the biggest issue</a> because they could evict each other from i-cache and less-predictable VM dispatch makes it harder to prefetch code. She also proved in her <a href="https://www.youtube.com/watch?v=vucLAqv7qpc&amp;t=2862s">30k_methods benchmark</a> that large code can run faster as long as you stay in linear predictable JIT-ed code. MJIT would share many of these characteristics too even while its code may not be as linear as YJIT&#39;s.</p><p>I think <a href="https://twitter.com/k0kubun/status/1256142302608650244">our significant code size reduction in Ruby 3.0</a> contributed to making this possible as well.</p><h3>So, should I use JIT on Rails?</h3><p>We&#39;re almost there, but we have some stuff to be fixed before bringing this to production.</p><h4>Ruby 3.0.1 bug that&#39;s not in 3.0.0</h4><p>One of the backports to Ruby 3.0.1 causes JIT compilation failures. 3.0.1 is significantly slower than 3.0.0 if you use JIT. Let&#39;s wait for the next release.</p><h4>Ruby 3 bug that stops compilation in the middle</h4><p>There&#39;s a bug that the JIT worker stops compiling the remaining methods in edge cases. You cannot reproduce the above speed if it happens. Please wait until <a href="https://github.com/ruby/ruby/commit/dfe21ef7a15a30fc33f3a5cc089e15d09e905eb4">this fix</a> is backported and released.</p><h4>Incompatibility with Zeitwerk / TracePoint</h4><p>In ruby 2.5, Koichi, the author of TracePoint, implemented <a href="https://bugs.ruby-lang.org/issues/14104">an optimization</a> that makes overall Ruby execution faster <em>as long as you never use TracePoint</em>. I&#39;ve thought you should not enable TracePoint if you care about performance. Thus MJIT has not supported TracePoint.</p><p>However, Byroot has found that <a href="https://github.com/fxn/zeitwerk/issues/135#issuecomment-688394208">it doesn&#39;t have a significant impact on VM performance</a> nowadays. So the problem is only associated with JIT. Given that Zeitwerk disables TracePoint after eager loading finishes, now I&#39;m thinking about re-enabling all JIT-ed code when all TracePoints get disabled.</p><h4>Incompatibility with GC.compact</h4><p>We either need to compromise performance by making every memory access indirect or recompile many methods whenever GC.compact is called. It has not been implemented, so all JIT-ed code is canceled for now.</p><h4>The default value of --jit-max-cache</h4><p>To compile everything, the default 100 of --jit-max-cache was too small. I&#39;ll change it to maybe 10000 in Ruby 3.1, but you&#39;d need to override the default for Ruby 3.0.</p><h4>Scalability of &quot;JIT compaction&quot;</h4><p>If you want to reproduce the above benchmark results, you have to see the &quot;JIT compaction&quot; log at the end of --jit-verbose=1 logs. However, because the compacted binary has all methods and we currently have no way to distinguish which version of JIT-ed code is in use, the compacted code is almost always not GC-ed properly. Because of that, we restricted the max number of &quot;JIT compaction&quot; to 10% of --jit-max-cache (10 times by default). If JIT continues to compile methods after the last JIT compaction, it&#39;ll be slow.</p><p>Also, it takes a lot of time before &quot;JIT compaction&quot; is triggered. See <a href="https://gist.github.com/k0kubun/60c5486a26960973f86fdf1c0e9cc120#file-ruby3_vs_others-md">this benchmark result</a> for how long MJIT takes to reach the peak performance. If you measure MJIT&#39;s performance while GCC is running or before &quot;JIT compaction&quot;, it would be often slower than the baseline.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SQWauRyik04ndShiEMz1OA.png" /></figure><p>I hope using MIR instead of GCC will shorten the warmup time in the future.</p><h3>Next steps</h3><p>I don&#39;t care which JIT backend we use to generate machine code out of MJIT, MIR, or YJIT. What I do care about the most is the performance in real-world workloads and optimization ideas implemented in the JIT compiler on top of the backend, which will continue to be useful even if you replace MJIT with MIR <em>and</em>/or YJIT.</p><p>In addition to approaching the problems discussed above, I&#39;m thinking about working on the following things, which were slightly changed from <a href="https://medium.com/@k0kubun/ruby-3-0-jit-and-beyond-4d9404ce33c">the previous article</a>.</p><h4>Ruby-based JIT compiler</h4><p>I considered &quot;Ractor-based JIT worker&quot; in the previous article because process invocation is something Ruby is good at and I thought doing that at a Ruby level might make it easier to maintain locks, in addition to using Ruby to easily maintain complicated optimization logic.</p><p>However, I found that running multiple Ractors has its own overhead unlike running only the default Ractor. So now I&#39;m thinking to continue to maintain the JIT worker in C, but starting and stopping a Ractor whenever the JIT compilation happens so that we won&#39;t see the multi-Ractor overhead once it reaches the peak performance. We&#39;d need to check if the overhead is insignificant compared to GCC&#39;s compilation time first though.</p><h4>Faster deoptimization</h4><p>One theme in the previous article was &quot;On-Stack Replacement&quot;, but I split it to &quot;Faster deoptimization&quot; and the next &quot;Lazy stack frame push&quot; this time. Ultimately, On-Stack Replacement is just one way to implement those optimizations, and whether we use OSR or not doesn&#39;t really matter.</p><p>One of the reasons why we don&#39;t support TracePoint and GC.compact was to avoid adding too many branches in the code for deoptimization. If there&#39;s no extra overhead for doing it, it&#39;ll be easier to introduce the support.</p><h4>Lazy stack frame push</h4><p>From Ruby 3.0, we started to annotate methods that don&#39;t need any stack frame to be pushed. The number of methods we can annotate as such is limited for now because object allocation might raise NoMemoryError and a backtrace needs the frame. If we can lazily push the frame when it&#39;s being raised, we can skip pushing the frame. It&#39;s tricky to implement that with MJIT, but it&#39;d be useful for reducing the method call overheads.</p><h3>Sponsors</h3><p>My contribution to OSS like the JIT, <a href="https://github.com/ruby/erb">ERB</a>, <a href="https://github.com/haml/haml">Haml</a>, and <a href="https://github.com/ruby/irb/pulls?q=author%3Ak0kubun">IRB</a> and my own projects like <a href="https://github.com/k0kubun/hamlit">Hamlit</a>, <a href="https://github.com/k0kubun/sqldef">sqldef</a>, <a href="https://github.com/k0kubun/pp">pp</a>, <a href="https://github.com/itamae-kitchen/mitamae">mitamae</a>, and <a href="https://github.com/k0kubun/rspec-openapi">rspec-openapi</a> has been supported by the following <a href="https://github.com/sponsors/k0kubun">GitHub Sponsors</a>. Thank you!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pNPN9ZjXZoMMVClJZJSS0A.png" /><figcaption><a href="https://github.com/sponsors/k0kubun">https://github.com/sponsors/k0kubun</a></figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=756310f235a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ruby 3.0 JIT and beyond]]></title>
            <link>https://k0kubun.medium.com/ruby-3-0-jit-and-beyond-4d9404ce33c?source=rss-c2f58218a6ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/4d9404ce33c</guid>
            <category><![CDATA[ruby]]></category>
            <dc:creator><![CDATA[k0kubun]]></dc:creator>
            <pubDate>Fri, 25 Dec 2020 09:49:48 GMT</pubDate>
            <atom:updated>2020-12-25T09:49:48.014Z</atom:updated>
            <content:encoded><![CDATA[<h3>TL;DR</h3><p>Ruby 3.0 JIT is the fastest JIT ever for MRI. However, despite Ruby 3.0&#39;s big improvement in reducing i-cache misses, it&#39;s still not ready for optimizing Rails applications. Stay tuned for Ruby 3.1.</p><h3>&quot;Is Ruby 3 Actually Three Times Faster?&quot;</h3><p>Ruby 3 is indeed 3x faster than Ruby 2.0 in <em>a</em> Ruby 3x3 benchmark, <a href="https://github.com/mame/optcarrot">Optcarrot</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NF36Wh0BPz-sbrAid3MXQg.png" /><figcaption><a href="https://www.ruby-lang.org/en/news/2020/12/25/ruby-3-0-0-released/">https://www.ruby-lang.org/en/news/2020/12/25/ruby-3-0-0-released/</a></figcaption></figure><p>As <a href="https://github.com/mame/optcarrot/pull/26">proposed by Eregon</a>, we measured performance after 3000 frames to measure peak time performance with Optcarrot.</p><p>Note that Ruby&#39;s JIT compiler methodology using a C compiler called &quot;MJIT&quot; was originally <a href="https://developers.redhat.com/blog/2018/03/22/ruby-3x3-performance-goal/">implemented by Vladimir Makarov</a>. The JIT of Ruby 2.6 / 2.7 was slower than his implementation because we <a href="https://medium.com/@k0kubun/the-method-jit-compiler-for-ruby-2-6-388ee0989c13">only partially merged his work and replaced the compiler to mitigate risks</a>.</p><p>I&#39;ve implemented lots of optimizations in Ruby 3, and finally it performs better than his original version. Ruby 3 JIT is the fastest JIT which MRI has ever had, which of course couldn&#39;t be achieved without his great work.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ukb27l_PDJficQBsg2_yNQ.png" /><figcaption>Measured at my local Linux x86-64 / GCC 9.3.0 environment</figcaption></figure><p>However, Optcarrot is just one of the two official Ruby 3 benchmarks. Please see <a href="http://codefol.io/posts/is-ruby-3-actually-three-times-faster/">Is Ruby 3 Actually Three Times Faster?</a> for details, which was written by Noah Gibbs, the author of the other benchmark <a href="https://github.com/noahgibbs/rails_ruby_bench">rails_ruby_bench</a>.</p><h3>Should I enable JIT on my Rails applications?</h3><p>Not yet.</p><p>Let me quote what I wrote in the <a href="https://www.ruby-lang.org/en/news/2020/12/25/ruby-3-0-0-released/">Ruby 3.0.0 release note</a>:</p><blockquote>As of Ruby 3.0, JIT is supposed to give performance improvements in limited workloads, such as games (<a href="https://benchmark-driver.github.io/benchmarks/optcarrot/commits.html#chart-1">Optcarrot</a>), AI (<a href="https://benchmark-driver.github.io/benchmarks/rubykon/commits.html">Rubykon</a>), or whatever application that spends majority of time in calling a few methods many times.</blockquote><blockquote>Although Ruby 3.0<a href="https://twitter.com/k0kubun/status/1256142302608650244"> significantly decreased a size of JIT-ed code</a>, it is still not ready for optimizing workloads like Rails, which often spend time on so many methods and therefore suffer from i-cache misses exacerbated by JIT. Stay tuned for Ruby 3.1 for further improvements on this issue.</blockquote><p>The JIT compiler generates native code and Ruby needs to run the JIT-ed code in addition to existing VM instructions. Apparently Rails uses more features than Optcarrot, e.g. Optcarrot doesn&#39;t trigger GC, and therefore a CPU&#39;s i-cache has little space for accommodating extra code.</p><p>As explained above, Ruby 3 <a href="https://twitter.com/k0kubun/status/1256142302608650244">decreased a generated code size for example from 1.3MB to 260KB in 100 methods</a> of <a href="https://github.com/benchmark-driver/sinatra">a Sinatra benchmark</a>, which improved performance of the Sinatra benchmark, but still it was not enough.</p><h3>Ruby 3.1 JIT</h3><p>I still haven&#39;t given up to optimize web applications made by Rails or whatever frameworks by JIT, because that&#39;s where many people and I use Ruby for production.</p><p>I know some people are working on a lightweight JIT compiler project, such as <a href="https://developers.redhat.com/blog/2020/01/20/mir-a-lightweight-jit-compiler-project/">MIR</a>. Even if we introduce such a lightweight JIT as another tier of MRI JIT, for the best performance of long running applications like Rails, a heavyweight JIT should generate ideal native code for any method.</p><p>Given Ruby 3.0, I have some battle plans for the Ruby 3.1 JIT development.</p><h4>Ractor-based JIT worker</h4><p>The MJIT worker thread has been implemented using C, because Ruby has GVL for threads and we didn&#39;t want to block Ruby threads by what MJIT does.</p><p>Now we have <a href="https://rubykaigi.org/2020-takeout/presentations/ko1.html">Ractor</a>. We can rewrite the MJIT worker implementation with Ractor to develop more complicated optimizations with Ruby. For example, if we annotate a method as side-effect free, we could call the method on compilation time to calculate the result beforehand, which we didn&#39;t want to do before because calling a Ruby method in an MJIT worker meant to acquire GVL.</p><h4>On-Stack Replacement</h4><p>On-Stack Replacement is a technique to replace code on stack. This is useful for eliminating various checks and branches in JIT-ed code because we can abandon too-optimized code <em>outside the optimized code</em>, for example when we redefine a method.</p><p>I know this is very hard to implement. It may be impossible as long as we use a C compiler for MJIT. Though I&#39;m very convinced we need it for optimizing Ruby 3 further. For instance, because MRI has debug_inspector API which allows users to fetch any local variable in any frame from any method, MJIT currently doesn&#39;t optimize Ruby&#39;s local variables at all. If we can lazily move values from a native stack to Ruby&#39;s local variables when debug_inspector API is called, we can optimize them.</p><h4>Reduce a code size further</h4><p>Once we start using Ruby and have On-Stack Replacement, we should be able to reduce a JIT-ed code size further. I believe this effort will allow us to optimize a wider range of applications like Rails.</p><p>I&#39;ll also look for VM implementations that touch many cache lines and try to reduce their code size.</p><h3>Sponsors</h3><p>I can&#39;t thank my wife enough for supporting my development of Ruby. Ruby 3x3 couldn&#39;t be achieved without her help.</p><p>Also <a href="https://github.com/sponsors/k0kubun">GitHub Sponsors</a>, thank you for sponsoring my OSS development! Your support keeps me motivated even more.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/598/1*qA3htTbPuwMbkgylJFV5sA.png" /><figcaption><a href="https://github.com/sponsors/k0kubun">https://github.com/sponsors/k0kubun</a></figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4d9404ce33c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JIT development progress at Ruby 2.7]]></title>
            <link>https://k0kubun.medium.com/jit-development-progress-at-ruby-2-7-d6dd62a8c76a?source=rss-c2f58218a6ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/d6dd62a8c76a</guid>
            <category><![CDATA[ruby]]></category>
            <dc:creator><![CDATA[k0kubun]]></dc:creator>
            <pubDate>Thu, 26 Dec 2019 08:46:54 GMT</pubDate>
            <atom:updated>2019-12-26T08:46:54.376Z</atom:updated>
            <content:encoded><![CDATA[<h3>TL;DR</h3><p>We focused to improve Rails application performance in Ruby 2.7 JIT, but <a href="https://medium.com/@k0kubun/ruby-2-6-jit-progress-and-future-84e0a830ecbf">the last year&#39;s assumption</a> was wrong and Ruby 2.7 JIT didn&#39;t meet the goal. We&#39;ll change the approach for it in Ruby 3.0.</p><h3>Ruby 2.7 is released!</h3><p>Merry Christmas! The new Ruby is out. As I&#39;ve sometimes promised to make performance improvements in Ruby 2.7 JIT, let me explain how it went. See also <a href="https://medium.com/@k0kubun/the-method-jit-compiler-for-ruby-2-6-388ee0989c13">The method JIT compiler for Ruby 2.6</a> and <a href="https://medium.com/@k0kubun/ruby-2-6-jit-progress-and-future-84e0a830ecbf">Ruby 2.6 JIT - Progress and Future</a> in case you&#39;re not familiar with the context.</p><p>Since my original motivation to work on JIT is to improve Rails application&#39;s performance, I focused to improve JIT on such a workload. I prepared some more benchmarks using Rack, Sinatra, Rails and experimented some patches I believed were effective for Rails with them.</p><p>However, none of them didn&#39;t have a major impact on Rails benchmarks. I particularly spent a lot of time for &quot;Optimized JIT-ed code dispatch&quot;, which will be explained later, as <a href="https://medium.com/@k0kubun/ruby-2-6-jit-progress-and-future-84e0a830ecbf">I thought it&#39;s very important last year</a>. Therefore I changed my plan to stop thinking about it and optimize code more, but it was too late and the time is over to improve the Rails benchmark in Ruby 2.7.</p><p>Now I&#39;m hoping to improve things in Ruby 3.0. To share the current progress towards that, let me summarize what I was doing in Ruby 2.7 development.</p><h3>What&#39;s implemented in Ruby 2.7?</h3><h4>Default option changes</h4><p>The easiest change, but maybe the largest impact on some benchmarks. Here&#39;s what&#39;s changed:</p><ul><li>--jit-max-cache: 1,000 → 100</li><li>--jit-min-calls: 5 → 10,000</li></ul><p>--jit-max-cache limits the number of methods to be JIT-ed. This is related to later &quot;Optimized JIT-ed code dispatch&quot; topic, because having a lot of JIT-ed code tends to increase the JIT-ed code dispatch overhead and therefore it impacts long-running application&#39;s performance a lot.</p><p>--jit-min-calls requires methods to be called such times before letting JIT worker consider compiling it. Because running a C compiler has a very big performance impact, Ruby&#39;s JIT is performing badly in most of existing Ruby benchmarks just for the C compiler&#39;s resource consumption. While I&#39;m focusing on performance after all compilations, I thought changing this value has some easy benefits on such situations, at least skipping methods called only on startup.</p><p>These values are kind of influenced by OpenJDK&#39;s C2 compiler options.</p><h4>Deoptimized recompilation</h4><p><a href="https://speakerdeck.com/k0kubun/rubyrussia-2019?slide=44">https://speakerdeck.com/k0kubun/rubyrussia-2019?slide=44</a></p><p>JIT has its own deoptimization mechanism. When some assumption like &quot;this JIT-ed method is used only for this class&quot; is not met, JIT-ed code fallbacks to VM interpretation because it&#39;s optimized using such an assumption.</p><p>While Ruby 2.6 continues to make the fallback forever, Ruby 2.7&#39;s JIT marks such methods to be recompiled and it generates less-optimized code which doesn&#39;t fallback to VM for the same assumption.</p><p>This was built in <a href="https://bugs.ruby-lang.org/issues/12589">the original MJIT</a> which was not merged in Ruby 2.6. In Ruby 2.7 development, we thought it&#39;s valuable for reducing the number of VM fallbacks, especially because Rails has many polymorphic class usages.</p><h4>Frame-omitted method inlining</h4><p><a href="https://speakerdeck.com/k0kubun/rubyrussia-2019?slide=56">https://speakerdeck.com/k0kubun/rubyrussia-2019?slide=56</a></p><p>When we call a method, we push a frame to a call stack, evaluate the method, and pop the frame. The push and the pop of the frame has some overhead, especially for very fast operations like Integer#+. Therefore eliminating such frame manipulation has been implemented in VM level for some popular methods.</p><p>This &quot;frame-omitted&quot; method inlining introduces the same optimization for various more methods by JIT. For doing this, we need to know an inlined method has no chance to rely on the call stack. The CRuby&#39;s VM developers and I have developed VM instruction&#39;s attributes to examine such kind of things on methods and JIT is using the mechanism.</p><p>However, not so many methods are considered as frame-omittable for now. While we indeed spent time for making some instructions &quot;pure&quot; (no side effect) in Ruby 2.7 which is used for the optimization, we didn&#39;t make the optimization available for methods with a normal level of complexity. There are some rooms to be improved here for now.</p><h3>What&#39;s not committed in Ruby 2.7?</h3><h4>Optimized JIT-ed code dispatch</h4><p><a href="https://speakerdeck.com/k0kubun/rubyrussia-2019?slide=64">https://speakerdeck.com/k0kubun/rubyrussia-2019?slide=64</a></p><p>This <em>was</em> the most promising thing to improve Rails performance on JIT. As mentioned in the --jit-max-cache explanation, it is the fact that a JIT-ed method dispatch becomes slow when there are many JIT-ed methods. Because I&#39;ve benchmarked Rails with all methods compiled (up to --jit-max-cache), I thought JIT-ed code dispatch can be a bottleneck in the benchmark.</p><p>I managed to make a patch which <a href="https://speakerdeck.com/k0kubun/rubyrussia-2019?slide=73">offsets JIT-ed code dispatch overhead up to 100 methods</a>, but in reality it didn&#39;t improve JIT&#39;s performance on Rails benchmark. So the bottleneck seems to live in another place.</p><p>Given that, I stopped working on eliminating low-level overhead. I changed my plan in Ruby 3.0 to just focus on optimizing compiled code instead.</p><h4>Method inlining for more methods</h4><p>The most important thing missing in the above &quot;frame-omitted method inlining&quot; now is that a method is not inlined if the method has local variables.</p><p>This is not super hard to solve, but I prioritized &quot;Optimized JIT-ed code dispatch&quot; after implementing the method inlining capability and making some important instructions pure.</p><p>Another important topic is writing core-class methods in Ruby. For example, if we define Numeric#zero? as `self == 0`, the JIT can consider the method as pure and it can perform frame-omitted method inlining on it, which can be a resolution for <a href="https://bugs.ruby-lang.org/issues/15589">[Bug #15589]</a>.</p><p>Because there was <a href="https://rubykaigi.org/2019/presentations/ko1.html">&quot;Write a Ruby interpreter in Ruby for Ruby 3&quot; project</a> by ko1, I was just waiting for his work. But it turned out that the current form of his work is basically inlining C code in Ruby code and therefore the JIT is blocked to inline such methods because it cannot examine whether C code is pure or not.</p><p>We&#39;re planning to mark C code with attributes like &quot;pure&quot; to make that happen. I thought it was for Ruby 2.7, but it just wasn&#39;t.</p><h4>Stack-based object allocation</h4><p><a href="https://speakerdeck.com/k0kubun/rubyrussia-2019?slide=74">https://speakerdeck.com/k0kubun/rubyrussia-2019?slide=74</a></p><p>The optimization, allocating objects on stack instead of heap, is known to require a hard implementation, escape analysis. Though I thought it&#39;s possible to implement it in some very basic cases and I made an experimental implementation.</p><p>But even in such a very easy case, we need to know whether a method&#39;s receiver or arguments escapes or not, and this was blocked by the C code attributes work by ko1. I could have implemented a tentative version of it dedicated for JIT, but my time was spent for the &quot;Optimized JIT-ed code dispatch&quot; project and ko1 was working on many other things.</p><h3>What will k0kubun do in Ruby 3.0?</h3><p>I still don&#39;t think that the JIT can&#39;t optimize Rails. While it&#39;d be important to achieve Ruby 3x3 in Optcarrot benchmark for some workloads, I&#39;ll continue to work on JIT in Ruby 3.0 to avoid slowing down Rails and optimize such a workload further.</p><p>Instead of focusing on the &quot;Optimized JIT-ed code dispatch&quot; project, I&#39;ll try to introduce optimizations which work well on Rails benchmarks, with the following strategies:</p><h4>More per-instruction optimizations</h4><p>You know more sophisticated JITs like HotSpot&#39;s C2 compiler or Graal are doing a very complicated task on optimizations. They&#39;d be doing optimization over multiple instructions or methods. At some point Ruby&#39;s JIT should somehow have the thing to have the same level of optimization.</p><p>However, even in a single instruction level, the current implementation of Ruby JIT has a special optimization only in some very limited instructions. It optimizes a normal method call and instruction variables, but most of others are untouched and not doing per-instruction optimization, while C compilers can perform multi-instruction optimizations for some limited extent.</p><p>Once per-instruction optimizations are done well, we&#39;ll have more chance that C compilers can perform multi-instruction optimizations. Even in C code manipulated by JIT, it&#39;d be harder to generate a simple code if per-instruction code were not already simplified.</p><p>The things I&#39;m currently planning to try are:</p><ul><li>Revisit type-profiling instruction operands and generate type-specific code; <a href="https://github.com/k0kubun/ruby/pull/11">The last attempt</a> did not went well maybe because it increases the number of VM instructions. Maybe we&#39;d be able to profile the same thing without having many instructions.</li><li>Instance variable optimization for core-classes and their subclasses (VM-level preparation is done in Ruby 2.7, but JIT-level support is not done yet)</li><li>Optimize getconstant; We haven’t touched this in JIT yet and maybe we can do something. To make constant folding happen in the future, we should be able to optimize constant references in per-instruction level first.</li></ul><h4>More inlining</h4><p>Some more places are theoretically inlinable but not implemented to be inlined yet. Example things are:</p><ul><li>`super`; We attempted to implement VM-level method cache (prerequisite for JIT-level inlining) in Ruby 2.7, but we got a bug and abandoned it. We should debug the bug and introduce it again.</li><li>C methods; Because it&#39;s hard to just rewrite C methods to Ruby methods without degrading the method&#39;s performance on VM, we&#39;d need to continue to maintain things in C. Then we should consider supporting method inlining for C methods too, at least for methods used very often.</li><li>Inlining Ruby methods with local variables; Explained above. It&#39;s just waiting to be implemented.</li></ul><p>Not only inlining unblocks C compiler&#39;s optimizations, but also specializing code for inlining itself sometimes gives faster code because some values will be inlined in generated code instead of read from memory.</p><h4>More C method&#39;s metadata</h4><p>Things like allocating objects on stack and omitting frames for inlined C method would be blocked by the inability to know the behavior of C code. To unblock these optimizations, Ruby 3.0 should have a way to identify C method&#39;s behavior automatically.</p><p>As the first step, I&#39;d like to have following attributes in C methods:</p><ul><li>pure (a.k.a. &quot;leaf&quot; in YARV instructions); It doesn&#39;t call another arbitrary method.</li><li>noescape; We&#39;d like to know whether a specific receiver or argument is not escaped by calling a C method.</li></ul><p>Just adding the attributes is easy, but to avoid providing false information, we need to somehow automatically verify the information. @shyouhei did a good job for verifying &quot;leaf&quot; attribute in VM instructions on CI, and I hope we can do a similar thing in these new attributes too.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d6dd62a8c76a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ruby 2.6 JIT - Progress and Future]]></title>
            <link>https://k0kubun.medium.com/ruby-2-6-jit-progress-and-future-84e0a830ecbf?source=rss-c2f58218a6ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/84e0a830ecbf</guid>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[k0kubun]]></dc:creator>
            <pubDate>Wed, 19 Dec 2018 16:45:48 GMT</pubDate>
            <atom:updated>2018-12-20T00:15:56.702Z</atom:updated>
            <content:encoded><![CDATA[<p>In early 2018, Ruby core team merged JIT compiler infrastructure called “MJIT”, which is created by Vladimir Makarov and uses C compiler to generate native code and loads a shared object file dynamically, along with my JIT compiler that is implemented on MJIT and Ruby 2.5-compatible Virtual Machine.</p><p><a href="https://medium.com/@k0kubun/the-method-jit-compiler-for-ruby-2-6-388ee0989c13">The method JIT compiler for Ruby 2.6</a></p><p>Fortunately my effort has not been reverted, so it will be officially released next week!</p><p>Over 10 months, Ruby’s JIT has evolved for the following focuses:</p><ul><li>Performance</li><li>Portability</li><li>Stability</li><li>Security</li></ul><p>Let me show you what we’ve achieved for each of them in Ruby 2.6.</p><h3>Performance</h3><p>At least for Ruby, JIT is introduced to improve performance, specifically for speed in my understanding. So, if it doesn’t make your application fast, it would be useless for you, right? Given the whole year, I believed I would be able to achieve small but meaningful performance improvement on real-world applications in 2.6. However, I failed to make it.</p><p>In an early stage of 2.6, some micro benchmarks have had <a href="https://gist.github.com/k0kubun/7a1145f8391fbc3c076052d6f40d9e8e">the good progress</a>.</p><pre>Comparison:<br>                    while<br>         trunk+JIT: 493.8 i/s<br>2.6.0-preview1+JIT: 246.4 i/s - 2.00x  slower<br>    2.6.0-preview1:  86.8 i/s - 5.69x  slower<br>             trunk:  86.2 i/s - 5.73x  slower<br>             2.5.0:  80.9 i/s - 6.11x  slower<br>             2.0.0:  78.4 i/s - 6.30x  slower</pre><p>And until 2.6.0-preview3, <a href="https://gist.github.com/k0kubun/8a553cb832b8995d95f0d7fc4f7c6301">we&#39;ve continued to improve</a> the <a href="https://github.com/mame/optcarrot">Optcarrot</a> benchmark to measure Ruby 3x3 project, which hopes to let Ruby 3 be 3 times faster than Ruby 2.0.</p><pre>Comparison:<br>             Optcarrot Lan_Master.nes<br>      2.6.0-preview3+JIT:        86.6 fps<br>      2.6.0-preview2+JIT:        73.9 fps - 1.17x  slower<br>      2.6.0-preview1+JIT:        59.2 fps - 1.46x  slower<br>          2.6.0-preview3:        54.6 fps - 1.59x  slower<br>          2.6.0-preview2:        53.3 fps - 1.62x  slower<br>          2.6.0-preview1:        53.0 fps - 1.63x  slower<br>                   2.5.3:        48.5 fps - 1.78x  slower<br>                   2.0.0:        34.6 fps - 2.50x  slower</pre><p>This looks very great, and this is gonna be released as is in Ruby 2.6. It doesn&#39;t look so bad.</p><p>Guess what? It turned out that the NES emulator Optcarrot does not reflect a typical real-world workload in Ruby like Rails application so much. In Optcarrot, 2~3 methods which can be JIT-ed in a very short time are called very frequently, and the most busy method is large enough to be optimized well with MJIT&#39;s current optimization strategy.</p><p>In a typical Rails application, there&#39;s no single busy CPU-intensive method that can be optimized well by current MJIT. Noah Gibbs has written articles about MJIT&#39;s performance on Rails, and <a href="http://engineering.appfolio.com/appfolio-engineering/2018/12/13/a-short-update-how-fast-is-ruby-260rc1">the latest article</a> showed the current JIT does not make Rails fast.</p><p>After preview3, I called for Ruby benchmarks that are made slow by JIT:</p><h3>k0kubun on Twitter</h3><p>People, please give me Ruby benchmark which is made slower by passing --jit option on Ruby 2.6.0-preview3. That&#39;s super helpful at this moment because we still have 1~2 months to fix it before the 2.6.0 release. Examples (thanks!): https://t.co/HZD3JMLpdF https://t.co/EmFMIyS7RY</p><p>I collected them to experiment an easy fix that can be done within 1 month prior to the final 2.6 release, but I seem to have underestimated the difficulty of it, time flies, and I suddenly realized that this can&#39;t be fixed by a small change that can be rushed after 2.6.0-rcX releases.</p><p>Therefore, none of them hasn&#39;t been improved yet and apparently JIT on Ruby 2.6 failed to be ready for production. The only improvements we made in rc1 and rc2 were MJIT support in forked child processes and a bug fix for JIT on bootsnap.</p><p>So, it succeeded to be an interesting toy to experiment possible great future, but I failed to provide meaningful performance improvements as of Ruby 2.6. (Skip to the last &quot;Future&quot; section if you&#39;re interested in only performance.)</p><h3>Portability</h3><p>As written in the release note of Ruby 2.6-rc2, it aims to support JIT when it&#39;s built with following C compilers on non-EOL platforms.</p><ul><li>GCC</li><li>Clang</li><li>Microsoft Visual C++</li></ul><p>When MJIT infrastructure is published by Vladimir Makarov, it had support for only GCC or Clang running on Unix environments, and it actually had some issues on non-Linux platforms like macOS or Windows MinGW.</p><p>As of <a href="https://medium.com/@k0kubun/the-method-jit-compiler-for-ruby-2-6-388ee0989c13#e708">my previous article</a>, JIT on macOS was running properly, but we just passed builds on some other <a href="https://rubyci.org/">RubyCI</a> environments or skipped test failures. MinGW had a fatal SEGV which had been hard to fix.</p><p>With a <strong>lot</strong> of investment on debugging Windows environments, I&#39;m pleased to say that MJIT infrastructure is working fine on MinGW and Microsoft Visual C++. Both of them had its own difficult challenges. For example, we&#39;re relying on pre-processor-only mode of GCC and Clang but VC++ doesn&#39;t have that, then I ended up creating completely different build system. Who cares?</p><p>Helped by Nobuyoshi Nakada and Usa Nakamura, now MJIT is running for the above compilers at least on <a href="https://rubyci.org/">platforms listed in RubyCI</a>.</p><p>Another good news is that now Heroku supports MJIT. Thank you so much for the effort on it!</p><h3>Richard Schneeman 🤠 Beto for Senate 2020 on Twitter</h3><p>Ruby 2.6.0 JIT now works on @heroku thanks to GCC being added to the stack image https://t.co/oAgO50lOGL ~ $ ruby --jit -e &quot;RubyVM::MJIT.pause(wait: false)&quot; ; echo $? 0</p><h3>Stability</h3><p>We&#39;ve invested testing environment of MJIT as well. To prevent bugs, we have:</p><ul><li><a href="http://ci.rvm.jp/results/trunk-mjit-wait@silicon-docker">http://ci.rvm.jp/results/trunk-mjit-wait@silicon-docker</a></li><li><a href="http://ci.rvm.jp/results/trunk-mjit@silicon-docker">http://ci.rvm.jp/results/trunk-mjit@silicon-docker</a></li><li><a href="http://ci.rvm.jp/results/trunk-no-mjit@silicon-docker">http://ci.rvm.jp/results/trunk-no-mjit@silicon-docker</a></li><li><a href="https://app.wercker.com/ruby/ruby/runs">https://app.wercker.com/ruby/ruby/runs</a></li></ul><p>ci.rvm.jp is a continuous testing environment maintained by Sasada Koichi. It&#39;s running for 24 hours even while any commit is not made. It has been revealed bugs which rarely occur.</p><p>On that, we have 3 types of MJIT-related builds. trunk-mjit-wait is for running all tests with &quot;--jit-wait&quot;, which blocks when a method is queued to be JIT-ed and synchronously waits for JIT compilation, so that it exposes VM or JIT compiler-related bugs. trunk-mjit is running with &quot;--jit&quot; because synchronous compilation can&#39;t test SEGV or deadlocks on race condition. trunk-no-mjit is for testing configure flag --disable-mjit-support.</p><p>We also configured Wercker CI, which is dedicated for MJIT testing at this repository. It&#39;s running both --jit and --jit-wait testing. It&#39;s integrated with GitHub, and I think it&#39;s convenient for those who mainly watch GitHub for Ruby core development, including me.</p><p>These CI environments have reported many bugs which were not easy to fix. Have you ever considered what if Ruby script waits all child processes including MJIT’s C compiler process? Can you imagine what could happen on multi-threaded environment without GVL (for MJIT worker thread) that forks and execs on each thread and needs to deal with asynchronous signal handler? I can&#39;t count how many deadlocks, SEGVs, and esoteric test failures we&#39;ve fixed. But now it&#39;s stable enough at least on the above CI environments, with a large help from Eric Wong.</p><p>We created the following continuous benchmark environments as well so that we can prevent unexpected performance regression:</p><ul><li><a href="https://rubybench.org/">https://rubybench.org/</a></li><li><a href="https://benchmark-driver.github.io/">https://benchmark-driver.github.io/</a></li></ul><p>RubyBench has existed for a long time. This year original RubyBench developers added a support for measuring --jit, and I joined the team and maintained the --jit testing and benchmark driver part.</p><p>benchmark-driver.github.io is originally created for testing my benchmark tool benchmark_driver.gem at <a href="https://www.ruby.or.jp/ja/news/20171206">RubyGrant 2017</a>, but it now exists for covering more test cases along with RubyBench. Benchmarking is hard because we should use the same machine without any parallelism forever and increasing the number of test cases in one benchmark system could require a lot of time for measuring one commit.</p><p>I succeeded to catch some unexpected performance regressions of MJIT at <a href="https://benchmark-driver.github.io/benchmarks/optcarrot/commits.html">https://benchmark-driver.github.io/benchmarks/optcarrot/commits.html</a>, and sometimes it contributed to expose random failure on a specific benchmark program.</p><h3>Security</h3><p>Ruby’s JIT compiler translates Ruby method to C code on /tmp, invokes C compiler with it, and dynamically loads the generated object file. Doesn’t it sound like dangerous?</p><p>We can’t make everything safe. For example, if attackers already have access to root user, they could do almost anything. So we want the JIT to be <em>somewhat reasonably secure</em>.</p><p>As a user, I would like to be able to prevent at least the following situations:</p><ol><li>C compiler is replaced to an arbitrary binary</li><li>C header files or libraries are replaced</li><li>Another user’s program on the same server overwrites C code to be JIT-ed</li></ol><p>For 1, Ruby binary remembers the full path of C compiler which is used to build Ruby interpreter, and Ruby’s JIT uses it on runtime. If C compiler is securely installed, unless an attacker has access to replace the original C compiler or somehow can introduce unexpected chroot on start, it would be hard to spoil it.</p><p>For 2, Ruby’s JIT uses only one header file in Ruby’s install directory and just links to usual libraries like ones used for normal C extensions. An attacker may need to access to Ruby’s install directory or C libraries to break it.</p><p>For 3, we create C source file with O_EXCL|O_CREAT to prevent any other program from changing the permission by creating a file with the same name beforehand. This part has been fine since I wrote <a href="https://medium.com/@k0kubun/the-method-jit-compiler-for-ruby-2-6-388ee0989c13#0fce">my previous article</a>.</p><p>Actually these parts were mostly improved by Nobuyoshi Nakada after the initial merge. Kudos to <a href="https://github.com/nobu">@nobu</a> for a lot of improvements on build system!</p><h3>Future</h3><p>While I failed to make Ruby&#39;s JIT production-ready in 2.6, I don&#39;t regret what we&#39;ve done this year. I needed to develop or lead all of the above things for Ruby 2.6, but I believe I can invest my time mainly on performance improvement in Ruby 2.7.</p><p>I assume the main reason of slow down on real-world applications is that the overhead of loading JIT-ed code and increasing the times to invoke new Virtual Machine over JIT-ed frame becomes larger than what we can optimize now.</p><p>I&#39;m thinking about taking following 2 strategies to approach this:</p><ul><li>Inline Ruby methods as further as possible to avoid loading code or creating a VM frame many times</li><li>Optimize object allocations which we&#39;ve not invested so much in JIT so far</li></ul><p>The former is relatively easy for me because I experimented method inlining and have a patch for 1-level inlining. I just need to change it to support multi-level inlining. I&#39;m also planning to take time to learn other JIT implementation&#39;s strategy to decide what kind of unit to compile.</p><p>The latter might require to implement escape analysis for allocating objects on stack, which is known to be difficult to implement. But I&#39;m sure that there&#39;s a room to improve in it and real-world applications allocate more objects than Optcarrot.</p><p>Also there may be an ongoing experiment for MRI JIT by another person and it might be published for Ruby 2.7 or 3.0.</p><p>I hope that we&#39;ll be able to say &quot;MJIT makes Rails X times faster&quot; in near future, until we hit version 3.0.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=84e0a830ecbf" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Benchmark Driver Designed for Ruby 3x3]]></title>
            <link>https://k0kubun.medium.com/benchmark-driver-designed-for-ruby-3x3-6a5eeb1d1494?source=rss-c2f58218a6ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/6a5eeb1d1494</guid>
            <category><![CDATA[ruby]]></category>
            <dc:creator><![CDATA[k0kubun]]></dc:creator>
            <pubDate>Tue, 27 Mar 2018 15:49:01 GMT</pubDate>
            <atom:updated>2018-03-28T00:52:26.074Z</atom:updated>
            <content:encoded><![CDATA[<p>My benchmark tool development was accepted as a <a href="http://www.ruby.or.jp/en/news/20171206">Ruby Association Grant 2017</a> project and completed on March 21st. I’ll show you the details of the project.</p><p><strong>benchmark_driver.gem&#39;s example usage that outputs a graph:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*O0XDYM1y6poWxZh2H-QJVw.png" /></figure><h3>Project Summary</h3><p>Here is a copy of the project summary which I sent to Ruby Association:</p><blockquote>This project aims to improve benchmark_driver.gem, which is built to compare performance of different Ruby binaries easily and precisely. It also aims to increase the number of benchmark test cases to cover more Ruby core features, and make it easier to optimize Ruby 3x faster by preparing an environment to continuously benchmark the tests with the tool.</blockquote><p>I also created some detailed milestones for it and achieved all of them during the project period.</p><h3>What’s benchmark_driver.gem?</h3><p>benchmark_driver.gem is originally designed as a successor of <a href="https://github.com/ruby/ruby/blob/trunk/benchmark/driver.rb">benchmark/driver.rb</a> in Ruby language repository, which can measure a Ruby script performance accurately by subtracting time taken for while loop from a looped script to measure, and which can benchmark multiple Ruby binaries at the same time and compare them.</p><p>While the original benchmark/driver.rb hard-codes the loop count of while loop to be subtracted for some specific benchmark name prefixes, benchmark_driver.gem has the count as <em>loop_count</em> by design and thus you can adjust the loop count as you like. It can also automatically calculate the loop count which is expected to run for 3s (changeable by <em>--run-duratio</em>n option).</p><p>One great part of benchmark_driver.gem is a variety of built-in output formats and measurable metrics, and extensibility of them.<br>benchmark_driver.gem not only can measure execution time, i/s and memory, but also can integrate custom metrics of existing Ruby benchmark<br>like <a href="https://github.com/mame/optcarrot">Optcarrot</a><a href="https://github.com/mame/optcarrot)&#39;s">&#39;s</a> fps. And you can output the results in a sexy comparison output similar to benchmark-ips, markdown or a graph image. Both metrics and outputs are fully pluggable.</p><h4>Basic usage: Ruby interface</h4><p>benchmark_driver.gem has 2 benchmark definition formats: Ruby and YAML.<br>Ruby interface is designed for a usual Ruby benchmark or a use case to generate a benchmark target script dynamically, while YAML format is good for minimizing effort to write benchmark definitions and for flexibly changing parameters on the fly without modifying code.</p><p>Let’s see the difference between an ordinary benchmark tool and benchmark_driver.gem.</p><p>This is a benchmark example with benchmark-ips.gem:</p><pre>require &#39;benchmark/ips&#39;</pre><pre>class Array<br>  alias_method :blank?, :empty?<br>end</pre><pre>Benchmark.ips do |x|<br>  array = []<br>  x.report(&#39;Array#empty?&#39;) { array.empty? }<br>  x.report(&#39;Array#blank?&#39;) { array.blank? }<br>  x.compare!<br>end</pre><p>With Ruby 2.5, It outputs a result like:</p><pre>Warming up --------------------------------------<br>        Array#empty?   524.659k i/100ms<br>        Array#blank?   495.794k i/100ms<br>Calculating -------------------------------------<br>        Array#empty?     15.497M (± 2.5%) i/s -     77.650M in   5.013969s<br>        Array#blank?     14.171M (± 2.1%) i/s -     70.899M in   5.005282s</pre><pre>Comparison:<br>        Array#empty?: 15497274.4 i/s<br>        Array#blank?: 14171303.0 i/s - 1.09x  slower</pre><p>From the result, you may understand ActiveSupport’s `Array#blank?` is not so slow compared to Ruby’s built-in `Array#empty?`. Let’s try measuring the same thing in benchmark_driver.gem.</p><p>Here is the example of benchmark_driver.gem usage with Ruby interface:</p><pre>require &#39;benchmark_driver&#39;</pre><pre>Benchmark.driver do |x|<br>  x.prelude %{<br>    class Array<br>      alias_method :blank?, :empty?<br>    end<br>    array = []<br>  }<br>  x.report &#39;Array#empty?&#39;, %{ array.empty? }<br>  x.report &#39;Array#blank?&#39;, %{ array.blank? }<br>end</pre><p>When you run the above script with Ruby 2.5.0 (on Linux to see clocks/i), you will get an output like:</p><pre>Warming up --------------------------------------<br>        Array#empty?    56.340M i/s<br>        Array#blank?    42.795M i/s<br>Calculating -------------------------------------<br>        Array#empty?   181.135M i/s -    169.019M times in 0.933111s (5.52ns/i, 23clocks/i)<br>        Array#blank?    99.275M i/s -    128.386M times in 1.293235s (10.07ns/i, 44clocks/i)</pre><pre>Comparison:<br>        Array#empty?: 181134991.8 i/s<br>        Array#blank?:  99275008.9 i/s - 1.82x  slower</pre><p>So this result shows `Array#empty?` is actually about 1.8x faster than `Array#blank?`. The result is reasonable because Ruby’s optimized instruction for `Array#empty?` (opt_empty_p) is applied only if method name is &quot;empty?&quot;.</p><p>Why does this result difference happen? There are 2 reasons:</p><ul><li>Overhead of calling a block has large overhead, compared to just running a part of while loop. benchmark_driver.gem takes a benchmark definition as string to dynamically generate such a loop script, instead of taking a script as a block.</li><li>As I said before, it subtracts while loop overhead.</li></ul><p>You may claim that benchmark-ips.gem also takes a string instead of a block for a measured script, which actually is exactly the same interface as benchmark_driver.gem’s `x.report`, but it doesn’t have `x.prelude` and so you can’t have a predefined local variable. I assume that it’s not designed for such usage.</p><p>Like `Array#empty?`, there are some methods which can run even faster than the overhead of calling a block. As such methods are optimized because they are frequently used, it’s important to measure performance of such methods accurately.</p><h4>Comparing multiple Ruby binaries</h4><p>Another reason why benchmark_driver.gem is good for measuring Ruby’s performance is that it can run any Ruby binaries at the same time and compare the benchmark results.</p><p>Here is the example to compare the performance between multiple Ruby implementations. In `x.rbenv`, you can specify Ruby binaries managed by rbenv as &quot;[shown name]::[rbenv name],[arg1],[arg2]…&quot;.</p><pre>require &#39;benchmark_driver&#39;</pre><pre>Benchmark.driver do |x|<br>  x.prelude %{<br>    def script<br>      i = 0<br>      while i &lt; 1000_000<br>        i += 1<br>      end<br>      i<br>    end<br>  }<br>  x.report &#39;while&#39;, %{ script }<br>  x.loop_count 2000</pre><pre>  x.rbenv(<br>    &#39;2.0.0::2.0.0-p0&#39;,<br>    &#39;2.5.0&#39;,<br>    &#39;2.6.0-dev&#39;,<br>    &#39;2.6.0-dev+JIT::2.6.0-dev,--jit&#39;,<br>  )<br>  x.verbose<br>end</pre><p>And here is the output:</p><pre>2.0.0: ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-linux]<br>2.5.0: ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-linux]<br>2.6.0-dev: ruby 2.6.0dev (2018-03-21 trunk 62870) [x86_64-linux]<br>2.6.0-dev+JIT: ruby 2.6.0dev (2018-03-21 trunk 62870) +JIT [x86_64-linux]<br>Calculating -------------------------------------<br>                          2.0.0       2.5.0   2.6.0-dev  2.6.0-dev+JIT<br>               while     77.952      80.325      87.239        491.907 i/s -      2.000k times in 25.656691s 24.898879s 22.925498s 4.065807s</pre><pre>Comparison:<br>                            while<br>       2.6.0-dev+JIT:       491.9 i/s<br>           2.6.0-dev:        87.2 i/s - 5.64x  slower<br>               2.5.0:        80.3 i/s - 6.12x  slower<br>               2.0.0:        78.0 i/s - 6.31x  slower</pre><p>Obviously this tool is convenient to know: what times is the current Ruby faster than Ruby 2.0? Please try comparing performance with JRuby, Rubinius and truffleruby.</p><p>Not only plain text output like this, but also you can visualize this as a graph using a plugin (explained later).</p><h3>Advanced usage: YAML and CLI</h3><p>benchmark_driver.gem has CLI like this (descriptions are omitted because the width of medium is too small...):</p><pre>$ benchmark-driver -h<br>Usage: benchmark-driver [options] [YAML]<br>    -r, --runner [TYPE]              <br>    -o, --output [TYPE]              <br>    -e, --executables [EXECS]        <br>        --rbenv [VERSIONS]           <br>        --repeat-count [NUM]         <br>        --bundler                    <br>        --filter [REGEXP]            <br>        --verbose [LEVEL]            <br>        --run-duration [SECONDS]     </pre><p>It takes YAMLs as its arguments.</p><p>Here is an example of a benchmark definition in YAML.</p><pre>prelude: |<br>  large_a = &quot;Hellooooooooooooooooooooooooooooooooooooooooooooooooooo&quot;<br>  large_b = &quot;Wooooooooooooooooooooooooooooooooooooooooooooooooooorld&quot;<br>  small_a = &quot;Hello&quot;<br>  small_b = &quot;World&quot;</pre><pre>benchmark:<br>  large: &#39;&quot;#{large_a}, #{large_b}!&quot;&#39;<br>  small: &#39;&quot;#{small_a}, #{small_b}!&quot;&#39;</pre><p>If you save it as “benchmark.yml”, you can run it like:</p><pre>$ benchmark-driver benchmark.yml --rbenv &#39;2.4.3;2.5.0&#39;<br>Warming up --------------------------------------<br>               large     3.693M i/s<br>               small     9.913M i/s<br>Calculating -------------------------------------<br>                          2.4.3       2.5.0<br>               large     3.895M      5.485M i/s -     11.079M times in 2.844249s 2.019943s<br>               small    11.755M     11.103M i/s -     29.740M times in 2.529966s 2.678612s</pre><pre>Comparison:<br>                            large<br>               2.5.0:   5484705.8 i/s<br>               2.4.3:   3895155.8 i/s - 1.41x  slower</pre><pre>small<br>               2.4.3:  11755264.9 i/s<br>               2.5.0:  11102923.0 i/s - 1.06x  slower</pre><p>This shows characteristics of Ruby 2.5’s string interpolation performance improvement.</p><p>In YAML output, you can simplify the definition by its format, and you can specify Ruby executable without modifying code. Please use either of them depending on your use case.</p><h3>Plugin System</h3><p>You can customize metrics to be measured and a format for output as you like,<br>and build your own versions if you want.</p><p>The plugin interface has evolved dramatically and it’s not so stable yet,<br>which is the only reason I don’t release it as 1.x, but you&#39;ll be able to continue using the same interface unless you&#39;re a plugin developer.</p><p>To know what plugin is available, refer to benchmark_driver&#39;s README: <a href="https://github.com/k0kubun/benchmark_driver#output-options">https://github.com/k0kubun/benchmark_driver#output-options</a></p><p>You may be interested in a graph output, markdown output, memory runner or the &quot;command_stdout&quot; runner to integrate any existing Ruby benchmarks without building a runner plugin.</p><h3>benchmark-driver.github.io</h3><p><a href="https://benchmark-driver.github.io/">benchmark-driver</a></p><p>The goals of this project included that “preparing an environment to continuously benchmark the tests with the tool”. I wish I could use <a href="https://rubybench.org/">https://rubybench.org</a> or its temporary fork to use benchmark_driver.gem, but a server which I could borrow for benchmark can’t be reached from the Internet.</p><p>To make sure my goal is achieved in a short period using the server, instead of contributing to RubyBench, I decided to create a system that can generate a static page to be published on GitHub pages. As we can easily create a plugin<br>to integrate benchmark_driver.gem with any services, we’ll be able to merge my works to RubyBench project later. After that, benchmark-driver.github.io might be obsoleted. The major difference between RubyBench and the site for now is that it includes JIT results.</p><p>If you are interested in how it works, you may want to see:<br><a href="https://github.com/benchmark-driver/skybench">https://github.com/benchmark-driver/skybench</a><br><a href="https://github.com/benchmark-driver/benchmark-driver.github.io">https://github.com/benchmark-driver/benchmark-driver.github.io</a></p><p>In this section, I’ll mainly describe about the benchmark sets which are included in the site.</p><h4>Ruby Core</h4><p><a href="https://github.com/benchmark-driver/ruby-benchmarks">benchmark-driver/ruby-benchmarks</a></p><p>They are benchmarks in Ruby repository, but they are converted to YAML format to abstract away the while loop which I mentioned above.</p><p>While the benchmark results are also avalable on RubyBench, you can see JIT-ed results on benchmark-driver.github.io. But note that current Ruby’s JIT is method JIT and many of those benchmarks don’t create a method to be JIT-ed. In such a situation, JIT results in these benchmark might be useless and you may want to see “MJIT benchmarks” instead.</p><h4>Ruby Method</h4><p><a href="https://github.com/benchmark-driver/ruby-method-benchmarks">benchmark-driver/ruby-method-benchmarks</a></p><p>This is originally created by <a href="http://twitter.com/Watson1978">@Watson1978</a> at <a href="https://github.com/Watson1978/ruby-method-benchmarks">https://github.com/Watson1978/ruby-method-benchmarks</a>. He has actively improved Ruby’s performance by measuring many Ruby core features and find parts to work on, using the benchmark set.</p><p>Having a good coverage of benchmarked features is good to catch the performance regression. You may be able to find Ruby core features to be improved in the results.</p><h4>MJIT</h4><p><a href="https://github.com/benchmark-driver/mjit-benchmarks">benchmark-driver/mjit-benchmarks</a></p><p>The benchmark set is created by Vladimir Makarov. As I said above, a benchmark hotspot needs to be a method to measure method JIT’s performance. At the same time, we want to omit the overhead of method call as much as possible. The benchmark set seems to be designed for measuing method JIT performance correctly.</p><p>After benchmark is finished for recent revisions, you’ll be able to see latest JIT performance improvements.</p><h4>Optcarrot</h4><p><a href="https://github.com/benchmark-driver/optcarrot">benchmark-driver/optcarrot</a></p><p>It’s created by <a href="http://twitter.com/mame">@mame</a> and the repository adds only benchmark.yml to it.<br>You may want to see <a href="https://www.slideshare.net/mametter/optcarrot-a-pureruby-nes-emulator">https://www.slideshare.net/mametter/optcarrot-a-pureruby-nes-emulator</a> for the details of the amazing program for benchmark.</p><h3>Future works</h3><p>Outside the scope which was originally proposed for this project, we have several things to work on to make the best use of the outcome.</p><h4>Sophisticating plugin interface</h4><p>Right now the interface between Runner and Output is like this:</p><pre>module BenchmarkDriver<br>  Metrics = ::BenchmarkDriver::Struct.new(<br>    :value,      # <a href="http://twitter.com/param">@param</a> [Float]<br>    :executable, # <a href="http://twitter.com/param">@param</a> [BenchmarkDriver::Config::Executable]<br>    :duration,   # <a href="http://twitter.com/param">@param</a> [Float,nil]<br>  )</pre><pre>  Metrics::Type = ::BenchmarkDriver::Struct.new(<br>    :unit,          # <a href="http://twitter.com/param">@param</a> [String]<br>    :larger_better, # <a href="http://twitter.com/param">@param</a> [TrueClass,FalseClass]<br>    :worse_word,    # <a href="http://twitter.com/param">@param</a> [String]<br>    defaults: { larger_better: true, worse_word: &#39;slower&#39; },<br>  )<br>end</pre><p>This was good enough to express all of execution time, memory consumption as max resident set and optcarrot fps, and to make a comparison or a graph from it.</p><p>But what if want to show real time and user/system CPU time at the same time? What about showing 50/75/90/99 percentile in one graph?</p><p>To achieve such use case, I’m planning to change only one Float value to a Hash as { Symbol =&gt; Float }. It should have a Symbol that should express the main metrics, and I’ll define some well-known Symbols to achieve special outputs available for limited plugins.</p><h4>Add more benchmarks to benchmark-driver.github.io</h4><p>Only measuring micro benchmarks and NES emulator performance sounds not enough to achieve Ruby 3x3. But adding benchmarks is a little challenging because it’s already taking a lot of time to finish running all benchmarks for one revision. Also, installing many gems for each revision will trigger a disk consumption problem if we keep many Ruby revisions for future benchmark addition.</p><p>Anyway, I’ll show some future canditates to be added to benchmark-driver.github.io.</p><p><strong>Discourse</strong></p><p><a href="https://github.com/noahgibbs/rails_ruby_bench">https://github.com/noahgibbs/rails_ruby_bench</a><br><a href="https://github.com/discourse/discourse">https://github.com/discourse/discourse</a></p><p>Discourse is a popular Rails application and has script/bench.rb to measure its performance. And Noah Gibbs created its improved version as rails_ruby_bench.</p><p>Since one of major Ruby use cases is Rails, we definitely need to measure Ruby performance with Rails. This is blocked by “showing 50/75/90/99 percentile” issue and a disk consumption problem for now. Rails has too many dependencies compared to Optcarrot…</p><p><strong>Fluentd</strong></p><p><a href="https://github.com/benchmark-driver/fluentd-benchmark">https://github.com/benchmark-driver/fluentd-benchmark</a></p><p>Fluentd is a log collector written in Ruby, which is used by so many large-scale services <a href="https://www.fluentd.org/">https://www.fluentd.org</a>.So Fluentd might be a good real-world Ruby use case whose performance is very important.<br>Disclaimer: Note that I’m an employee of Treasure Data, a company building Fluentd.</p><p>I modified one_forward benchmark in <a href="https://github.com/fluent/fluentd-benchmark">https://github.com/fluent/fluentd-benchmark</a> so that we can use it from benchmark_driver.gem. Currently it’s not on benchmark-driver.github.io because the benchmark result does not look affected by Ruby’s performance. I’m not sure why, and we need to investigate the cause to help Fluentd become faster.</p><h4>Integrating derailed_benchmarks.gem</h4><p>You may want to use your Rails application for a benchmark test case for Ruby 3x3. If I integrate <a href="http://github.com/schneems/derailed_benchmarks">derailed_benchmarks.gem</a> with benchmark_driver.gem, we may be able to compare Ruby’s performance using the Rails application easily.</p><p>Recently <a href="http://twitter.com/schneems">@schneems</a> kindly gave me a commit bit of the gem. So I’ll be able to improve the situation from both sides.</p><h3>Conclusion</h3><p>benchmark_driver.gem is good for many use cases of Ruby&#39;s benchmark.<br>Please try it and give me a feedback to achieve Ruby 3x3.</p><h3>Acknowledgements</h3><p>The success of this project would be impossible without help from Ruby Association. Especially Koichi Sasada, a mentor of this project, gave me many good ideas to improve it. Thank you so much.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6a5eeb1d1494" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The method JIT compiler for Ruby 2.6]]></title>
            <link>https://k0kubun.medium.com/the-method-jit-compiler-for-ruby-2-6-388ee0989c13?source=rss-c2f58218a6ba------2</link>
            <guid isPermaLink="false">https://medium.com/p/388ee0989c13</guid>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[k0kubun]]></dc:creator>
            <pubDate>Fri, 16 Feb 2018 11:55:34 GMT</pubDate>
            <atom:updated>2018-02-16T11:55:34.051Z</atom:updated>
            <content:encoded><![CDATA[<p>This month, I merged the Ruby&#39;s first JIT compiler.</p><h3>k0kubun on Twitter</h3><p>I&#39;ve just committed the initial JIT compiler for Ruby. It&#39;s not still so fast yet (especially it&#39;s performing badly with Rails for now), but we have much time to improve it until Ruby 2.6 (or 3.0) release. https://t.co/7mO5FZM80C</p><p>Then I gave a talk about it yesterday at my company&#39;s conference.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fspeakerdeck.com%2Fplayer%2Fd641b89542b04a6b9a1e4e140c983a96&amp;url=https%3A%2F%2Fspeakerdeck.com%2Fk0kubun%2Fvm-generated-jit-compiler-for-ruby-2-dot-6&amp;image=https%3A%2F%2Fspeakerd.s3.amazonaws.com%2Fpresentations%2Fd641b89542b04a6b9a1e4e140c983a96%2Fslide_0.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=speakerdeck" width="710" height="463" frameborder="0" scrolling="no"><a href="https://medium.com/media/e9e2f6d0e3094785bebd7fc0b44ac6cb/href">https://medium.com/media/e9e2f6d0e3094785bebd7fc0b44ac6cb/href</a></iframe><p>As it isn&#39;t ultra-fast yet, I haven&#39;t shared it deeply until the conference. But some of you guys kindly wrote great articles about it.</p><ul><li><a href="https://www.johnhawthorn.com/2018/02/playing-with-ruby-jit-mjit/">Playing with ruby&#39;s new JIT: MJIT</a></li><li><a href="https://medium.com/square-corner-blog/rubys-new-jit-91a5c864dd10">Ruby&#39;s New JIT</a></li></ul><p>Probably they are easier to understand than my article. But I found several things to point out or announce. So I decided to write this article.</p><h3>What&#39;s &quot;MJIT&quot;?</h3><p>Let me share the summary about recent JIT topics on Ruby. Those who read the above articles may want to skip this section.</p><h4>The RTL instruction project</h4><p>In July 2016, <a href="https://bugs.ruby-lang.org/issues/12142">Vladimir Makarov, or Vlad, who optimized Hash at Ruby 2.4</a> filed a ticket <a href="https://bugs.ruby-lang.org/issues/12589">&quot;VM performance improvement proposal&quot; (Feature #12589)</a>. It&#39;s a project to replace all Ruby VM&#39;s stack-based instruction set with register-based one, which is named RTL (Register Transfer Language) instructions.</p><p>He is also known as the author of GCC&#39;s register allocator and a maintainer of its instruction scheduler. I assumed that he wanted to apply some optimization knowledge in GCC to Ruby.</p><p>While it&#39;s such a big change to replace many parts of Ruby VM, he successfully passed `make test` with RTL instruction implementation. It succeeded to improve some micro benchmarks, but <a href="https://github.com/mame/optcarrot">a larger benchmark named Optcarrot</a> wasn&#39;t improved.</p><h4>MJIT: JIT compiler that runs on RTL instructions</h4><p>To approach the problem, in March 2017, he also published JIT compiler which compiles RTL instructions, which is named MJIT (MRI JIT).</p><p>He discussed some possible JIT methods in <a href="https://github.com/vnmakarov/ruby/tree/rtl_mjit_branch#a-few-possible-approaches-in-jit-implementation">rtl_mjit_branch</a> and chose the approach that generates C code to a disk, lets C compiler compile it to .so and loads machine code from the object file.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-TywiN2VEwfH6cDuHbTa1A.png" /></figure><p>JIT succeeded to make the Optcarrot result 2 or 3 times faster.</p><p>On the other hand, as a side effect of largely rewriting the implementation of VM, although the `make test` is passed, a more severe test such as make test-all` is not passed regardless of whether JIT is enabled or disabled, and Rails fails to start on the RTL instructions.</p><h4>YARV-MJIT: Yet another JIT compiler that runs on YARV instructions</h4><p>In this way, RTL instruction conversion was a somewhat risky change, but my company implements cloud service as Rails application and provides it, so very high availability is required. If high risk changes were made to Ruby, upgrading Ruby would be very difficult.</p><p>After Vlad&#39;s session at RubyKaigi last year, I thought about how to reduce the risk to introduce JIT compiler to Ruby. Then I decided to implement another approach called YARV-MJIT, which compiles original stack-based YARV instructions instead of RTL instructions.</p><p>The JIT approach is very safe because it can be achieved without modifying VM implementation at all. Even if you find some bug after next Ruby release, you can ensure Ruby works as before by disabling optional JIT compiler.</p><p>As it doesn&#39;t change bytecode dynamically to avoid risks, it can&#39;t reach the performance of original MJIT. But with some improvements, I succeeded to make its performance comparable with MJIT.</p><p>By removing some premature aggressive optimizations, YARV-MJIT could pass all Ruby test cases with and without JIT enablement, keeping the performance improvement in some level. I proposed to merge it to Ruby in late 2017, and it&#39;s successfully merged this month after fixing some potential bugs and random crashes.</p><h3>Ruby&#39;s JIT status</h3><p>Ruby 2.6 merged following 2 components.</p><ul><li>JIT Compiler: YARV-MJIT</li><li>JIT Infrastructure: MJIT</li></ul><p>I call the infrastructure part as &quot;MJIT infrastructure&quot; in source code or ticket because &quot;MJIT&quot; was originally the combination of the part and the built-in JIT compiler. Sometimes people call both of them just as MJIT, but I think it&#39;s okay. Even Ruby committers don&#39;t say YARV-MJIT.</p><p>I&#39;ll share how Ruby&#39;s JIT implementation is going.</p><h3>JIT compiler benchmarks</h3><p>Even though the conservative JIT compiler has the aim to minimize risks of JIT introduction, of course it must improve the performance because the change is for optimization. I&#39;ll share some benchmark results on my machine: Intel 4.0GHz i7-4790K 8 Cores, 16GB memory, x86_64 Linux.</p><h4><strong>Optcarrot</strong></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oXoXHmYGO07uRtMUq4XKNQ.png" /></figure><p>This is the latest result of Optcarrot benchmark. While <a href="https://speakerdeck.com/k0kubun/just-in-time-compiler-for-mri?slide=14">YARV-MJIT achieved about 68fps previously</a>, it became a little slower because it dropped some premature optimizations.</p><p>In <a href="https://github.com/ruby/ruby/pull/1782">my Pull Request to merge JIT compiler</a>, I attached the benchmark result with 63fps. <a href="https://medium.com/square-corner-blog/rubys-new-jit-91a5c864dd10">Shannon&#39;s article</a> seems to quote the result, but as I previously updated in the PR&#39;s description, <a href="https://github.com/ruby/ruby/commit/1fd816803b3c7c2d0b138e06be578b833a132718">it&#39;s temporarily changed to 58fps</a>. I know the cause of the gap between 63fps and 58fps, and now I&#39;m working on fixing it.</p><p>While we can&#39;t know which commit contributes to VM performance because recently RubyBench stops to be updated, recent shyouhei&#39;s VM improvements (like <a href="https://github.com/ruby/ruby/pull/1779">this</a>) seem to make Ruby 2.6 much faster even without JIT compiler.</p><p>It&#39;s a shame for me that my JIT compiler doesn&#39;t contribute to Ruby 2.6&#39;s performance improvements so much, but the improvements of VM performance sounds a good news for sure.</p><h4><strong>Benchmark in “Playing with ruby’s new JIT”</strong></h4><p>Is the JIT compiler&#39;s optimization limited to only about 11%?</p><p>The article <a href="https://www.johnhawthorn.com/2018/02/playing-with-ruby-jit-mjit/">&quot;Playing with ruby&#39;s new JIT: MJIT&quot;</a> has a benchmark script. And the author of truffleruby shared <a href="https://gist.github.com/chrisseaton/4ff76c04e5938ee85c8f8de4e7086d73">improved version of the benchmark</a>.</p><pre>def calculate(a, b, n = 40_000_000)<br>  i = 0<br>  c = 0<br>  while i &lt; n<br>    a = a * 16807 % 2147483647<br>    b = b * 48271 % 2147483647<br>    c += 1 if (a &amp; 0xffff) == (b &amp; 0xffff)<br>    i += 1<br>  end<br>  c<br>end</pre><pre>Benchmark.ips do |x|<br>  x.iterations = 3<br>  x.report(&quot;calculate&quot;) do |times|<br>    calculate(65, 8921, 100_000)<br>  end<br>end</pre><p>Here is its result:</p><pre>$ ruby -v<br>ruby 2.6.0dev (2018-02-15 trunk 62410) [x86_64-linux]<br>$ ruby bench.rb<br>Warming up --------------------------------------<br>    calculate    13.000  i/100ms<br>    calculate    13.000  i/100ms<br>    calculate    13.000  i/100ms<br>Calculating -------------------------------------<br>    calculate    1.800k (± 2.7%) i/s - 8.996k in   5.002504s<br>    calculate    1.785k (± 7.4%) i/s - 8.853k in   5.003616s<br>    calculate    1.802k (± 4.0%) i/s - 8.996k in   5.006199s<br>$ ruby --jit bench.rb<br>Warming up --------------------------------------<br>    calculate    13.000  i/100ms<br>    calculate    18.000  i/100ms<br>    calculate    27.000  i/100ms<br>Calculating -------------------------------------<br>    calculate    7.182k (± 9.1%) i/s - 35.397k in   5.000332s<br>    calculate    7.296k (± 2.9%) i/s - 36.450k in   5.001392s<br>    calculate    7.295k (± 3.1%) i/s - 36.450k in   5.002572s</pre><p>1.802k is improved to 7.296k. It&#39;s about 4x faster. It seems that we already achieved Ruby 3x3!</p><p>Obviously the benchmark looks designed for JIT propaganda, but it suggests the potential performance improvements in the real world if we tune the benchmark for it.</p><p>Even for such an arbitrarily created benchmark, we should aim to improve CRuby in the order of hundred because truffleruby shows 370x performance on my machine.</p><h4><strong>Other benchmarks</strong></h4><p><a href="https://github.com/ruby/ruby/commit/ed935aa5be0e5e6b8d53c3e7d76a9ce395dfa18b">The commit merging JIT compiler</a> shows other benchmark results in the commit message. Some of micro benchmarks are improved about 2x faster compared to Ruby 2.6&#39;s VM execution.</p><p>It also includes discourse&#39;s benchmark result. I wanted to use <a href="https://github.com/noahgibbs/rails_ruby_bench">rails_ruby_bench by Noah Gibbs</a>, but for now I&#39;m failing to run it on my machine and so using discourse&#39;s script/bench.rb. For now, JIT compiler even makes Rails a little slower. <a href="https://twitter.com/samsaffron/status/963219086833434624">Sam Saffron pointed out that Unicorn is forking and MJIT is disabled in the child process</a>. There are many things to be improved. I continue to investigate Rails performance with JIT.</p><h3>VM-Generated JIT Compiler</h3><p>As JIT compiler needs to generate code that works in the same way as VM implementation, a very simple JIT compiler would have many copy-pastes of VM implementation. It would be bad for maintainability.</p><p>Ruby VM is generated by the special template format &quot;insns.def&quot;. ko1 suggested to generate JIT compiler from insns.def too. Just before merging JIT compiler to Ruby trunk, I tried it and it worked fine.</p><p>It&#39;s so simple that you can understand immediately. Ruby has mjit_compile.inc.erb like following one. (%-starting line is the special mode of ERB. Did you know that?)</p><pre>switch (insn) {<br>% RubyVM::BareInstructions.to_a.each do |insn|<br>  case BIN(&lt;%= insn.name %&gt;):<br>&lt;%= render &#39;mjit_compile_insn&#39;, locals: { insn: insn } -%&gt;<br>  break;<br>% end<br>}</pre><p>Branching by the type of VM instruction, _mjit_compile_insn_body.erb prints the content of insns.def with the following code.</p><pre>% expand_simple_macros.call(insn.expr.expr).each_line do |line|<br>%   if line =~ /\A\s+JUMP\((?&lt;dest&gt;[^)]+)\);\s+\z/<br>      /* Dynamic generation of JUMP code */<br>%   else<br>      fprintf(f, &lt;%= to_cstr.call(line) %&gt;);<br>%   end<br>% end</pre><p>It converts macros for JIT beforehand, and then JUMP macro is dynamically generated because it requires special conversion. From this ERB file, following C code is generated, which runs as JIT compiler on runtime.</p><pre>switch (insn) {<br>  case BIN(nop):<br>    fprintf(f, &quot;{\n&quot;);<br>    {<br>        fprintf(f, &quot;    reg_cfp-&gt;pc = original_body_iseq + %d;\n&quot;, pos);<br>        fprintf(f, &quot;    reg_cfp-&gt;sp = (VALUE *)reg_cfp-&gt;bp + %d;\n&quot;, b-&gt;stack_size + 1);<br>        fprintf(f, &quot;    {\n&quot;);<br>        fprintf(f, &quot;        /* none */\n&quot;);<br>        fprintf(f, &quot;    }\n&quot;);<br>        b-&gt;stack_size += attr_sp_inc_nop();<br>    }<br>    fprintf(f, &quot;}\n&quot;);<br>    break;<br>  case BIN(getlocal):<br>    /* ... */<br>}</pre><p>Just listing many `fprintf`s. It&#39;s so easy to understand, right? When Ruby&#39;s main method requests to JIT a method in hotspot, the code in MJIT thread generates the following code.</p><pre>VALUE<br>_mjit0(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp)<br>{<br>    VALUE *stack = reg_cfp-&gt;sp;<br>    static const VALUE *const original_body_iseq = (VALUE *)0x5643d9a852a0;<br>    if (reg_cfp-&gt;pc != original_body_iseq) {<br>        return Qundef;<br>    }</pre><pre>label_0: /* nop */<br>{<br>    reg_cfp-&gt;pc = original_body_iseq + 1;<br>    reg_cfp-&gt;sp = (VALUE *)reg_cfp-&gt;bp + 2;<br>    {<br>        /* none */<br>    }<br>}</pre><pre>/* snip... */</pre><pre>} /* end of _mjit0 */</pre><p>`rb_execution_context_t` is a kind of Thread context and `rb_control_frame_t` is one frame of call stack. The interface allows to execute a frame that holds a method&#39;s bytecode on a specific Thread context.</p><p>By compiling this and calling `dlopen` and `dlsym`, you can get the function pointer of it and VM calls the function instead of executing bytecode on VM.</p><p>Why is it made fast? Please read the slides on the top of this article if you are interested.</p><h3>How is JIT infrastructure?</h3><h4>Support status of platforms</h4><p>I could easily port pthread to Windows native thread using existing thread abstraction layer for Ruby.</p><p>Instead, it was so hard to successfully build a C header file which is used on JIT compilation on some platforms. After merging JIT infrastructure, following platforms were reported to be failing or broken on RubyCI.</p><ul><li>NetBSD</li><li>Solaris</li><li>AIX</li><li>Intel C/C++ Compiler</li><li>Old Visual Studio</li></ul><p>I&#39;ve tested the JIT infrastructure with Linux, macOS, MinGW and newer Visual Studio. But it wasn&#39;t sufficient for Ruby&#39;s portability requirement. After installing some VMs or using CI environments, I succeeded to fix them. Now we can build the JIT header on many platforms.</p><p>Then I added `--jit-wait` option for JIT compiler testing and wrote unit tests for it. Linux+gcc usually works perfectly, but some tests fail on clang and MinGW environments. Not only the test failure, but also JIT performance with clang is a little worse than gcc. So it may be hard to see improvement on macOS. And MinGW is skipping to transform the JIT header due to bugs in transformation. It results in the slowness of JIT compilation on MinGW. I&#39;m working to fix those problems.</p><h4>Security</h4><p>Other core committers (nobu, usa) has improved many parts of JIT infrastructure. One of them is security.</p><p>MJIT worker thread does `fopen` with a path like `/tmp/_ruby_mjit_p123u4` when writing a C file to a disk. First of all, initial implementation didn&#39;t control the permission of the file (it was a known issue). It also didn&#39;t restrict opening the existing file. It means that the opened file may have arbitrary permission. It&#39;s fixed to force creating a new file.</p><p>When you test the JIT on production before release, please own your risk and be careful.</p><h3>Others</h3><h4>About &quot;Startup Time&quot;</h4><p>The article <a href="https://medium.com/square-corner-blog/rubys-new-jit-91a5c864dd10">Ruby&#39;s New JIT</a> pointed out that:</p><blockquote>Startup time is one thing to take into consideration when considering using the new JIT. Starting up Ruby with the JIT takes about six times (6x) longer.</blockquote><p>You found a very important part. The cause of it is that `--jit` option always starts to precompile the header for JIT on initializer, and it waits for the finish on Ruby process finalizer. So unfortunately it takes at least the time to compile one C header.</p><p>We have following 2 approaches to solve it.</p><ul><li>Building precompiled header on Ruby&#39;s build, not on runtime</li><li>Immediately cancel JIT compiler thread regardless of its state</li></ul><p>Even though it&#39;s named &quot;precompiled header&quot;, it&#39;s actually not precompiled. That&#39;s because I suspect that the format of precompiled header may change after upgrading a C compiler used for JIT. If it&#39;s turned out to be wrong, we can take the approach. For now, I took the conservative strategy.</p><p>We can immediately cancel JIT compiler thread ignoring the state of it, but it might be dangerous in some situations.</p><h4>`--jit-cc` does no longer exist</h4><p>Sometimes C compiler fails to compile a header generated by another C compiler. It&#39;s not Ruby&#39;s fault and we&#39;re not going to support it.</p><p>Now Ruby trunk automatically uses C compiler which was used to build Ruby itself. If you want to test performance with clang, you need to build Ruby with clang.</p><h3>Acknowledgements</h3><p>I&#39;m still not sure if my JIT compiler will be in the release of Ruby 2.6, but I want to say &quot;Thank you&quot; for following people.</p><ul><li>The inventor of MJIT: Vladimir Makarov</li><li>Ruby&#39;s father: Matz</li><li>Reviewers of YARV-MJIT code: ko1, mame</li><li>Many bug reports and fixes: wanabe</li><li>The first MinGW support patch: Lars Kannis</li><li>Maintainer of many build environments: hsbt</li><li>Core committers fixing MJIT infrastructure: nobu, usa, znz, knu</li><li>Recent VM improvements: shyouhei</li></ul><h3>What&#39;s next?</h3><p>We&#39;re preparing to release very early preview of Ruby 2.6.0. The performance may not be improved so much at the release, but I hope you enjoy hacking MJIT there.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=388ee0989c13" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>