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

Hash: optimize `to_a`, `keys` and `values` #8042

Merged
merged 1 commit into from Aug 5, 2019

Conversation

@asterite
Copy link
Member

commented Aug 4, 2019

Hash#to_a was using the method from Enumerable but that doesn't preallocate an Array with a good initial size.

Benchmark:

require "benchmark"
require "./old_hash"

[5, 10, 15, 20, 30, 50, 100, 500, 1_000].each do |size|
  new_hash = Hash(Int32, Int32).new
  size.times do |i|
    new_hash[i] = i
  end

  old_hash = OldHash(Int32, Int32).new
  size.times do |i|
    old_hash[i] = i
  end

  Benchmark.ips do |x|
    x.report("old (#{size})") { old_hash.to_a }
    x.report("new (#{size})") { new_hash.to_a }
  end
end

Results:

old (5)  12.82M ( 77.98ns) (± 2.43%)   128B/op   1.79× slower
new (5)  23.00M ( 43.47ns) (± 3.40%)  80.0B/op        fastest
old (10)   7.88M (126.93ns) (± 0.94%)  240B/op   2.23× slower
new (10)  17.53M ( 57.04ns) (± 2.17%)  128B/op        fastest
old (15)   5.20M (192.49ns) (± 0.92%)  448B/op   2.91× slower
new (15)  15.10M ( 66.21ns) (± 2.26%)  160B/op        fastest
old (20)   4.81M (208.00ns) (± 0.92%)  448B/op   2.69× slower
new (20)  12.94M ( 77.26ns) (± 1.35%)  208B/op        fastest
old (30)   3.29M (304.00ns) (± 1.23%)  896B/op   3.02× slower
new (30)   9.94M (100.64ns) (± 1.04%)  288B/op        fastest
old (50)   2.14M (466.62ns) (± 1.78%)  1.66kB/op   3.14× slower
new (50)   6.73M (148.68ns) (± 3.46%)    480B/op        fastest
old (100)   1.14M (879.71ns) (± 0.62%)  3.65kB/op   3.03× slower
new (100)   3.45M (289.90ns) (± 1.01%)  1.04kB/op        fastest
old (500) 317.24k (  3.15µs) (± 3.64%)  12.7kB/op   2.54× slower
new (500) 804.41k (  1.24µs) (± 0.55%)  3.96kB/op        fastest
old (1000) 167.01k (  5.99µs) (± 0.26%)  24.7kB/op   3.59× slower
new (1000) 600.38k (  1.67µs) (± 0.34%)  7.86kB/op        fastest

For keys and values it's now a bit faster because Array.new(size, &block) is slightly faster than Array.new(size) + appending later because the first version doesn't need index bounds checking.

@asterite asterite force-pushed the asterite:hash_to_a branch from 886824d to 6842b65 Aug 5, 2019
@asterite asterite changed the title Hash: optimize `to_a` Hash: optimize `to_a`, `keys` and `values` Aug 5, 2019
@asterite asterite force-pushed the asterite:hash_to_a branch from 6842b65 to 972ae25 Aug 5, 2019
@asterite asterite merged commit ea77acd into crystal-lang:master Aug 5, 2019
4 of 5 checks passed
4 of 5 checks passed
ci/circleci: test_linux32 Your tests failed on CircleCI
Details
ci/circleci: check_format Your tests passed on CircleCI!
Details
ci/circleci: test_darwin Your tests passed on CircleCI!
Details
ci/circleci: test_linux Your tests passed on CircleCI!
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
@asterite asterite deleted the asterite:hash_to_a branch Aug 5, 2019
@asterite asterite added this to the 0.31.0 milestone Aug 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.