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

Avoid defining singleton methods #62

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions benchmarks/ostruct.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
prelude: |
# frozen_string_literal: true

require "ostruct"
keys = (0..29).map { :"method_#{_1}" }
input = keys.to_h { [_1, _1] }

benchmark:
new: |
OpenStruct.new(input)

attr_write_read: |
ostruct = OpenStruct.new
ostruct.method_0 = "foo"
ostruct.method_1 = "bar"
ostruct.method_2 = "baz"
ostruct.method_3 = "quux"
ostruct.method_4 = "foo"
ostruct.method_5 = "bar"
ostruct.method_6 = "baz"
ostruct.method_7 = "quux"
ostruct.method_8 = "quux"
ostruct.method_9 = "quux"
ostruct.method_10 = "foo"
ostruct.method_11 = "bar"
ostruct.method_12 = "baz"
ostruct.method_13 = "quux"
ostruct.method_14 = "foo"
ostruct.method_15 = "bar"
ostruct.method_16 = "baz"
ostruct.method_17 = "quux"
ostruct.method_18 = "quux"
ostruct.method_19 = "quux"
ostruct.method_20 = "foo"
ostruct.method_21 = "bar"
ostruct.method_22 = "baz"
ostruct.method_23 = "quux"
ostruct.method_24 = "foo"
ostruct.method_25 = "bar"
ostruct.method_26 = "baz"
ostruct.method_27 = "quux"
ostruct.method_28 = "quux"
ostruct.method_29 = "quux"
ostruct.method_0
ostruct.method_1
ostruct.method_2
ostruct.method_3
ostruct.method_4
ostruct.method_5
ostruct.method_6
ostruct.method_7
ostruct.method_8
ostruct.method_9
ostruct.method_10
ostruct.method_11
ostruct.method_12
ostruct.method_13
ostruct.method_14
ostruct.method_15
ostruct.method_16
ostruct.method_17
ostruct.method_18
ostruct.method_19
ostruct.method_20
ostruct.method_21
ostruct.method_22
ostruct.method_23
ostruct.method_24
ostruct.method_25
ostruct.method_26
ostruct.method_27
ostruct.method_28
ostruct.method_29

index_write_attr_read: |
ostruct = OpenStruct.new
keys.each_with_index do ostruct[_1] = _2 end
keys.each do ostruct.send(_1) end

index_write_attr_read: |
ostruct = OpenStruct.new
keys.each_with_index do ostruct[_1] = _2 end
keys.each do ostruct.send(_1) end

null_reads: |
ostruct = OpenStruct.new
ostruct.method_0
ostruct.method_1
ostruct.method_2
ostruct.method_3
ostruct.method_4
ostruct.method_5
ostruct.method_6
ostruct.method_7
ostruct.method_8
ostruct.method_9
ostruct.method_10
ostruct.method_11
ostruct.method_12
ostruct.method_13
ostruct.method_14
ostruct.method_15
ostruct.method_16
ostruct.method_17
ostruct.method_18
ostruct.method_19
ostruct.method_20
ostruct.method_21
ostruct.method_22
ostruct.method_23
ostruct.method_24
ostruct.method_25
ostruct.method_26
ostruct.method_27
ostruct.method_28
ostruct.method_29

10x_reads: |
ostruct = OpenStruct.new(input)
10.times do
ostruct.method_0
ostruct.method_1
ostruct.method_2
ostruct.method_3
ostruct.method_4
ostruct.method_5
ostruct.method_6
ostruct.method_7
ostruct.method_8
ostruct.method_9
end

100x_reads: |
ostruct = OpenStruct.new(input)
100.times do
ostruct.method_0
ostruct.method_1
ostruct.method_2
ostruct.method_3
ostruct.method_4
ostruct.method_5
ostruct.method_6
ostruct.method_7
ostruct.method_8
ostruct.method_9
end

contexts:
- name: v0.6.0
gems:
ostruct: 0.6.0
- name: local
prelude: |
$LOAD_PATH.unshift "./lib"
31 changes: 27 additions & 4 deletions lib/ostruct.rb
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ def marshal_dump # :nodoc:
# OpenStruct. It does this by using the metaprogramming function
# define_singleton_method for both the getter method and the setter method.
#
def new_ostruct_member!(name) # :nodoc:
def override_ostruct_method!(name) # :nodoc:
unless @table.key?(name) || is_method_protected!(name)
if defined?(::Ractor)
getter_proc = nil.instance_eval{ Proc.new { @table[name] } }
Expand All @@ -246,11 +246,11 @@ def new_ostruct_member!(name) # :nodoc:
define_singleton_method!("#{name}=", &setter_proc)
end
end
private :new_ostruct_member!
private :override_ostruct_method!

private def is_method_protected!(name) # :nodoc:
if !respond_to?(name, true)
false
true
elsif name.match?(/!$/)
true
else
Expand All @@ -271,6 +271,29 @@ def freeze
super
end

def singleton_methods(*) # :nodoc:
(super + @table.keys.flat_map {|k| [k, :"#{k}="] }).uniq
end

def methods(*) # :nodoc:
(super + @table.keys.flat_map {|k| [k, :"#{k}="] }).uniq
end

def respond_to_missing?(mid, *) # :nodoc:
if (mname = mid[/.*(?==\z)/m])
@table&.key?(mname.to_sym)
elsif @table&.key?(mid)
true
else
begin
super
rescue NoMethodError => err
err.backtrace.shift
raise!
end
end
end

private def method_missing(mid, *args) # :nodoc:
len = args.length
if mname = mid[/.*(?==\z)/m]
Expand Down Expand Up @@ -317,7 +340,7 @@ def [](name)
#
def []=(name, value)
name = name.to_sym
new_ostruct_member!(name)
override_ostruct_method!(name)
@table[name] = value
end
alias_method :set_ostruct_member_value!, :[]=
Expand Down
1 change: 1 addition & 0 deletions test/ostruct/test_ostruct.rb
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ def test_accessor_defines_method
assert_respond_to(os, :foo)
assert_equal(42, os.foo)
assert_equal([:foo, :foo=], os.singleton_methods.sort)
assert_equal([:foo, :foo=], os.methods(false).sort)
end

def test_does_not_redefine
Expand Down