Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite old attribute mapping to save many object allocations #963

Merged
merged 2 commits into from Oct 26, 2017
Merged

Rewrite old attribute mapping to save many object allocations #963

merged 2 commits into from Oct 26, 2017

Conversation

dillonwelch
Copy link
Contributor

Stringifying the keys of a hash can be done without allocating many
arrays like the previous approach did.

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"
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

@old = {a: :b, c: :d, e: :f}

def master_version
  Hash[@old.map { |k, v| [k.to_s, v] }]
end

def fast_version
  result = {}
  @old.each { |k, v| result[k.to_s] = v }
end

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
master_version
{:FREE=>-14768, :T_STRING=>6054, :T_ARRAY=>7000, :T_HASH=>1000, :T_IMEMO=>1000}
fast_version
{:FREE=>-7001, :T_STRING=>6000, :T_HASH=>1000}
Warming up --------------------------------------
      master_version    38.137k i/100ms
        fast_version    50.133k i/100ms
Calculating -------------------------------------
      master_version    451.898k (±19.2%) i/s -      2.174M in   5.002186s
        fast_version    633.579k (±19.4%) i/s -      3.058M in   5.019391s

Comparison:
        fast_version:   633578.7 i/s
      master_version:   451897.6 i/s - same-ish: difference falls within error

Results of benchmark.rb

                                         Haml |     ERB |  Erubis |
-------------------------------------------------------------------
Cached                                  0.086 |   0.074 |   0.066 |
ActionView                             17.940 |  12.237 |         |
ActionView with deep partials          44.639 |  40.158 |         |

compared to current master

                                         Haml |     ERB |  Erubis |
-------------------------------------------------------------------
Cached                                  0.174 |   0.420 |   0.224 |
ActionView                             21.360 |  11.631 |         |
ActionView with deep partials          43.563 |  39.238 |         |

@amatsuda
Copy link
Member

@oniofchaos Thanks, this change makes sense.
But can you please rebase from master and push -f here instead of merging origin/master into your branch?

@dillonwelch
Copy link
Contributor Author

@amatsuda done!

Stringifying the keys of a hash can be done without allocating many
arrays like the previous approach did.

```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"
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

@old = {a: :b, c: :d, e: :f}

def master_version
  Hash[@old.map { |k, v| [k.to_s, v] }]
end

def fast_version
  result = {}
  @old.each { |k, v| result[k.to_s] = v }
end

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
```

```ruby
master_version
{:FREE=>-14768, :T_STRING=>6054, :T_ARRAY=>7000, :T_HASH=>1000, :T_IMEMO=>1000}
fast_version
{:FREE=>-7001, :T_STRING=>6000, :T_HASH=>1000}
Warming up --------------------------------------
      master_version    38.137k i/100ms
        fast_version    50.133k i/100ms
Calculating -------------------------------------
      master_version    451.898k (±19.2%) i/s -      2.174M in   5.002186s
        fast_version    633.579k (±19.4%) i/s -      3.058M in   5.019391s

Comparison:
        fast_version:   633578.7 i/s
      master_version:   451897.6 i/s - same-ish: difference falls within error
```
@amatsuda
Copy link
Member

@oniofchaos Thanks!

@amatsuda amatsuda merged commit 291a7a7 into haml:master Oct 26, 2017
@dillonwelch dillonwelch deleted the attribute-builder-change branch October 26, 2017 01:51
netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this pull request Mar 24, 2020
Update ruby-haml to 5.1.2.
pkgsrc change: add "USE_LANGUAGES=	# none".


## 5.1.2

Released on August 6, 2019
([diff](haml/haml@v5.1.1...v5.1.2)).

* Fix crash in some environments such as New Relic by unfreezing string literals for ParseNode#inspect. [#1016](haml/haml#1016) (thanks [Jalyna](https://github.com/jalyna))

## 5.1.1

Released on May 25, 2019
([diff](haml/haml@v5.1.0...v5.1.1)).

* Fix NameError bug for that happens on ruby 2.6.1-2.6.3 + haml 5.1.0 + rails 4.2.x + erubi. (Akira Matsuda)

## 5.1.0

Released on May 16, 2019
([diff](haml/haml@v5.0.4...v5.1.0)).

* Rails 6 support [#1008](haml/haml#1008) (thanks [Seb Jacobs](https://github.com/sebjacobs))
* Add `escape_filter_interpolations` option for backwards compatibility with haml 4 defaults [#984](haml/haml#984) (thanks [Will Jordan](https://github.com/wjordan))
* Fix error on empty :javascript and :css filter blocks [#986](haml/haml#986) (thanks [Will Jordan](https://github.com/wjordan))
* Respect changes in Haml::Options.defaults in `Haml::TempleEngine` options (Takashi Kokubun)
* Un-freeze TempleEngine precompiled string literals [#983](haml/haml#983) (thanks [Will Jordan](https://github.com/wjordan))
* Various performance/memory improvements [#965](haml/haml#965), [#966](haml/haml#966), [#963](haml/haml#963) (thanks [Dillon Welch](https://github.com/oniofchaos))
* Enable `frozen_string_literal` magic comment for all .rb files [#967](haml/haml#967) (thanks [Dillon Welch](https://github.com/oniofchaos))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants