This article is an article that redefines the method Float (arg, exception: true)
using builtin introduced in the following article I wrote earlier.
I implemented Ruby with Ruby (and C) (I played with builtin)
What you are doing is roughly the same as in the previous article. The difference from the last time is that I tried to redefine the method that can be called anywhere with builtin instead of the method of a specific class.
After writing the previous article, I was wondering if methods such as raise
and Float
could support builtin, so I felt like trying it out.
From the conclusion, it seems that such global methods can also be supported by builtin (although it may not be recommended because we have not confirmed whether it is the intended behavior ...)
builtin is to implement Ruby itself in Ruby (and C). For details, please read the previous article.
I implemented Ruby with Ruby (and C) (I played with builtin)
The environment of the previous article is used as it is. There are no major changes.
I implemented Ruby with Ruby (and C) (I played with builtin)
First, find the source that implements the Float
method. Ruby methods are generally defined like this.
rb_define_method(rb_mKernel, "to_s", rb_any_to_s, 0);
The first argument is the variable of the class / module in which the method is defined. Generally, it is created by class name or module name such as rb_cArray
. The second argument " to_s "
is the method name called on the Ruby side. The third argument is a C function that is executed when the method is called, and the last argument specifies the number of arguments the method receives.
This time, I want to reimplement the Float
method, so let's search in the Ruby source code withgit grep \ "Float \"
etc.
Then, it seems that it is implemented as follows in ʻobject.c`.
rb_define_global_function("Float", rb_f_float, -1);
Let's reimplement it.
In the previous article, I reimplemented Hash # delete
. Also, to support builtin, I modified common.mk
etc.
However, in the current Ruby master code, Kernel # clone
, which was implemented in ʻobject.c`, is made compatible with builtin, so there is no need to modify them.
There are two sources that need to be modified: ʻobject.c and
kernel.rb`.
In ʻobject.c, first delete the following code that defines the
Float` method.
void
InitVM_Object(void)
{
Init_class_hierarchy();
//abridgement
- rb_define_global_function("Float", rb_f_float, -1);
//abridgement
}
Next, modify the rb_f_float
that defines the processing of the Float
method as follows.
- /*
- * call-seq:
- * Float(arg, exception: true) -> float or nil
- *
- * Returns <i>arg</i> converted to a float. Numeric types are
- * converted directly, and with exception to String and
- * <code>nil</code> the rest are converted using
- * <i>arg</i><code>.to_f</code>. Converting a String with invalid
- * characters will result in a ArgumentError. Converting
- * <code>nil</code> generates a TypeError. Exceptions can be
- * suppressed by passing <code>exception: false</code>.
- *
- * Float(1) #=> 1.0
- * Float("123.456") #=> 123.456
- * Float("123.0_badstring") #=> ArgumentError: invalid value for Float(): "123.0_badstring"
- * Float(nil) #=> TypeError: can't convert nil into Float
- * Float("123.0_badstring", exception: false) #=> nil
- */
static VALUE static VALUE
- rb_f_float(int argc, VALUE *argv, VALUE obj)
+ rb_f_float(rb_execution_context_t *ec, VALUE main, VALUE arg, VALUE opts)
{
- VALUE arg = Qnil, opts = Qnil;
-
- rb_scan_args(argc, argv, "1:", &arg, &opts);
- return rb_convert_to_float(arg, opts_exception_p(opts));
+ return rb_convert_to_float(arg, opts_exception_p(opts));
}
Since it uses builtin and receives keyword arguments, it is set to VALUE main, VALUE arg, VALUE opts
instead of ʻint argc, VALUE * argv. Also,
VALUE main` is added to take the arguments that the global method implicitly receives instead.
This completes the modification in ʻobject.c`!
In Ruby, global methods that can be called from anywhere can be redefined in the Kernel
module.
For example, the puts
method can be monkey patched as follows:
module Kernel
def puts *args
p "hoge"
end
end
puts :ho
#=> "hoge"Is displayed
Use this to add the Float
method in kernel.rb
as shown below.
module Kernel
+
+ #
+ # call-seq:
+ # Float(arg, exception: true) -> float or nil
+ #
+ # Returns <i>arg</i> converted to a float. Numeric types are
+ # converted directly, and with exception to String and
+ # <code>nil</code> the rest are converted using
+ # <i>arg</i><code>.to_f</code>. Converting a String with invalid
+ # characters will result in a ArgumentError. Converting
+ # <code>nil</code> generates a TypeError. Exceptions can be
+ # suppressed by passing <code>exception: false</code>.
+ #
+ # Float(1) #=> 1.0
+ # Float("123.456") #=> 123.456
+ # Float("123.0_badstring") #=> ArgumentError: invalid value for Float(): "123.0_badstring"
+ # Float(nil) #=> TypeError: can't convert nil into Float
+ # Float("123.0_badstring", exception: false) #=> nil
+ #
+ def Float(arg, exception: true)
+ __builtin_rb_f_float(arg, exception)
+ end
+
#
# call-seq:
# obj.clone(freeze: nil) -> an_object
#
# Produces a shallow copy of <i>obj</i>---the instance variables of
# <i>obj</i> are copied, but not the objects they reference.
# #clone copies the frozen value state of <i>obj</i>, unless the
# +:freeze+ keyword argument is given with a false or true value.
# See also the discussion under Object#dup.
#
# class Klass
# attr_accessor :str
# end
# s1 = Klass.new #=> #<Klass:0x401b3a38>
# s1.str = "Hello" #=> "Hello"
# s2 = s1.clone #=> #<Klass:0x401b3998 @str="Hello">
# s2.str[1,4] = "i" #=> "i"
# s1.inspect #=> "#<Klass:0x401b3a38 @str=\"Hi\">"
# s2.inspect #=> "#<Klass:0x401b3998 @str=\"Hi\">"
#
# This method may have class-specific behavior. If so, that
# behavior will be documented under the #+initialize_copy+ method of
# the class.
#
def clone(freeze: nil)
__builtin_rb_obj_clone2(freeze)
end
end
The rb_f_float
that was made compatible with builtin earlier is called with __builtin_rb_f_float
. Now the fix in kernel.rb
is OK.
After that, if you can build by executing make
and make install
, the implementation with builtin is completed.
It seems that methods such as Float
and ʻInteger` can be built in like this. Regardless of whether it was sent as a patch, it was interesting that it seemed to be quite applicable.
By the way, when I tried the following benchmarks, I felt that I could expect performance improvement.
benchmark:
float: "Float(42)"
float_true: "Float(42, exception: true)"
float_false: "Float(42, exception: false)"
loop_count: 10000
Results below
Calculating -------------------------------------
compare-ruby built-ruby
float 37.495M 15.878M i/s - 10.000k times in 0.000267s 0.000630s
float_true 1.109M 1.211M i/s - 10.000k times in 0.009015s 0.008261s
float_false 1.227M 1.191M i/s - 10.000k times in 0.008150s 0.008395s
Comparison:
float
compare-ruby: 37495314.0 i/s
built-ruby: 15878056.0 i/s - 2.36x slower
float_true
built-ruby: 1210507.2 i/s
compare-ruby: 1109250.0 i/s - 1.09x slower
float_false
compare-ruby: 1226948.7 i/s
built-ruby: 1191128.5 i/s - 1.03x slower
If a method such as Float
is built in, I think that code maintainability will be more important than performance.
Recommended Posts