[rub][source] +とかconcatのソースを読む

Rubyレシピブックを久々に見てみたら、concat処理は早く、+は遅いとかって感じのことがかかれてた(garbage collectionの関係とか)ので、ソースコードを読んでみた。

はじめに、+の処理部から読んでみる。なお、rubyのメソッドは、rb_define_method()関数部分で登録されてる。

  1. は以下のようにちなみに、以下のような感じで登録されています
    rb_define_method(rb_cString, "+", rb_str_plus, 1);

上の関数でのrb_str_plusが+の実体なので、その処理部分を読んでみると、以下のような感じになってました。

/*
 *  call-seq:
 *     str + other_str   => new_str
 *
 *  Concatenation---Returns a new <code>String</code> containing
 *  <i>other_str</i> concatenated to <i>str</i>.
 *
 *     "Hello from " + self.to_s   #=> "Hello from main"
 */

VALUE
rb_str_plus(str1, str2)
d(rb_cString, "+", rb_str_plus, 1);

    VALUE str1, str2;
{
    VALUE str3;

    StringValue(str2);
    str3 = rb_str_new(0, RSTRING(str1)->len+RSTRING(str2)->len);
    memcpy(RSTRING(str3)->ptr, RSTRING(str1)->ptr, RSTRING(str1)->len);
    memcpy(RSTRING(str3)->ptr + RSTRING(str1)->len,
           RSTRING(str2)->ptr, RSTRING(str2)->len);
    RSTRING(str3)->ptr[RSTRING(str3)->len] = '\0';

    if (OBJ_TAINTED(str1) || OBJ_TAINTED(str2))
        OBJ_TAINT(str3);
    return str3;
}

VALUE(unsigned long) str3という新しい要素を作成し、それにstr1+str2とした文字列を代入しreturnしてるのがわかる。

これに対して、concatは以下のような感じになっています。

    rb_define_method(rb_cString, "concat", rb_str_concat, 1);
    rb_define_method(rb_cString, "<<", rb_str_concat, 1);

concat = <<っぽいですね。

実際の処理はrb_str_concatで処理してるので、どのような処理がなされてるかみてみます

/*
 *  call-seq:
 *     str << fixnum        => str
 *     str.concat(fixnum)   => str
 *     str << obj           => str
 *     str.concat(obj)      => str
 *
 *  Append---Concatenates the given object to <i>str</i>. If the object is a
 *  <code>Fixnum</code> between 0 and 255, it is converted to a character before
 *  concatenation.
 *
 *     a = "hello "
 *     a << "world"   #=> "hello world"
 *     a.concat(33)   #=> "hello world!"
 */

VALUE
rb_str_concat(str1, str2)
    VALUE str1, str2;
{
    if (FIXNUM_P(str2)) {
        int i = FIX2INT(str2);
        if (0 <= i && i <= 0xff) { /* byte */
            char c = i;
            return rb_str_cat(str1, &c, 1);
        }
    }
    str1 = rb_str_append(str1, str2);

    return str1;
}

rb_str_plusみたいに、str3を新しく作らずに、str1にstr2を追加した文字列を返してることが分かります。

str1にstr2を追加させてるrb_str_append()関数の内部での処理では、新たにstrlen(str2)の領域をstr1に追加させるためにrealloc処理をしてからstringを返す処理が行われています。(str2が数値の場合はascii文字コードに変換され、値がrb_str_cat()で返されます。)

str2が数値の場合の例はこんな感じ

p "tako".conccat(126)

出力 "tako~"(126のascii文字は~)

まぁ、無駄な領域を作ってるだけに処理が遅くなるってことでしょうか。しかし、レシピブックの30秒と、0.05秒ってのの差は驚異的だなぁ.. メモリはできるだけ節約しましょうってことですかね。

mallocの処理も結構時間がかかるって話を聞いたことがあるのだけど、reallocはそうでもないのかな?謎だ