Here's a memory allocation report taken from one of our Rails endpoints with miniprofiler in production:
Total allocated: 36329328 bytes (541884 objects)
Total retained: 4395083 bytes (18004 objects)
allocated memory by gem
-----------------------------------
14044335 transit-ruby-ff2e3acd071a
4148691 temple-0.7.6
3993243 slim-3.0.6
3794721 activesupport-5.0.7
2622300 actionview-5.0.7
1827160 front/app
1686968 other
834761 dalli-2.7.6
787136 transit-rails-6d6b533ba1df
494528 set
491982 actionpack-5.0.7
430072 activerecord-5.0.7
315464 concurrent-ruby-1.0.5
...
You see that transit-ruby is at the top, which is no surprise because the endpoint renders a heavy transit response. But if we look closely on allocations grouped by file, then there's definitely a room for improvement:


We can do nothing with cruby/json.rb, because it's the oj's part:
|
def emit_array_start(size) |
|
@state << :array |
|
@oj.push_array |
|
end |
|
|
|
def emit_array_end |
|
@state.pop |
|
@oj.pop |
|
end |
|
def emit_value(obj, as_map_key=false) |
|
if @state.last == :array |
|
@oj.push_value(obj) |
|
else |
|
as_map_key ? @oj.push_key(obj) : @oj.push_value(obj) |
|
end |
|
end |
Same with the marshaler/base.rb, which produces a string interpolation each time:
|
def emit_string(prefix, tag, value, as_map_key, cache) |
|
encoded = "#{prefix}#{tag}#{value}" |
|
if cache.cacheable?(encoded, as_map_key) |
|
emit_value(cache.write(encoded), as_map_key) |
|
else |
|
emit_value(encoded, as_map_key) |
|
end |
|
end |
But write handlers produce a large amount of static strings for nothing (lines 210, 234):
|
class KeywordHandler |
|
def tag(_) ":" end |
|
def rep(s) s.to_s end |
|
def string_rep(s) rep(s) end |
|
end |
|
class IntHandler |
|
def tag(i) i > MAX_INT || i < MIN_INT ? "n" : "i" end |
|
def rep(i) i > MAX_INT || i < MIN_INT ? i.to_s : i end |
|
def string_rep(i) i.to_s end |
|
end |
Line 211 also produces a large amount of strings, but it's a symbol to string conversion, which I believe is inevitable.
Another offender is RollingCache:
|
def cacheable?(str, as_map_key=false) |
|
str.size >= MIN_SIZE_CACHEABLE && (as_map_key || str.start_with?("~#","~$","~:")) |
|
end |
Solution
After adding # frozen_string_literal: true magic comment to the top of the mentioned files, I see the much nicer picture:
Total allocated: 31684710 bytes (425875 objects)
Total retained: 4398650 bytes (18009 objects)
allocated memory by gem
-----------------------------------
9395815 transit-ruby/lib
4148546 temple-0.7.6
3993613 slim-3.0.6
3796808 activesupport-5.0.7
2621277 actionview-5.0.7
1830296 front/app
1686968 other
834761 dalli-2.7.6
787136 transit-rails-6d6b533ba1df
494528 set
491790 actionpack-5.0.7
429992 activerecord-5.0.7
315464 concurrent-ruby-1.0.5
that is, 20% less object allocations in total.
As for execution speed, I don't see any improvements in my local tests, but having less pressure on GC is always good.
Usually, those comments are being added throughout entire library to be Rails 3 ready.
Here's a memory allocation report taken from one of our Rails endpoints with miniprofiler in production:
You see that transit-ruby is at the top, which is no surprise because the endpoint renders a heavy transit response. But if we look closely on allocations grouped by file, then there's definitely a room for improvement:
We can do nothing with cruby/json.rb, because it's the oj's part:
transit-ruby/lib/transit/marshaler/cruby/json.rb
Lines 31 to 39 in b4973f8
transit-ruby/lib/transit/marshaler/cruby/json.rb
Lines 59 to 65 in b4973f8
Same with the marshaler/base.rb, which produces a string interpolation each time:
transit-ruby/lib/transit/marshaler/base.rb
Lines 87 to 94 in b4973f8
But write handlers produce a large amount of static strings for nothing (lines 210, 234):
transit-ruby/lib/transit/write_handlers.rb
Lines 209 to 213 in b4973f8
transit-ruby/lib/transit/write_handlers.rb
Lines 233 to 237 in b4973f8
Line 211 also produces a large amount of strings, but it's a symbol to string conversion, which I believe is inevitable.
Another offender is RollingCache:
transit-ruby/lib/transit/rolling_cache.rb
Lines 49 to 51 in b4973f8
Solution
After adding
# frozen_string_literal: truemagic comment to the top of the mentioned files, I see the much nicer picture:that is, 20% less object allocations in total.
As for execution speed, I don't see any improvements in my local tests, but having less pressure on GC is always good.
Usually, those comments are being added throughout entire library to be Rails 3 ready.