Prevent extra string allocations when no 'rel' arg passed

Do a check if the 'rel' argument is passed in, and simply set it to
'nofollow' if 'rel' was not passed in. This prevents three string
allocations for each call to `link_to` in that scenario. In the scenario
where the 'rel' argument is passed in, performance is around the same as
before as the `key?` check is very fast.

```ruby
begin
  require "bundler/inline"
rescue LoadError => e
  $stderr.puts "Bundler version 1.10 or later is required. Please update
                your Bundler"
  raise e
end

gemfile(true) do
  source "https://rubygems.org"

  gem "benchmark-ips"
  gem "rails"
end

def allocate_count
  GC.disable
  before = ObjectSpace.count_objects
  yield
  after = ObjectSpace.count_objects
  after.each { |k,v| after[k] = v - before[k] }
  after[:T_HASH] -= 1 # probe effect - we created the before hash.
  GC.enable
  result = after.reject { |k,v| v == 0 }
  GC.start
  result
end

@hash = {}

def master_version
  "#{@hash['rel'.freeze]} nofollow".lstrip
end

def fast_version
  if @hash.key?('rel'.freeze)
    "#{@hash["rel"]} nofollow".lstrip
  else
    "nofollow".freeze
  end
end

puts 'no rel key'

puts "master_version"
puts allocate_count { 1000.times { master_version } }
puts "fast_version"
puts allocate_count { 1000.times { fast_version } }

Benchmark.ips do |x|
  x.report("master_version") { master_version }
  x.report("fast_version")     { fast_version }
  x.compare!
end

puts 'rel key'

@hash['rel'] = 'hi'.freeze

puts "master_version"
puts allocate_count { 1000.times { master_version } }
puts "fast_version"
puts allocate_count { 1000.times { fast_version } }

Benchmark.ips do |x|
  x.report("master_version") { master_version }
  x.report("fast_version")     { fast_version }
  x.compare!
end
```

```
no rel key
master_version
{:FREE=>-2791, :T_STRING=>3052}
fast_version
{:FREE=>-1}
Warming up --------------------------------------
      master_version    80.324k i/100ms
        fast_version   200.262k i/100ms
Calculating -------------------------------------
      master_version      2.049M (±11.9%) i/s -     10.121M in   5.025613s
        fast_version      6.645M (±21.3%) i/s -     29.439M in   5.007488s

Comparison:
        fast_version:  6644506.3 i/s
      master_version:  2048833.0 i/s - 3.24x  slower

rel key
master_version
{:FREE=>-2001, :T_STRING=>2000}
fast_version
{:FREE=>-2001, :T_STRING=>2000}
Warming up --------------------------------------
      master_version   155.673k i/100ms
        fast_version   106.515k i/100ms
Calculating -------------------------------------
      master_version      2.652M (±20.4%) i/s -     12.610M in   5.036494s
        fast_version      2.237M (±16.8%) i/s -     10.865M in   5.035366s

Comparison:
      master_version:  2651702.2 i/s
        fast_version:  2237470.6 i/s - same-ish: difference falls within error
```
This commit is contained in:
Dillon Welch 2017-10-26 18:17:38 -07:00
parent 432b193d3a
commit f5f0b49b9b

@ -589,10 +589,14 @@ def link_to_remote_options?(options)
end
def add_method_to_attributes!(html_options, method)
if method && method.to_s.downcase != "get".freeze && html_options["rel".freeze] !~ /nofollow/
html_options["rel".freeze] = "#{html_options["rel".freeze]} nofollow".lstrip
if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/
if html_options.key?("rel")
html_options["rel"] = "#{html_options["rel"]} nofollow".lstrip
else
html_options["rel"] = "nofollow"
end
end
html_options["data-method".freeze] = method
html_options["data-method"] = method
end
def token_tag(token = nil, form_options: {})