为什么==比eql更快?

我在文档中读到了关于eql?的String类eql? 是一个严格的相等运算符,没有类型转换,并且==是一个等于运算符,它尝试将其参数的第二个转换为String,并且此方法的C源代码确认:

这个eql? 源代码:

 static VALUE rb_str_eql(VALUE str1, VALUE str2) { if (str1 == str2) return Qtrue; if (TYPE(str2) != T_STRING) return Qfalse; return str_eql(str1, str2); } 

==源代码:

 VALUE rb_str_equal(VALUE str1, VALUE str2) { if (str1 == str2) return Qtrue; if (TYPE(str2) != T_STRING) { if (!rb_respond_to(str2, rb_intern("to_str"))) { return Qfalse; } return rb_equal(str2, str1); } return str_eql(str1, str2); } 

但是当我尝试对这些方法进行基准测试时,我很惊讶==eql?更快eql? 高达20%! 我的基准代码是:

 require "benchmark" RUN_COUNT = 100000000 first_string = "Woooooha" second_string = "Woooooha" time = Benchmark.measure do RUN_COUNT.times do |i| first_string.eql?(second_string) end end puts time time = Benchmark.measure do RUN_COUNT.times do |i| first_string == second_string end end puts time 

结果:

Ruby 1.9.3-p125:

 26.420000 0.250000 26.670000 ( 26.820762) 21.520000 0.200000 21.720000 ( 21.843723) 

Ruby 1.9.2-p290:

 25.930000 0.280000 26.210000 ( 26.318998) 19.800000 0.130000 19.930000 ( 19.991929) 

那么,任何人都可以解释为什么更简单的eql? 当我为两个相似的字符串运行它时,方法比==方法慢?

您看到差异的原因与== vs eql?的实现无关eql? 但是由于Ruby优化运算符(例如== )以避免在可能的情况下进行常规方法查找。

我们可以通过两种方式validation这一点:

  • ==创建一个别名,然后调用它。 你会得到类似的结果到eql? 因此结果比==慢。

  • 比较使用send :==send :eql? 相反,你会得到类似的时间; 速度差异消失,因为Ruby只会使用优化来直接调用运算符,而不是使用send__send__

这里的代码显示了两个:

 require 'fruity' first = "Woooooha" second = "Woooooha" class String alias same_value? == end compare do with_operator { first == second } with_same_value { first.same_value? second } with_eql { first.eql? second } end compare do with_send_op { first.send :==, second } with_send_eql { first.send :eql?, second } end 

结果:

 with_operator is faster than with_same_value by 2x ± 0.1 with_same_value is similar to with_eql with_send_eql is similar to with_send_op 

如果你很好奇,运算符的优化是在insns.def

注意 :这个答案仅适用于Ruby MRI,例如,如果JRuby / rubinius存在速度差异,我会感到惊讶。

