« See all posts

Is Ruby 2.3 Faster? Immutable (Frozen) String Literals Performance

Posted by Alexander Dymo on February 18, 2016

Ruby 2.3 was released last month with yet another bunch of performance improvements. But is it really faster than 2.2? Let's take a look.

This is the fourth post in my series about Ruby 2.3 performance. This time we'll look at the immutable (aka frozen) string literals, the experimental feature that will be enabled by default in Ruby 3.0.

TL;DR

Nope, immutable/frozen string literals do not have a significant impact on Ruby performance. Here's why.

What's the Deal With Immutable Strings?

The idea is that every string literal cannot change after the creation. For example, you are not allowed to append to the string:

# test.rb
x = "foo"
x << " bar"

> RUBYOPT=--enable-frozen-string-literal ruby test.rb
test.rb:2:in `<main>': can't modify frozen String (RuntimeError)

Note that I had to explicitly enable frozen string literal support by setting the $RUBYOPT environment variable to --enable-frozen-string-literal. Here's the ticket where Ruby team discusses the change and its implementation.

The change applies only to literals, meaning strings that appear in the code. String objects continue to be mutable. For example, this is allowed:

# test.rb
x = String.new("foo")
x << " bar"
puts x

> RUBYOPT=--enable-frozen-string-literal ruby test.rb
foo bar

How Is That Faster?

The idea is that same string literals across your code will internally be represented with just one Ruby object. Think about hash keys, constants declared as strings, error messages, etc.

That in theory will reduce the number of allocations and the number of Ruby objects on the heap. This means less GC. And, as we all know, less GC is always good.

Read the first chapter from my Ruby Performance Optimization book to better understand the real effect of garbage collection on Ruby performance.

But the change applies only to string literals. Every other string that you use is not going to be immutable. Strings that you read from the file or the database? Nope. Strings that you generate from ERB templates? Nope. Strings you get as HTTP responses? Nope. Pretty much every string that you use as a data is not going to be immutable.

So what's the real world impact of this change in Ruby? Everything suggests it's going to be minimal. Unless there are tens of thousands of string literals somewhere in your code, you probably won't even see it.

An Unscientific Benchmark

Don't try to benchmark the effect of frozen string literals at home yet. Ruby itself, Rails, and many rubygems require patching to make them work with --enable-frozen-string-literal.

For example, ERB requires this patch. Bundler needs one file to be fixed. I also had to patch Railties, ActionPack, ActionView, Mail, Rack, Logging, Nokogiri, and some other less known gems.

In the end I got one of my Rails apps (that processes lots of strings) running. Then I issued one hundred of requests to the application and measured the execution time. Here's what I got:

Execution Time Memory
Mutable String Literals 209 ± 13 s 171 MB at startup
276 MB afterwards
Immutable String Literals 193 ± 11 s 163 MB at startup
267 MB afterwards

As you can see, there's no statistically significant difference between the execution time results. We only see that Rails took 8 MB less memory at startup (good). Most likely this reduction in memory usage is the one promised by our immutable string literals theory. As I predicted, it's not enough to make a difference.

Verdict: Not Faster

Turning on immutable string literals support in Ruby 2.3 does not lead to significant performance improvements. Yes, the memory usage is slightly reduced. But that has little or no effect on execution time.

Did you like this post? Follow me on Twitter or Google+ to stay updated on Ruby performance optimization news.

Next: Ruby Performance Optimization Book Tour, San Francisco Bay Area, March 21-28, 2016
Previous: Is Ruby 2.3 Faster? Rails ERB Template Rendering Performance