rurema / doctree

Repository of Japanese Ruby reference manual

Home Page:https://docs.ruby-lang.org/ja/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

String.new の :capacity の説明が合っていない?

elfham opened this issue · comments

commented

String.new:capacity の説明ですが、

内部バッファのサイズを指定します。指定することで、なんども文字列連結する (そしてreallocがなんども呼ばれる)ときのパフォーマンスが改善されるかもしれません。省略した場合、引数stringのバイト数が127未満であれば127、それ以上であればstring.bytesizeになります。

以前のバージョンでの仕様は分からないのですが、 3.1.0 以降では、省略時のデフォルトの説明が実際の動作と合っていないように思われます。

:capacityRStringas.heap.aux.capa に相当するかと思いますが、実際には以下のような動作になるようです。

  • :capacity の指定の無い場合
    • 文字列のサイズが小さい場合
      • 文字列は RString に embed されるので capa を持たない
    • 文字列のサイズが大きい (※) 場合
      • 文字列は別の freeze された文字列を参照して共有しているので、当該文字列オブジェクト自体は capa を持たない
  • :capacity を指定した場合
    • 62 未満の場合
      • as.heap.aux.capa は 63 になる
    • 63 以上の場合
      • as.heap.aux.capa は指定した値になる

※ 3.1.0 (USE_RVARGC 未指定) では 24 バイトから、 3.2.0 では 16 バイトから

説明のうまい修正案がちょっと思い付きませんが、「省略した場合」以降は削ってしまっても良いように思います。

メソッドシグネチャーの

new(string = "") -> String
new(string = "", encoding: string.encoding, capacity: 63) -> String
new(string = "", encoding: string.encoding, capacity: string.bytesize) -> String

も悩ましいですが、少なくとも string.bytesize になることは無いようなので 3 つ目は無くて良いように思います。

確認に使用したスクリプトと実行結果を以下に示します。

require 'inline'

class String
  def super_inspect
    self.class.superclass.instance_method(:inspect).bind(self).call
  end
  raise NotImplementedError, 'Ruby 3.1.0 required' if RUBY_VERSION < '3.1.0'
  inline do |builder|
    builder.include '<stdio.h>'
    builder.add_compile_flags '-Wall'
    builder.c_raw <<~CODE
      VALUE inspect_rstring(int argc, VALUE *argv, VALUE self) {
        FILE *o = stdout;
        if (argc == 1) {
          /* Output Title */
          if (TYPE(argv[0]) != T_STRING)
            rb_raise(rb_eTypeError, "Title Not String");
          fprintf(o, "%s\\n", RSTRING_PTR(argv[0]));
        }
        char *rstring_ptr = RSTRING_PTR(self);
        long rstring_len = RSTRING_LEN(self);
        bool str_embed = ! (RBASIC(self)->flags & RSTRING_NOEMBED);
        bool str_shared = RBASIC(self)->flags & ELTS_SHARED;
        struct RString *rstring = RSTRING(self);
        fprintf(o, "VALUE: 0x%lx\\n", self);
        fprintf(o, "OBJ_FROZEN: %d\\n", OBJ_FROZEN(self));
        fprintf(o, "STR_EMBED_P: %d\\n", str_embed);
        fprintf(o, "STR_SHARED_P: %d\\n", str_shared);
        fprintf(o, "RSTRING_PTR: %p\\n", rstring_ptr);
        fprintf(o, "RSTRING_LEN: %ld\\n", rstring_len);
        if (str_embed) {
      #if USE_RVARGC
          fprintf(o, "rstring->as.embed.len: %ld\\n", rstring->as.embed.len);
      #endif
          fprintf(o, "rstring->as.embed.ary: %p\\n", rstring->as.embed.ary);
          fprintf(o, "rstring->as.embed.ary: \\"%s\\"\\n", rstring->as.embed.ary);
        } else {
          fprintf(o, "rstring->as.heap.len: %ld\\n", rstring->as.heap.len);
          fprintf(o, "rstring->as.heap.ptr: %p\\n", rstring->as.heap.ptr);
          if (str_shared) {
            fprintf(o, "rstring->as.heap.aux.shared: 0x%lx\\n",
                    rstring->as.heap.aux.shared);
          } else {
            fprintf(o, "rstring->as.heap.aux.capa: %ld\\n",
                    rstring->as.heap.aux.capa);
          }
        }
        fprintf(o, "\\n");
        fflush(o);
        return Qnil;
      }
    CODE
  end
