Improve Rails performance by adding a few gems

tags

Working with Rails for some time you start nitpicking on how to improve it. This is a first in the series of articles regarding on how to improve (even marginally) Rails's performance.

I'll focus on a bunch of gems that speed up, in some cases considerably, small parts of Rails, like the html escaping, the String#blank? and JSON utils.

Benchmarking methodology

Methodology is a strong word for just running a couple of times in the console wrk but I'm not searching for the holy grail here, just to get a raw idea.

I switched from the old apache ab to wrk redirect:

wrk is a modern HTTP benchmarking tool capable of generating significant
load when run on a single multi-core CPU.

wrk -t10 -c10 -d10s http://localhost:3000

This runs a benchmark for 10 seconds, using 10 threads, and keeping 50 HTTP connections open i.e. this should suffice. Just remember to benchmark on your actual app to see the real improvements.

The escape_utils gem

Just faster all html escaping via the lovely escape_utils redirect gem. In order to use it in Rails one needs to add an initializer that patches things up:

begin
  require 'escape_utils/html/rack' # to patch Rack::Utils
  require 'escape_utils/html/erb' # to patch ERB::Util
  require 'escape_utils/html/cgi' # to patch CGI
  require 'escape_utils/html/haml' # to patch Haml::Helpers
rescue LoadError
  Rails.logger.info 'Escape_utils is not in the gemfile'
end

The logic to test it:

def escape_utils
  @escape_me = <<-HTML
    <body class="application articles_show">
      <!-- Responsive navigation
      ==================================================== -->
      <div class="container">
        <nav id="nav">
      <ul>
        <li><a href="/"><i class="ss-standard ss-home"></i>home</a></li>
        <li><a href="/home/about"><i class="ss-standard ss-info"></i>about</a></li>
        <li><a href="/contact"><i class="ss-standard ss-ellipsischat"></i>contact</a></li>
        <li><a href="/home/projects"><i class="ss-standard ss-fork"></i>projects</a></li>
        <li><a href="/tags"><i class="ss-standard ss-tag"></i>tags</a></li>
        <li><a href="/articles?query=code"><i class="ss-standard ss-search"></i>search</a></li>
      </ul>
    </nav>
    <a href="#" class="ss-standard ss-list" id="nav-toggle" aria-hidden="true"></a>
  HTML

  render inline: "Hello  world <%= @escape_me %>"
end

With standard Rails:

Running 10s test @ http://localhost:3000/sidechannels/bench
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    35.40ms    3.55ms  64.70ms   91.98%
    Req/Sec   142.19     11.68   164.00     83.12%
  2837 requests in 10.00s, 4.92MB read
Requests/sec:    283.61
Transfer/sec:    503.34KB

With the escape_utils gem:

Running 10s test @ http://localhost:3000/sidechannels/bench
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    34.06ms    3.89ms  63.92ms   89.10%
    Req/Sec   148.65     13.36   180.00     75.94%
  2960 requests in 10.00s, 5.46MB read
Requests/sec:    295.98
Transfer/sec:    558.72KB

The fast_blank gem

Living under the impression that the blank? method is too slow? say no more and just try the fast_blank redirect gem!

Just add gem 'fast_blank' to your Gemfile and this should speed up quite nicely the String#blank? method as described in this article redirect. For testing I just added this code:

fast_blank is a simple extension which provides a fast implementation of active support's string#blank? function

  def fast_blank_test
    n = 1000

    strings = [
      "",
      "\r\n\r\n  ",
      "this is a test",
      "   this is a longer test",
      "   this is a longer test
      this is a longer test
      this is a longer test
      this is a longer test
      this is a longer test"
    ]

    Benchmark.bmbm  do |x|
      strings.each do |s|
        x.report("Fast Blank #{s.length}    :") do
          n.times { s.blank? }
        end
      end
    end

    render nothing: true
  end

With standard Rails:

Running 10s test @ http://localhost:3000/sidechannels/bench
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.40s   207.72ms   1.58s    92.68%
    Req/Sec     3.10      2.11     6.00     53.66%
  69 requests in 10.01s, 33.08KB read
Requests/sec:      6.90
Transfer/sec:      3.31KB

With the fast_blank gem:

Running 10s test @ http://localhost:3000/sidechannels/bench
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.33s   179.56ms   1.41s    93.33%
    Req/Sec     3.07      0.80     4.00     40.00%
  72 requests in 10.00s, 34.52KB read
Requests/sec:      7.20
Transfer/sec:      3.45KB

The oj gem

# oj gem
gem 'oj'
gem 'oj_mimic_json' # we need this for Rails 4.1.x

The test logic is simple, just serialize all articles into JSON:

class SidechannelsController < ApplicationController
  def oj
    render json: Article.all
  end
end

With standard Rails serializers:

Running 10s test @ http://localhost:3000/sidechannels/bench
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   108.37ms    5.12ms 134.90ms   83.33%
    Req/Sec    45.76      3.60    55.00     57.69%
  922 requests in 10.00s, 57.41MB read
Requests/sec:     92.17
Transfer/sec:      5.74MB

With oj gem:

Running 10s test @ http://localhost:3000/sidechannels/bench
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    78.06ms    4.43ms  92.83ms   81.31%
    Req/Sec    63.64      5.33    71.00     64.49%
  1277 requests in 10.00s, 79.83MB read
Requests/sec:    127.65
Transfer/sec:      7.98MB

Using jemalloc

OK, this is not really a gem, if you want to dig into it then do check out my gist redirect. On initial testing it won't yield much performance gains, at least for my use case.

note: it will be included by default in Ruby at some point.

update: do try the jemalloc redirect gem by kzk:

gem install jemalloc

je -v rails s

Dig into your Rails app

Fear not and use MiniProfiler redirect with the awesome FlameGraphs redirect by Sam Saffron.

Conclusion

Depending on what your app is doing you might want to add to your Gemfile some of these gems, I usually add them all just for good measure (you might want to check your RAM usage and have a full test suite before doing this though).

The oj gem is just great for a Rails based JSON API where you can drop the views and just serialize using representers or your pattern of choice.

Marian Posăceanu

rubyist

okapistudio