diff options
author | Aaron Patterson <tenderlove@ruby-lang.org> | 2023-09-18 14:44:51 -0700 |
---|---|---|
committer | Aaron Patterson <aaron.patterson@gmail.com> | 2023-09-28 10:43:45 -0700 |
commit | d3574c117a637a4456aa3ee78e24d8df510b9355 (patch) | |
tree | cbd48b14dc4e6114d8e556416828971a21465040 /io.c | |
parent | 655bcee95a5eeca789646cf63a2c00120c7092b3 (diff) | |
download | ruby-d3574c117a637a4456aa3ee78e24d8df510b9355.tar.gz |
Move IO#readline to Ruby
This commit moves IO#readline to Ruby. In order to call C functions,
keyword arguments must be converted to hashes. Prior to this commit,
code like `io.readline(chomp: true)` would allocate a hash. This
commits moves the keyword "denaturing" to Ruby, allowing us to send
positional arguments to the C API and avoiding the hash allocation.
Here is an allocation benchmark for the method:
```
x = GC.stat(:total_allocated_objects)
File.open("/usr/share/dict/words") do |f|
f.readline(chomp: true) until f.eof?
end
p ALLOCATIONS: GC.stat(:total_allocated_objects) - x
```
Before this commit, the output was this:
```
$ make run
./miniruby -I./lib -I. -I.ext/common -r./arm64-darwin22-fake ./test.rb
{:ALLOCATIONS=>707939}
```
Now it is this:
```
$ make run
./miniruby -I./lib -I. -I.ext/common -r./arm64-darwin22-fake ./test.rb
{:ALLOCATIONS=>471962}
```
[Bug #19890] [ruby-core:114803]
Diffstat (limited to 'io.c')
-rw-r--r-- | io.c | 35 |
1 files changed, 20 insertions, 15 deletions
@@ -4357,22 +4357,28 @@ rb_io_set_lineno(VALUE io, VALUE lineno) return lineno; } -/* - * call-seq: - * readline(sep = $/, chomp: false) -> string - * readline(limit, chomp: false) -> string - * readline(sep, limit, chomp: false) -> string - * - * Reads a line as with IO#gets, but raises EOFError if already at end-of-stream. - * - * Optional keyword argument +chomp+ specifies whether line separators - * are to be omitted. - */ - +/* :nodoc: */ static VALUE -rb_io_readline(int argc, VALUE *argv, VALUE io) +io_readline(rb_execution_context_t *ec, VALUE io, VALUE sep, VALUE lim, VALUE chomp) { - VALUE line = rb_io_gets_m(argc, argv, io); + if (NIL_P(lim)) { + // If sep is specified, but it's not a string and not nil, then assume + // it's the limit (it should be an integer) + if (!NIL_P(sep) && NIL_P(rb_check_string_type(sep))) { + // If the user has specified a non-nil / non-string value + // for the separator, we assume it's the limit and set the + // separator to default: rb_rs. + lim = sep; + sep = rb_rs; + } + } + + if (!NIL_P(sep)) { + StringValue(sep); + } + + VALUE line = rb_io_getline_1(sep, NIL_P(lim) ? -1L : NUM2LONG(lim), RTEST(chomp), io); + rb_lastline_set_up(line, 1); if (NIL_P(line)) { rb_eof_error(); @@ -15420,7 +15426,6 @@ Init_IO(void) rb_define_method(rb_cIO, "read", io_read, -1); rb_define_method(rb_cIO, "write", io_write_m, -1); rb_define_method(rb_cIO, "gets", rb_io_gets_m, -1); - rb_define_method(rb_cIO, "readline", rb_io_readline, -1); rb_define_method(rb_cIO, "getc", rb_io_getc, 0); rb_define_method(rb_cIO, "getbyte", rb_io_getbyte, 0); rb_define_method(rb_cIO, "readchar", rb_io_readchar, 0); |