OSSチャレンジPart6 (RubyのC API実装)

目的

学んだことを忘れないようにメモ!

間違えたことを書いている可能性あるので、注意してください!

あと、全部教えてもらいながらやったことを書いてるだけなので、自分の力ではないです。

やりたいこと

前回Part5で、作成したglibの実装を使って、MonthIntervalType等をred-arrowでも利用できるようになった。

しかし、arrowは大量のデータを使うので、一つ一つの値の変換処理を、Rubyでやっていたら遅いという問題がある。

なので、Cで変換するように他は実装されてるので、今回作ったMonthIntervalType等も同様にCで変換できるようにする。

やったこと

MonthIntervalType

ARROW-15749: [Ruby] Add support for #values of Month Interval Type by okadakk · Pull Request #12529 · apache/arrow · GitHub

たったこれだけ。

INT2NUMで、CのInt型から、Rubyで扱えるVALUE型(Rubyで扱うときはNumber型)に変換してる。

inline VALUE convert(const arrow::MonthIntervalArray& array,
                         const int64_t i) {
      return INT2NUM(array.Value(i));
}

DayTimeIntervalType

ARROW-15885 [Ruby] Add support for #values of DayTime Interval Type by okadakk · Pull Request #12593 · apache/arrow · GitHub

DayTimeIntervalのvalueは、daysとmillisecondsというメンバー変数を持つDayMillisecondというクラス?なので、そのまま渡せない。

今回は、RubyのhashObjectを作って(rb_hash_new)、そこに rb_hash_aset(hash[0] = hoge みたいにhashの特定のkeyに値を入れられる)を入れて値を入れている。

変数のdaysとか、millisecondsは、int32なので、VALUE型(Rubyで扱うときはNumber型)に変換。

    inline VALUE convert(const arrow::DayTimeIntervalArray& array,
                         const int64_t i) {
      auto value = rb_hash_new();
      auto arrow_value = array.Value(i);
      rb_hash_aset(value,
                   red_arrow::symbols::day,
                   INT2NUM(arrow_value.days));
      rb_hash_aset(value,
                   red_arrow::symbols::millisecond,
                   INT2NUM(arrow_value.milliseconds));
      return value;
    }

red_arrow::symbols::day は、以下のように定義してる。

rb_internとすると、整数ID型とやらにできる。

ID2SYMは整数ID型を受け取り、VALUE型(Rubyで扱うときはSymbol型)に変換してるから、ここでは、red_arrow::symbols::day は「:day」のことを指す。

red_arrow::symbols::day = ID2SYM(rb_intern("day"));

テストリファクタ

ARROW-15918 [Ruby] Support hash as argument of DayTimeIntervalArray.new by okadakk · Pull Request #12611 · apache/arrow · GitHub

こんな感じのクラスを追加すると、DayTimeIntervalArray.newした時に、好きな形式(今回はHash)でvalueを渡すことができた!

module Arrow
  class DayTimeIntervalArrayBuilder
    private
    def convert_to_arrow_value(value)
      if value.is_a?(Hash)
        Arrow::DayMillisecond.new(value[:day], value[:millisecond])
      else
        value
      end
    end
  end
end

感想

Ruby はすべてのデータがオブジェクトって聞いたことがあって、よく分かってなかったけど、今回で理解できた。

Cから受け取った値をRubyで扱うために、全てValue型にしないといけないというのを実感できて、本当に勉強になった。

この実装方法を教えてもらってから、Ruby gemのC実装のコードもほぼ読めるようになったし、書けるようにもなってきたから、本当に感謝!!