Bug #13365
closedImprove performance of rb_equal() with special constants
Description
rb_equal() is been using in many places to compare the object.
If objects are special constants,
"if (obj1 == obj2) return Qtrue;" can check whether objects are equal or not.
(https://github.com/ruby/ruby/blob/0b1f6aed9414a4d7714910e61db08fdb2ac3ecd1/object.c#L90)
So, it can skip rb_funcall() calling to comfirm that special constant objects are not equal.
At least, Time#eql? will be faster around 60%.
Time object might have Finuxm (special constant) value internally on 64 bit environment.
Before¶
user system total real
Time#eql? with receiver 0.890000 0.000000 0.890000 ( 0.891377)
Time#eql? with other 1.430000 0.000000 1.430000 ( 1.429047)
After¶
user system total real
Time#eql? with receiver 0.890000 0.000000 0.890000 ( 0.890050)
Time#eql? with other 0.900000 0.000000 0.900000 ( 0.905941)
Test code¶
require 'benchmark'
Benchmark.bmbm do |x|
t1 = Time.now
t2 = Time.now
x.report "Time#eql? with receiver" do
10000000.times do
t1.eql?(t1)
end
end
x.report "Time#eql? with other" do
10000000.times do
t1.eql?(t2)
end
end
end
Patch¶
The patch is in https://github.com/ruby/ruby/pull/1552
Updated by watson1978 (Shizuo Fujita) almost 7 years ago
- Status changed from Open to Closed
Applied in changeset trunk|r58880.
Improve performance of rb_equal()
-
object.c (rb_equal): add optimized path to compare the objects using
rb_equal_opt(). Previously, if not same objects were given, rb_equal() would
call `==' method via rb_funcall() which took a long time.rb_equal_opt() has provided faster comparing for Fixnum/Float/String objects.
Now, Time#eql? uses rb_equal() to compare with argument object and it will
be faster around 40% on 64-bit environment. -
array.c (rb_ary_index): remove redundant rb_equal_opt() calling.
Now, rb_equal() was optimized using rb_equal_opt().
If rb_equal_opt() returns Qundef, it will invoke rb_equal() -> rb_equal_opt(),
and it will cause the performance regression.So, this patch will remove first redundant rb_equal_opt() calling.
-
array.c (rb_ary_rindex): ditto.
-
array.c (rb_ary_includes): ditto.
[ruby-core:80360] [Bug #13365] [Fix GH-#1552]
Before¶
Time#eql? with other 7.309M (± 1.4%) i/s - 36.647M in 5.014964s
Array#index(val) 1.433M (± 1.2%) i/s - 7.207M in 5.030942s
Array#rindex(val) 1.418M (± 1.6%) i/s - 7.103M in 5.009164s
Array#include?(val) 1.451M (± 0.9%) i/s - 7.295M in 5.026392s
After¶
Time#eql? with other 10.321M (± 1.9%) i/s - 51.684M in 5.009203s
Array#index(val) 1.474M (± 0.9%) i/s - 7.433M in 5.044384s
Array#rindex(val) 1.449M (± 1.7%) i/s - 7.292M in 5.034436s
Array#include?(val) 1.466M (± 1.7%) i/s - 7.373M in 5.030047s
Test code¶
require 'benchmark/ips'
Benchmark.ips do |x|
t1 = Time.now
t2 = Time.now
x.report "Time#eql? with other" do |i|
i.times { t1.eql?(t2) }
end
Benchmarks to check whether it didn't introduce the regression¶
obj = Object.new
x.report "Array#index(val)" do |i|
ary = [1, 2, true, false, obj]
i.times { ary.index(obj) }
end
x.report "Array#rindex(val)" do |i|
ary = [1, 2, true, false, obj].reverse
i.times { ary.rindex(obj) }
end
x.report "Array#include?(val)" do |i|
ary = [1, 2, true, false, obj]
i.times { ary.include?(obj) }
end
end