在进行基准测试时,不要使用times ,因为这会创建一个RUN_COUNT次闭包。 结果所花费的额外时间在绝对意义上同样影响所有基准,但这使得更难注意到相对差异:

 require "benchmark" RUN_COUNT = 10_000_000 FIRST_STRING = "Woooooha" SECOND_STRING = "Woooooha" def times_eq_question_mark RUN_COUNT.times do |i| FIRST_STRING.eql?(SECOND_STRING) end end def times_double_equal_sign RUN_COUNT.times do |i| FIRST_STRING == SECOND_STRING end end def loop_eq_question_mark i = 0 while i < RUN_COUNT FIRST_STRING.eql?(SECOND_STRING) i += 1 end end def loop_double_equal_sign i = 0 while i < RUN_COUNT FIRST_STRING == SECOND_STRING i += 1 end end 1.upto(10) do |i| method_names = [:times_eq_question_mark, :times_double_equal_sign, :loop_eq_question_mark, :loop_double_equal_sign] method_times = method_names.map {|method_name| Benchmark.measure { send(method_name) } } puts "Run #{i}" method_names.zip(method_times).each do |method_name, method_time| puts [method_name, method_time].join("\t") end puts end 

 Run 1 times_eq_question_mark 3.500000 0.000000 3.500000 ( 3.578011) times_double_equal_sign 2.390000 0.000000 2.390000 ( 2.453046) loop_eq_question_mark 3.110000 0.000000 3.110000 ( 3.140525) loop_double_equal_sign 2.109000 0.000000 2.109000 ( 2.124932) Run 2 times_eq_question_mark 3.531000 0.000000 3.531000 ( 3.562386) times_double_equal_sign 2.469000 0.000000 2.469000 ( 2.484295) loop_eq_question_mark 3.063000 0.000000 3.063000 ( 3.109276) loop_double_equal_sign 2.109000 0.000000 2.109000 ( 2.140556) Run 3 times_eq_question_mark 3.547000 0.000000 3.547000 ( 3.593635) times_double_equal_sign 2.437000 0.000000 2.437000 ( 2.453047) loop_eq_question_mark 3.063000 0.000000 3.063000 ( 3.109275) loop_double_equal_sign 2.140000 0.000000 2.140000 ( 2.140557) Run 4 times_eq_question_mark 3.547000 0.000000 3.547000 ( 3.578011) times_double_equal_sign 2.422000 0.000000 2.422000 ( 2.437422) loop_eq_question_mark 3.094000 0.000000 3.094000 ( 3.140524) loop_double_equal_sign 2.140000 0.000000 2.140000 ( 2.140557) Run 5 times_eq_question_mark 3.578000 0.000000 3.578000 ( 3.671758) times_double_equal_sign 2.406000 0.000000 2.406000 ( 2.468671) loop_eq_question_mark 3.110000 0.000000 3.110000 ( 3.156149) loop_double_equal_sign 2.109000 0.000000 2.109000 ( 2.156181) Run 6 times_eq_question_mark 3.562000 0.000000 3.562000 ( 3.562386) times_double_equal_sign 2.407000 0.000000 2.407000 ( 2.468671) loop_eq_question_mark 3.109000 0.000000 3.109000 ( 3.124900) loop_double_equal_sign 2.125000 0.000000 2.125000 ( 2.234303) Run 7 times_eq_question_mark 3.500000 0.000000 3.500000 ( 3.546762) times_double_equal_sign 2.453000 0.000000 2.453000 ( 2.468671) loop_eq_question_mark 3.031000 0.000000 3.031000 ( 3.171773) loop_double_equal_sign 2.157000 0.000000 2.157000 ( 2.156181) Run 8 times_eq_question_mark 3.468000 0.000000 3.468000 ( 3.656133) times_double_equal_sign 2.454000 0.000000 2.454000 ( 2.484296) loop_eq_question_mark 3.093000 0.000000 3.093000 ( 3.249896) loop_double_equal_sign 2.125000 0.000000 2.125000 ( 2.140556) Run 9 times_eq_question_mark 3.563000 0.000000 3.563000 ( 3.593635) times_double_equal_sign 2.453000 0.000000 2.453000 ( 2.453047) loop_eq_question_mark 3.125000 0.000000 3.125000 ( 3.124900) loop_double_equal_sign 2.141000 0.000000 2.141000 ( 2.156181) Run 10 times_eq_question_mark 3.515000 0.000000 3.515000 ( 3.562386) times_double_equal_sign 2.453000 0.000000 2.453000 ( 2.453046) loop_eq_question_mark 3.094000 0.000000 3.094000 ( 3.140525) loop_double_equal_sign 2.109000 0.000000 2.109000 ( 2.156181) 
 equal? is reference equality == is value equality eql? is value and type equality 

第三种方法, eql? 通常用于测试两个对象是否具有相同的值以及相同的类型。 例如:

 puts "integer == to float: #{25 == 25.0}" puts "integer eql? to float: #{25.eql? 25.0}" gives: Does integer == to float: true Does integer eql? to float: false 

所以我以为自从eql? 更多检查它会更慢,对于字符串它,至少在我的Ruby 1.93。 所以我认为它必须依赖于类型并进行一些测试。 当整数和浮点数比较eql? 有点快。 当比较整数时==更快,直到x2。 错误的理论,重新开始。

下一个理论:比较同一类型的两个值将更快,其中一个被certificate是真的,在它们是相同类型的情况下==总是更快, eql? 类型不同时更快,再次到x2。

没有时间比较所有类型,但我相信你会得到不同的结果,虽然同样的比较总是给出类似的结果。 有人可以certificate我错了吗?

以下是我对OP测试的结果:

  16.863000 0.000000 16.863000 ( 16.903000) 2 strings with eql? 14.212000 0.000000 14.212000 ( 14.334600) 2 strings with == 13.213000 0.000000 13.213000 ( 13.245600) integer and floating with eql? 14.103000 0.000000 14.103000 ( 14.200400) integer and floating with == 13.229000 0.000000 13.229000 ( 13.410800) 2 same integers with eql? 9.406000 0.000000 9.406000 ( 9.410000) 2 same integers with == 19.625000 0.000000 19.625000 ( 19.720800) 2 different integers with eql? 9.407000 0.000000 9.407000 ( 9.405800) 2 different integers with == 21.825000 0.000000 21.825000 ( 21.910200) integer with string with eql? 43.836000 0.031000 43.867000 ( 44.074200) integer with string with ==