Skip to content

Commit

Permalink
Eliminate a hash allocation when rendering templates
Browse files Browse the repository at this point in the history
The `**options` in this code allocates a hash.  Most template renders
are probably not cached, so if we return a little early, we can save a
hash allocation on each render call.

Here is an allocation benchmark:

```ruby

require "active_record"
require "active_record/railties/collection_cache_association_loading"
require "action_controller"
require "action_view"
require "tmpdir"

ActionView::PartialRenderer.prepend(ActiveRecord::Railties::CollectionCacheAssociationLoading)

class TestController < ActionController::Base
  def _prefixes; []; end # Search the current directory rather than "test/"
end

Dir.mktmpdir do |dir|
  TestController.view_paths = dir

  File.write File.join(dir, "_thing.html.erb"), "Hello <%= yield %>"

  cv = TestController.new.view_context

  # heat
  cv.render("thing") { "World" }
  # delete the line below and allocations go *way* down

  def recurse(cv, depth)
    if depth == 0
      cv.render("thing") { "World" }
    else
      cv.render("thing") { recurse(cv, depth - 1) }
    end
  end

  def allocs
    x = GC.stat(:total_allocated_objects)
    yield
    GC.stat(:total_allocated_objects) - x
  end

  10.times do |i|
    p i => allocs { recurse(cv, i) }
  end
end
```

Before:

```
$ be ruby thing.rb
{0=>58}
{1=>77}
{2=>115}
{3=>153}
{4=>192}
{5=>231}
{6=>270}
{7=>309}
{8=>348}
{9=>387}
```

After:

```
$ be ruby thing.rb
{0=>55}
{1=>73}
{2=>109}
{3=>145}
{4=>182}
{5=>219}
{6=>256}
{7=>293}
{8=>330}
{9=>367}
```

I realize this is a "micro bench", but we're seeing the same line pop up
in benchmarks from our app.  I extracted this micro-bench from one we're
using to test renders inside our app.
  • Loading branch information
tenderlove committed Feb 25, 2020
1 parent 3dff30c commit 6878c50
Showing 1 changed file with 4 additions and 2 deletions.
Expand Up @@ -4,14 +4,16 @@ module ActiveRecord
module Railties # :nodoc:
module CollectionCacheAssociationLoading #:nodoc:
def setup(context, options, as, block)
@relation = nil

return super unless options[:cached]

@relation = relation_from_options(**options)

super
end

def relation_from_options(cached: nil, partial: nil, collection: nil, **_)
return unless cached

relation = partial if partial.is_a?(ActiveRecord::Relation)
relation ||= collection if collection.is_a?(ActiveRecord::Relation)

Expand Down

0 comments on commit 6878c50

Please sign in to comment.