end

module Kernel
  inline do |builder|
    builder.add_compile_flags '-Wall'
    # XXX: DANGER!!!
    builder.c_raw <<~CODE
      VALUE draw_object_by_value(int argc, VALUE *argv, VALUE _kernel) {
        if (argc != 1)
          return Qnil;
        unsigned long value = (VALUE)NUM2ULONG(argv[0]);
        return (VALUE)value;
      }
    CODE
  end
end
% ruby -v
ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [x86_64-freebsd13.1]
% irb -I . -r inspect_rstring
irb(main):001:0> String.new("*" * 23).inspect_rstring
VALUE: 0x804f94cd0
OBJ_FROZEN: 0
STR_EMBED_P: 1
STR_SHARED_P: 1
RSTRING_PTR: 0x804f94ce0
RSTRING_LEN: 23
rstring->as.embed.ary: 0x804f94ce0
rstring->as.embed.ary: "***********************"

=> nil
irb(main):002:0> String.new("*" * 24).inspect_rstring
VALUE: 0x8062f0ab8
OBJ_FROZEN: 0
STR_EMBED_P: 0
STR_SHARED_P: 1
RSTRING_PTR: 0x8051b5760
RSTRING_LEN: 24
rstring->as.heap.len: 24
rstring->as.heap.ptr: 0x8051b5760
rstring->as.heap.aux.shared: 0x8062f0a90

=> nil
irb(main):003:0> String.new(capacity: 62).inspect_rstring
VALUE: 0x805ae96d0
OBJ_FROZEN: 0
STR_EMBED_P: 0
STR_SHARED_P: 0
RSTRING_PTR: 0x805292cc0
RSTRING_LEN: 0
rstring->as.heap.len: 0
rstring->as.heap.ptr: 0x805292cc0
rstring->as.heap.aux.capa: 63

=> nil
irb(main):004:0> 
% ruby -v
ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-freebsd13.1]
% irb -I . -r inspect_rstring
irb(main):001:0> String.new("*" * 15).inspect_rstring
VALUE: 0x80c643700
OBJ_FROZEN: 0
STR_EMBED_P: 1
STR_SHARED_P: 0
RSTRING_PTR: 0x80c643718
RSTRING_LEN: 15
rstring->as.embed.len: 15
rstring->as.embed.ary: 0x80c643718
rstring->as.embed.ary: "***************"

=> nil
irb(main):002:0> String.new("*" * 16).inspect_rstring
VALUE: 0x80c604f00
OBJ_FROZEN: 0
STR_EMBED_P: 0
STR_SHARED_P: 1
RSTRING_PTR: 0x8084eb388
RSTRING_LEN: 16
rstring->as.heap.len: 16
rstring->as.heap.ptr: 0x8084eb388
rstring->as.heap.aux.shared: 0x8084eb370

=> nil
irb(main):003:0> String.new(capacity: 62).inspect_rstring
VALUE: 0x80c5c71c8
OBJ_FROZEN: 0
STR_EMBED_P: 0
STR_SHARED_P: 0
RSTRING_PTR: 0x807251b00
RSTRING_LEN: 0
rstring->as.heap.len: 0
rstring->as.heap.ptr: 0x807251b00
rstring->as.heap.aux.capa: 63

=> nil
irb(main):004:0> 
commented

# あれ?余談ですが、 3.1.3 では STR_EMBED_PSTR_SHARED_P の両方が立つことがあるんですね……。