Jekyll2023-09-01T00:48:41+00:00https://otegami.github.io//feed.xmlOtegami LetterOtegami が技術的に気になったテーマをまとめるブログApache Arrow の Ruby バインディングに新機能を追加する 実装編 Part42023-08-31T12:28:45+00:002023-08-31T12:28:45+00:00https://otegami.github.io//red-data-tools/2023/08/31/implement-each-raw-record-in-arrow-table<h2 id="最初に">最初に</h2>
<p><a href="https://www.youtube.com/watch?v=n0NC_c88kDM">Red Data Tools開発者に聞け!Apache Arrow の Ruby バインディングに新機能を追加するシリーズ</a> の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。
今回は Part14 で紹介された内容をまとめていきます。</p>
<ul>
<li>動画で kou さんがサクッと解説してくださっています。動画で見たい方は上のリンクを参照してください!</li>
</ul>
<h2 id="記事の位置付け">記事の位置付け</h2>
<ol>
<li>追加したい機能を Ruby で実装してイメージを掴む</li>
<li>Ruby が提供する C の API を利用してどのように実装していくのか学ぶ</li>
<li>追加対象に関連するコードを読み解いていく</li>
<li>追加機能の実装を行う <- <strong>今回の記事はここ</strong></li>
</ol>
<h2 id="前回までのあらすじ">前回までのあらすじ</h2>
<p>前回は、既存の <code class="language-plaintext highlighter-rouge">Arrow::Table#raw_records</code> の内部実装を読むことを通してコードベースの理解を深めるとともに実装イメージを掴みました。
今回は、実際に <code class="language-plaintext highlighter-rouge">Arrow::Table#raw_each_record</code> の実装を行っていきましょう!一気に作業をすると大変なのでおおまかな作業の流れをまとめて少しずつ実装していきます。</p>
<h2 id="おおまかな作業の流れ">おおまかな作業の流れ</h2>
<p>下記の 4 つのステップで実装していこうと思います。</p>
<ol>
<li>Ruby C API で Ruby のメソッドを定義する</li>
<li>Ruby C API で呼び出す C++ のメソッドを定義する</li>
<li>列指向のデータ構造を行指向のデータ構造に変換するための RawRecordsProducer クラスを定義する</li>
<li>変換結果を逐次的に yield して返す RawRecordsProducer#produce の実装する</li>
</ol>
<h3 id="1-ruby-c-api-で-ruby-のメソッドを定義する">1. Ruby C API で Ruby のメソッドを定義する</h3>
<p><code class="language-plaintext highlighter-rouge">Arrow::Table#raw_records</code> が定義されていたように <code class="language-plaintext highlighter-rouge">Arrow::Table#each_raw_record</code> を定義してあげたいので同様な形で書いてあげましょう!</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ruby/red-arrow/ext/arrow/arrow.cpp</span>
<span class="k">auto</span> <span class="n">cArrowTable</span> <span class="o">=</span> <span class="n">rb_const_get_at</span><span class="p">(</span><span class="n">mArrow</span><span class="p">,</span> <span class="n">rb_intern</span><span class="p">(</span><span class="s">"Table"</span><span class="p">));</span>
<span class="n">rb_define_method</span><span class="p">(</span><span class="n">cArrowTable</span><span class="p">,</span> <span class="s">"each_raw_record"</span><span class="p">,</span>
<span class="k">reinterpret_cast</span><span class="o"><></span><span class="p">(</span><span class="s">"TODO: C++ のメソッドを定義する必要あり"</span><span class="p">)),</span>
<span class="mi">0</span><span class="p">);</span>
</code></pre></div></div>
<p>Ruby C API の <code class="language-plaintext highlighter-rouge">rb_define_method</code> を利用して、<code class="language-plaintext highlighter-rouge">Arrow::Table</code> に <code class="language-plaintext highlighter-rouge">each_raw_record</code> を定義します。
これで Ruby からは、Arrow::Table#each_raw_record が見えるようになります。ただ、Ruby の世界で呼ばれた時に実際に実行したい C++ レイヤーのメソッドが登録されていないので何もしないメソッドになってしまっているので、次に C++ レイヤーのメソッドを定義してあげます。</p>
<h3 id="2-ruby-c-api-で呼び出す-c-のメソッドを定義する">2. Ruby C API で呼び出す C++ のメソッドを定義する</h3>
<p>Ruby C API から実際に呼び出す C++ のメソッド <code class="language-plaintext highlighter-rouge">red_arrow::record_batch_each_raw_record</code> を定義してあげます。
まずはメソッドを定義したいだけなので Qnil を返すだけにしてあげます。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ruby/red-arrow/ext/arrow/arrow.cpp</span>
<span class="n">rb_define_method</span><span class="p">(</span><span class="n">cArrowTable</span><span class="p">,</span> <span class="s">"each_raw_record"</span><span class="p">,</span>
<span class="k">reinterpret_cast</span><span class="o"><</span><span class="n">rb</span><span class="o">::</span><span class="n">RawMethod</span><span class="o">></span><span class="p">(</span><span class="n">red_arrow</span><span class="o">::</span><span class="n">table_each_raw_record</span><span class="p">),</span>
<span class="mi">0</span><span class="p">);</span>
</code></pre></div></div>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ruby/red-arrow/ext/arrow/raw-records.cpp</span>
<span class="k">namespace</span> <span class="n">red_arrow</span> <span class="p">{</span>
<span class="k">namespace</span> <span class="p">{</span>
<span class="n">VALUE</span>
<span class="n">table_each_raw_record</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">rb_table</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">Qnil</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>実際にちゃんと呼ばれているのか少し試してみましょう!
変更をコンパイルして反映してあげてから、コンパイルされたオブジェクトファイルを参照してあげるようにして irb 上で確認します。</p>
<pre><code class="language-cosnole">% make -C ext/arrow/
% bundle exec env DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:/tmp/local/lib irb -I ext/arrow
irb(main):001:0> require 'arrow'
irb(main):002:0> Arrow::Table.new(number: [1, 2, 3]).each_raw_record
=> nil
</code></pre>
<p>無事に <code class="language-plaintext highlighter-rouge">Arrow::Table#each_raw_record</code> が実行されていそうですね。nil も返ってきていそうです。
では、いよいよ行毎に yield して返すイテレーター部分の実装に入っていきましょう!</p>
<h3 id="3-列指向のデータ構造を行指向のデータ構造に変換するための-rawrecordsproducer-クラスを定義する">3. 列指向のデータ構造を行指向のデータ構造に変換するための RawRecordsProducer クラスを定義する</h3>
<p><code class="language-plaintext highlighter-rouge">red_arrow::table_each_raw_record</code> メソッドから <code class="language-plaintext highlighter-rouge">red_arrow::table_raw_records</code> メソッドのようにカラム指向のデータ構造を列指向のデータ構造に変換する責務をになった <code class="language-plaintext highlighter-rouge">RawRecordsProducer</code> Class クラスを呼び出すようにしてあげましょう。</p>
<ul>
<li>なぜ、新しく <code class="language-plaintext highlighter-rouge">RawRecordsProducer</code> を定義するかというと前回見たように <code class="language-plaintext highlighter-rouge">RawRecordsBuilder</code> では全てのデータを変換してから値を返す実装になっており、今回実装したいイテレーターとは根本的な役割が異なるため新しく定義をしています。</li>
</ul>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ruby/red-arrow/ext/arrow/raw-records.cpp</span>
<span class="k">namespace</span> <span class="n">red_arrow</span> <span class="p">{</span>
<span class="k">namespace</span> <span class="p">{</span>
<span class="k">class</span> <span class="nc">RawRecordsProducer</span> <span class="o">:</span> <span class="k">private</span> <span class="n">Converter</span><span class="p">,</span> <span class="k">public</span> <span class="n">arrow</span><span class="o">::</span><span class="n">ArrayVisitor</span> <span class="p">{</span>
<span class="nl">public:</span>
<span class="k">explicit</span> <span class="n">RawRecordsProducer</span><span class="p">()</span>
<span class="o">:</span> <span class="n">Converter</span><span class="p">(),</span>
<span class="n">record_</span><span class="p">(</span><span class="n">Qnil</span><span class="p">),</span> <span class="c1">// 返却する行データを入れるの利用する</span>
<span class="n">column_index_</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span>
<span class="n">row_offset_</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">VALUE</span>
<span class="n">table_each_raw_record</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">rb_table</span><span class="p">)</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">garrow_table</span> <span class="o">=</span> <span class="n">GARROW_TABLE</span><span class="p">(</span><span class="n">RVAL2GOBJ</span><span class="p">(</span><span class="n">rb_table</span><span class="p">));</span>
<span class="k">auto</span> <span class="n">table</span> <span class="o">=</span> <span class="n">garrow_table_get_raw</span><span class="p">(</span><span class="n">garrow_table</span><span class="p">).</span><span class="n">get</span><span class="p">();</span>
<span class="k">try</span> <span class="p">{</span>
<span class="n">RawRecordsProducer</span> <span class="n">producer</span><span class="p">;</span> <span class="c1">// メインとなるのはココ</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">rb</span><span class="o">::</span><span class="n">State</span><span class="o">&</span> <span class="n">state</span><span class="p">)</span> <span class="p">{</span>
<span class="n">state</span><span class="p">.</span><span class="n">jump</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">Qnil</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>ここまでで、核となる「変換結果を逐次的に yield して返す」ロジック部分を実装するためのお膳立てができましたね。
では最後のロジックの実装に移っていきます。</p>
<h3 id="4-変換結果を逐次的に-yield-して返す-rawrecordsproducerproduce-の実装する">4. 変換結果を逐次的に yield して返す RawRecordsProducer#produce の実装する</h3>
<p>今回の主体となる <code class="language-plaintext highlighter-rouge">RawRecordsProducer</code> クラスに対して変換し行毎に返すお仕事をしてもらうようのメソッド <code class="language-plaintext highlighter-rouge">RawRecordsProducer#produce</code> を定義していきます。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ruby/red-arrow/ext/arrow/raw-records.cpp</span>
<span class="k">namespace</span> <span class="n">red_arrow</span> <span class="p">{</span>
<span class="k">namespace</span> <span class="p">{</span>
<span class="k">class</span> <span class="nc">RawRecordsProducer</span> <span class="o">:</span> <span class="k">private</span> <span class="n">Converter</span><span class="p">,</span> <span class="k">public</span> <span class="n">arrow</span><span class="o">::</span><span class="n">ArrayVisitor</span> <span class="p">{</span>
<span class="nl">public:</span>
<span class="kt">void</span> <span class="n">produce</span><span class="p">(</span><span class="k">const</span> <span class="n">arrow</span><span class="o">::</span><span class="n">Table</span><span class="o">&</span> <span class="n">table</span><span class="p">)</span> <span class="p">{</span>
<span class="n">rb</span><span class="o">::</span><span class="n">protect</span><span class="p">([</span><span class="o">&</span><span class="p">]</span> <span class="p">{</span>
<span class="k">const</span> <span class="k">auto</span> <span class="n">n_columns</span> <span class="o">=</span> <span class="n">table</span><span class="p">.</span><span class="n">num_columns</span><span class="p">();</span>
<span class="k">const</span> <span class="k">auto</span> <span class="n">n_rows</span> <span class="o">=</span> <span class="n">table</span><span class="p">.</span><span class="n">num_rows</span><span class="p">();</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">chunk_indexes</span><span class="p">(</span><span class="n">n_columns</span><span class="p">);</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="kt">int64_t</span><span class="o">></span> <span class="n">row_offsets</span><span class="p">(</span><span class="n">n_columns</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int64_t</span> <span class="n">i_row</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i_row</span> <span class="o"><</span> <span class="n">n_rows</span><span class="p">;</span> <span class="o">++</span><span class="n">i_row</span><span class="p">)</span> <span class="p">{</span>
<span class="n">record_</span> <span class="o">=</span> <span class="n">rb_ary_new_capa</span><span class="p">(</span><span class="n">n_columns</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i_column</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i_column</span> <span class="o"><</span> <span class="n">n_columns</span><span class="p">;</span> <span class="o">++</span><span class="n">i_column</span><span class="p">)</span> <span class="p">{</span>
<span class="n">column_index_</span> <span class="o">=</span> <span class="n">i_column</span><span class="p">;</span>
<span class="k">const</span> <span class="k">auto</span> <span class="n">chunked_array</span> <span class="o">=</span> <span class="n">table</span><span class="p">.</span><span class="n">column</span><span class="p">(</span><span class="n">i_column</span><span class="p">).</span><span class="n">get</span><span class="p">();</span>
<span class="k">auto</span><span class="o">&</span> <span class="n">chunk_index</span> <span class="o">=</span> <span class="n">chunk_indexes</span><span class="p">[</span><span class="n">i_column</span><span class="p">];</span>
<span class="k">auto</span><span class="o">&</span> <span class="n">row_offset</span> <span class="o">=</span> <span class="n">row_offsets</span><span class="p">[</span><span class="n">i_column</span><span class="p">];</span>
<span class="k">auto</span> <span class="n">array</span> <span class="o">=</span> <span class="n">chunked_array</span><span class="o">-></span><span class="n">chunk</span><span class="p">(</span><span class="n">chunk_index</span><span class="p">).</span><span class="n">get</span><span class="p">();</span>
<span class="k">while</span> <span class="p">(</span><span class="n">array</span><span class="o">-></span><span class="n">length</span><span class="p">()</span> <span class="o">==</span> <span class="n">row_offset</span><span class="p">)</span> <span class="p">{</span>
<span class="o">++</span><span class="n">chunk_index</span><span class="p">;</span>
<span class="n">row_offset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">array</span> <span class="o">=</span> <span class="n">chunked_array</span><span class="o">-></span><span class="n">chunk</span><span class="p">(</span><span class="n">chunk_index</span><span class="p">).</span><span class="n">get</span><span class="p">();</span>
<span class="p">}</span>
<span class="n">row_offset_</span> <span class="o">=</span> <span class="n">row_offset</span><span class="p">;</span>
<span class="n">check_status</span><span class="p">(</span><span class="n">array</span><span class="o">-></span><span class="n">Accept</span><span class="p">(</span><span class="k">this</span><span class="p">),</span>
<span class="s">"[table][each-raw-record]"</span><span class="p">);</span>
<span class="o">++</span><span class="n">row_offset</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">rb_yield</span><span class="p">(</span><span class="n">record_</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">Qnil</span><span class="p">;</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">VALUE</span>
<span class="n">table_each_raw_record</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">rb_table</span><span class="p">)</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">garrow_table</span> <span class="o">=</span> <span class="n">GARROW_TABLE</span><span class="p">(</span><span class="n">RVAL2GOBJ</span><span class="p">(</span><span class="n">rb_table</span><span class="p">));</span>
<span class="k">auto</span> <span class="n">table</span> <span class="o">=</span> <span class="n">garrow_table_get_raw</span><span class="p">(</span><span class="n">garrow_table</span><span class="p">).</span><span class="n">get</span><span class="p">();</span>
<span class="k">try</span> <span class="p">{</span>
<span class="n">RawRecordsProducer</span> <span class="n">producer</span><span class="p">;</span>
<span class="n">producer</span><span class="p">.</span><span class="n">produce</span><span class="p">(</span><span class="o">*</span><span class="n">table</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">rb</span><span class="o">::</span><span class="n">State</span><span class="o">&</span> <span class="n">state</span><span class="p">)</span> <span class="p">{</span>
<span class="n">state</span><span class="p">.</span><span class="n">jump</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">Qnil</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>一見 <code class="language-plaintext highlighter-rouge">produce</code> メソッドでやっていくことが複雑に見えますが、実は難しいことはしておらず大きく2つの仕事をしているだけです。(少し難しい部分としては、列のデータ量が多い場合は、 ChunkedArray という複数の要素から形成される場合も考慮する必要がある部分なのですが、今回はシンプルな解説にしたいので省いています。)</p>
<ul>
<li>列指向のデータ構造から 1 行単位になるようにデータを抜き出して、<code class="language-plaintext highlighter-rouge">record_</code> に格納してあげる</li>
<li><code class="language-plaintext highlighter-rouge">record_</code> に格納したデータを Ruby C API が提供する <code class="language-plaintext highlighter-rouge">rb_yield</code> に渡してあげて、1 行単位のデータを返してあげる</li>
</ul>
<p>ここまでで、<code class="language-plaintext highlighter-rouge">Arrow::Table#each_raw_record</code> の実装は完了です。実際に動くか確認してみましょう。</p>
<p>変更をコンパイルして反映してあげてから、コンパイルされたオブジェクトファイルを参照してあげるようにして irb 上で確認します。
ちゃんと行毎のデータがブロックに渡されて実行されていることがわかりました。問題なさそうですね。お疲れ様です!</p>
<pre><code class="language-cosnole">% make -C ext/arrow/
% bundle exec env DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:/tmp/local/lib irb -I ext/arrow
irb(main):001:0> require 'arrow'
irb(main):002:0> Arrow::Table.new(number: [1, 2, 3]).each_raw_record { p _1 }
[1]
[2]
[3]
=> nil
</code></pre>
<h3 id="最後に">最後に</h3>
<p>「Apache Arrow の Ruby バインディングに新機能を追加する」を通して、「動的ライブラリとは」から「Rubyの仕組み」まで幅広い学びがありました。恥ずかしい話なのですが、普段は作成されたバインディングをライブラリ(gem)を通して利用するだけであったため何も知りませんでした。どこかの Rubyist が便利な機能を Ruby で使えるように道を用意してくれているから何も気にせずに私は利用できていたんだなと実感しました。</p>
<p>ただ、今回の体験を通して Ruby バインディングの作り方の基礎は理解できたのかなぁと思うので、これからは Rubyist が便利になる世界を作るのに貢献する側に回っていけたらと思っています!</p>
<p>最後に、全 15 回に及んでわかりやすく解説してくださった <a href="https://github.com/kou">kou</a> さんと一緒に開発に取り組んでくださった <a href="https://github.com/mterada1228">mterada1228</a> さんに改めてこの場でも感謝を伝えられたらと思います。本当にありがとうございました!!</p>最初に Red Data Tools開発者に聞け!Apache Arrow の Ruby バインディングに新機能を追加するシリーズ の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。 今回は Part14 で紹介された内容をまとめていきます。 動画で kou さんがサクッと解説してくださっています。動画で見たい方は上のリンクを参照してください!Symbol not found in flat namespace - arrow.bundle(LoadError)2023-08-08T23:37:19+00:002023-08-08T23:37:19+00:00https://otegami.github.io//red-data-tools/2023/08/08/debug-ruby-binding-for-arrow<h2 id="最初に">最初に</h2>
<p><a href="https://youtu.be/CNChc_WnAE0?list=PLKb0MEIU7gvQOIACKgdgKuAE7cMPDuTE6&t=1550">Red Data Tools開発者に聞け!Apache Arrow の Ruby バインディングに新機能を追加するシリーズ</a> の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。
今回は Part12 で紹介された内容をまとめていきます。</p>
<ul>
<li>動画で kou さんがサクッと解説してくださっています。動画で見たい方は上のリンクを参照してください!</li>
</ul>
<h2 id="遭遇した問題">遭遇した問題</h2>
<p>今回は、Red Arrow のテスト実行時に遭遇した下記のエラーになります。
具体的には、対象のシンボルが見つからず LoadError になっている状態です。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% pwd
/dev/project/arrow/ruby/red-arrowdev/project/arrow/ruby/red-arrow
</span><span class="gp">red-arrow % bundle exec env DYLD_LIBRARY_PATH=$</span>DYLD_LIBRARY_PATH:/tmp/local/lib <span class="si">$(</span>rbenv which ruby<span class="si">)</span> <span class="nb">test</span>/run-test.rb
<span class="go">/dev/project/arrow/ruby/red-arrow/lib/arrow/loader.rb:146:in `require': dlopen(/dev/project/arrow/ruby/red-arrow/ext/arrow/arrow.bundle, 0x0009): symbol not found in flat namespace '__ZN9red_arrow21table_each_raw_recordEm' - /dev/project/arrow/ruby/red-arrow/ext/arrow/arrow.bundle (LoadError)
from /dev/project/arrow/ruby/red-arrow/lib/arrow/loader.rb:146:in `require_extension_library'
from /dev/project/arrow/ruby/red-arrow/lib/arrow/loader.rb:31:in `post_load'
from /.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/gobject-introspection-4.1.6/lib/gobject-introspection/loader.rb:49:in `block in load'
from /.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/gobject-introspection-4.1.6/lib/gobject-introspection/loader.rb:636:in `prepare_class'
from /.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/gobject-introspection-4.1.6/lib/gobject-introspection/loader.rb:41:in `load'
from /.rbenv/versions/3.2.0/lib/ruby/gems/3.2.0/gems/gobject-introspection-4.1.6/lib/gobject-introspection/loader.rb:25:in `load'
from /dev/project/arrow/ruby/red-arrow/lib/arrow/loader.rb:24:in `load'
</span><span class="gp"> from /dev/project/arrow/ruby/red-arrow/lib/arrow.rb:29:in `<module:Arrow></span><span class="s1">'
</span><span class="gp"> from /dev/project/arrow/ruby/red-arrow/lib/arrow.rb:25:in `<top (required)></span><span class="s1">'</span>
<span class="go"> from /dev/project/arrow/ruby/red-arrow/test/helper.rb:18:in `require'
</span><span class="gp"> from /dev/project/arrow/ruby/red-arrow/test/helper.rb:18:in `<top (required)></span><span class="s1">'
</span><span class="go"> from test/run-test.rb:67:in `require_relative'
</span><span class="gp"> from test/run-test.rb:67:in `<main></span><span class="s1">'
</span></code></pre></div></div>
<h2 id="対応策">対応策</h2>
<p>エラーを見ると <code class="language-plaintext highlighter-rouge">__ZN9red_arrow21table_each_raw_recordEm</code> のようにマングリングされたシンボル情報が出ているので下記のコマンド(c++filt)でデマングルしてあげると、具体的に見つからないシンボル名を見ることができます。今回は、<code class="language-plaintext highlighter-rouge">red_arrow::table_each_raw_record</code> が見つかっていないようです。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% c++filt
__ZN9red_arrow21table_each_raw_recordEm
red_arrow::table_each_raw_record(unsigned long)
</span></code></pre></div></div>
<p>ソースコード内で <code class="language-plaintext highlighter-rouge">red_arrow::table_each_raw_record</code> に関する部分を探すと <code class="language-plaintext highlighter-rouge">red_arrow::table_each_raw_records</code> のようにタイポしているのを発見しました。タイポを修正し再度コンパイルを行った後に実行してあげると無事に実行できました。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">% bundle exec env DYLD_LIBRARY_PATH=$</span>DYLD_LIBRARY_PATH:/tmp/local/lib <span class="si">$(</span>rbenv which ruby<span class="si">)</span> <span class="nt">-I</span> ext/arrow sample.rb
<span class="go">[1, false]
[2, true]
[3, false]
[4, true]
</span></code></pre></div></div>
<p>ここからは、マングリングって何?と気になる人 or 上記で解決しない場合のもう少ししっかりしたデバッグ知識を得たい人向けになります。</p>
<h3 id="マングリングとは">マングリングとは?</h3>
<p>C++はRubyと異なり、ネームスペース、クラス、関数のオーバーロードなどで同じ名前を持つ要素が存在します。コンパイルしてバイナリに変換する際、これらの名前はリンカーによって区別される必要があります。</p>
<p>ここで出てくるのが、「マングリング(Name Mangling)」です。マングリングは、コンパイラがバイナリレベルで名前を一意にするための自動変換を指します。この変換により、異なるネームスペース、クラス、オーバーロードされた関数も一意の名前を持つようになるのです。(バイナリ内の関数名などは「シンボル」として呼ばれます。)</p>
<p>マングリングを通じて、リンカーは関数や変数を正確に識別し、適切にリンクすることができるのです。</p>
<p>ref: <a href="https://www.ibm.com/docs/ja/i/7.5?topic=linkage-name-mangling-c-only">https://www.ibm.com/docs/ja/i/7.5?topic=linkage-name-mangling-c-only</a></p>
<h3 id="デマングルとは">デマングルとは?</h3>
<p>マングリングされたシンボルを元の名前に戻す変換になります。<code class="language-plaintext highlighter-rouge">c++filt</code> を利用することでデマングルを行うことができます。</p>
<p>ref: <a href="https://www.ibm.com/docs/ja/xl-c-and-cpp-aix/16.1?topic=utilities-demangling-compiled-c-names">https://www.ibm.com/docs/ja/xl-c-and-cpp-aix/16.1?topic=utilities-demangling-compiled-c-names</a></p>
<h2 id="対応策詳細編">対応策(詳細編)</h2>
<p>デマングルすることで <code class="language-plaintext highlighter-rouge">red_arrow::table_each_raw_record</code> に問題があることがわかりました。
では本当にこのシンボルがバイナリファイル(<code class="language-plaintext highlighter-rouge">arrow.bundle</code>)に存在しないのかを確認してみましょう!</p>
<h3 id="バイナリファイル内からシンボルを探す">バイナリファイル内からシンボルを探す</h3>
<p>バイナリファイルは、人間には読みづらいので <code class="language-plaintext highlighter-rouge">nm</code> コマンドでファイル内のシンボル情報を表示してもらいましょう。</p>
<ul>
<li>今回は、デマングルも同時に行いたいので <code class="language-plaintext highlighter-rouge">-C</code> オプションをつけてあげましょう。</li>
</ul>
<p>下記のような結果が出てきましたので、それぞれが意味するところをみていきましょう!</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% nm -C ext/arrow/arrow.bundle | grep red_arrow::table_each_raw_record
U red_arrow::table_each_raw_record(unsigned long)
00000000000077d8 T red_arrow::table_each_raw_records(unsigned long)
</span></code></pre></div></div>
<p>左からアドレス(シンボルのオフセット)シンボルのタイプ、シンボル名を表しています。
では、<code class="language-plaintext highlighter-rouge">U</code> と <code class="language-plaintext highlighter-rouge">T</code> はそれぞれどういったシンボルのタイプなのかを確認してみましょう。
<code class="language-plaintext highlighter-rouge">man</code> コマンドで <code class="language-plaintext highlighter-rouge">nm</code> の説明を見ていきます。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% man nm
</code></pre></div></div>
<p>どうやら、U (undefined) は未定義状態、T (text section symbol)は、テキストシンボルであることがわかりました。
つまり、<code class="language-plaintext highlighter-rouge">red_arrow::table_each_raw_record</code> は未定義状態であり、<code class="language-plaintext highlighter-rouge">red_arrow::table_each_raw_records</code> は定義されていることがわかります。
なので、今回はタイポしていそうなことがわかります。</p>
<h3 id="個人的に思ったこと">個人的に思ったこと</h3>
<p>バイナリファイル(オブジェクトファイル)をデバックするなどと聞くと身構えてしまいますが、人間が理解しやすい形にツールを利用し変換してあげれば、簡単にデバックできることを学びました。デバックができればあとは Trial and error で学んでいけそうですね!</p>最初に Red Data Tools開発者に聞け!Apache Arrow の Ruby バインディングに新機能を追加するシリーズ の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。 今回は Part12 で紹介された内容をまとめていきます。 動画で kou さんがサクッと解説してくださっています。動画で見たい方は上のリンクを参照してください!Apache Arrow の Ruby バインディングに新機能を追加する 実装編 Part32023-07-26T11:23:15+00:002023-07-26T11:23:15+00:00https://otegami.github.io//red-data-tools/2023/07/26/read-raw-records-in-arrow-table<h2 id="最初に">最初に</h2>
<p><a href="https://www.youtube.com/playlist?list=PLKb0MEIU7gvQOIACKgdgKuAE7cMPDuTE6">Red Data Tools開発者に聞け!Apache Arrow の Ruby バインディングに新機能を追加するシリーズ</a> の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。
今回は Part9 で紹介された内容をまとめていきます。</p>
<ul>
<li>動画で kou さんがサクッと解説してくださっています。動画で見たい方は上のリンクを参照してください!</li>
</ul>
<h2 id="記事の位置付け">記事の位置付け</h2>
<ol>
<li>追加したい機能を Ruby で実装してイメージを掴む</li>
<li>Ruby が提供する C の API を利用してどのように実装していくのか学ぶ</li>
<li>追加対象に関連するコードを読み解いていく <- <strong>今回の記事はここ</strong></li>
<li>追加機能の実装を行う</li>
</ol>
<h2 id="前回までのあらすじ">前回までのあらすじ</h2>
<p>前回は、Ruby の拡張ライブラリを実装する際の簡単なお作法や C API の利用方法を押さえていく中で、Ruby の C API を利用して大まかにどのような流れで実装していくのかを見ていきました。
前回でおおまかな流れは抑えられたので、今回は実装に必要な知識を得るために内部実装を読んでいきましょう!</p>
<h2 id="内部実装を読んでいくための入り口を決めよう">内部実装を読んでいくための入り口を決めよう</h2>
<p>今回は <code class="language-plaintext highlighter-rouge">Arrow::Table#raw_records</code> イテラブルな結果を返すバージョンの <code class="language-plaintext highlighter-rouge">Arrow::Table#raw_each_record</code> を実装したいので、<code class="language-plaintext highlighter-rouge">Arrow::Table#raw_records</code> を読んでいきたいと思います!</p>
<p>すでに「Part1」で確認済みですが、実際にどのような結果が返ってくるのかを念の為おさらいをしていきます。
<code class="language-plaintext highlighter-rouge">Arrow::Table</code> で表現されるような表形式のデータを <code class="language-plaintext highlighter-rouge">Arrow::Table#raw_records</code> を利用することで行形式のデータに変換した値が返ってきます。メソッドの動作についての理解が深まったと思うので、次はその実装を詳しく見ていきましょう!</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s1">'arrow'</span>
<span class="n">table</span> <span class="o">=</span> <span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">number: </span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="ss">string: </span><span class="p">[</span><span class="s1">'a'</span><span class="p">,</span> <span class="s1">'b'</span><span class="p">,</span> <span class="s1">'c'</span><span class="p">])</span>
<span class="nb">p</span> <span class="n">table</span>
<span class="c1">#<Arrow::Table:0x1050f2c00 ptr=0x106cfcbb0></span>
<span class="n">number</span> <span class="n">string</span>
<span class="mi">0</span> <span class="mi">1</span> <span class="n">a</span>
<span class="mi">1</span> <span class="mi">2</span> <span class="n">b</span>
<span class="mi">2</span> <span class="mi">3</span> <span class="n">c</span>
<span class="nb">p</span> <span class="n">table</span><span class="p">.</span><span class="nf">raw_records</span>
<span class="p">[[</span><span class="mi">1</span><span class="p">,</span> <span class="s2">"a"</span><span class="p">],</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="s2">"b"</span><span class="p">],</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="s2">"c"</span><span class="p">]]</span>
</code></pre></div></div>
<h3 id="arrowtableraw_records-の実装を紐解いていく">Arrow::Table#raw_records の実装を紐解いていく</h3>
<p>Ruby の C API <code class="language-plaintext highlighter-rouge">rb_define_method</code> を利用して、<code class="language-plaintext highlighter-rouge">raw_records</code> は定義されています。
メソッドの実態はどうやら <code class="language-plaintext highlighter-rouge">red_arrow::table_raw_records</code> で実装されていそうですね。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ruby/red-arrow/ext/arrow/arrow.cpp</span>
<span class="k">auto</span> <span class="n">cArrowTable</span> <span class="o">=</span> <span class="n">rb_const_get_at</span><span class="p">(</span><span class="n">mArrow</span><span class="p">,</span> <span class="n">rb_intern</span><span class="p">(</span><span class="s">"Table"</span><span class="p">));</span>
<span class="n">rb_define_method</span><span class="p">(</span><span class="n">cArrowTable</span><span class="p">,</span> <span class="s">"raw_records"</span><span class="p">,</span>
<span class="k">reinterpret_cast</span><span class="o"><</span><span class="n">rb</span><span class="o">::</span><span class="n">RawMethod</span><span class="o">></span><span class="p">(</span><span class="n">red_arrow</span><span class="o">::</span><span class="n">table_raw_records</span><span class="p">),</span>
<span class="mi">0</span><span class="p">);</span>
</code></pre></div></div>
<p>ref: <a href="https://github.com/apache/arrow/blob/main/ruby/red-arrow/ext/arrow/arrow.cpp#L86-L89">https://github.com/apache/arrow/blob/main/ruby/red-arrow/ext/arrow/arrow.cpp#L86-L89</a></p>
<h3 id="red_arrowtable_raw_records">red_arrow::table_raw_records</h3>
<p><code class="language-plaintext highlighter-rouge">red_arrow::table_raw_records</code> の実装は下記のようになっています。
今回は全てを読み解いていくのは大変なので主要な部分を Pick Up して読んでいきます。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ruby/red-arrow/ext/arrow/raw-records.cpp</span>
<span class="k">namespace</span> <span class="n">red_arrow</span> <span class="p">{</span>
<span class="n">VALUE</span>
<span class="n">table_raw_records</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">rb_table</span><span class="p">)</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">garrow_table</span> <span class="o">=</span> <span class="n">GARROW_TABLE</span><span class="p">(</span><span class="n">RVAL2GOBJ</span><span class="p">(</span><span class="n">rb_table</span><span class="p">));</span>
<span class="k">auto</span> <span class="n">table</span> <span class="o">=</span> <span class="n">garrow_table_get_raw</span><span class="p">(</span><span class="n">garrow_table</span><span class="p">).</span><span class="n">get</span><span class="p">();</span>
<span class="k">const</span> <span class="k">auto</span> <span class="n">n_rows</span> <span class="o">=</span> <span class="n">table</span><span class="o">-></span><span class="n">num_rows</span><span class="p">();</span>
<span class="k">const</span> <span class="k">auto</span> <span class="n">n_columns</span> <span class="o">=</span> <span class="n">table</span><span class="o">-></span><span class="n">num_columns</span><span class="p">();</span>
<span class="k">auto</span> <span class="n">records</span> <span class="o">=</span> <span class="n">rb_ary_new_capa</span><span class="p">(</span><span class="n">n_rows</span><span class="p">);</span>
<span class="k">try</span> <span class="p">{</span>
<span class="n">RawRecordsBuilder</span> <span class="n">builder</span><span class="p">(</span><span class="n">records</span><span class="p">,</span> <span class="n">n_columns</span><span class="p">);</span>
<span class="n">builder</span><span class="p">.</span><span class="n">build</span><span class="p">(</span><span class="o">*</span><span class="n">table</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">rb</span><span class="o">::</span><span class="n">State</span><span class="o">&</span> <span class="n">state</span><span class="p">)</span> <span class="p">{</span>
<span class="n">state</span><span class="p">.</span><span class="n">jump</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">records</span><span class="p">;</span><span class="err"> </span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>ref: <a href="https://github.com/apache/arrow/blob/main/ruby/red-arrow/ext/arrow/raw-records.cpp#L167-L183">https://github.com/apache/arrow/blob/main/ruby/red-arrow/ext/arrow/raw-records.cpp#L167-L183</a></p>
<p>まず確認したいのがメソッドの戻り値の型です。これは <code class="language-plaintext highlighter-rouge">VALUE</code> 型になっています。
<a href="https://docs.ruby-lang.org/en/3.0/extension_rdoc.html#label-Basic+Knowledge">実は C レベルで Ruby の値の型は全て VALUE 型で表現されます</a>。なので、ここでは戻り値も引数も <code class="language-plaintext highlighter-rouge">VALUE</code> 型として表現されています。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">VALUE</span>
<span class="nf">table_raw_records</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">rb_table</span><span class="p">)</span> <span class="p">{</span> <span class="p">}</span>
</code></pre></div></div>
<p>次に確認したいのが下記の部分です。C++ では Ruby とは異なり配列の大きさを動的に変化させるにはコストがかかるので、決められるところでは事前に大きさを決めておくのが大切です。
<code class="language-plaintext highlighter-rouge">n_rows</code> の分だけ <code class="language-plaintext highlighter-rouge">rb_ary_new_capa</code> を利用して配列の大きさを確保しています。今回の例ですと <code class="language-plaintext highlighter-rouge">[[1, "a"], [2, "b"], [3, "c"]]</code> のような値が返るので <code class="language-plaintext highlighter-rouge">3</code> とかになりますね。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">auto</span> <span class="n">records</span> <span class="o">=</span> <span class="n">rb_ary_new_capa</span><span class="p">(</span><span class="n">n_rows</span><span class="p">);</span>
</code></pre></div></div>
<p>最後にメソッドの中核となる部分ですね。
RawRecordsBuilder class のインスタンスを生成して <code class="language-plaintext highlighter-rouge">build</code> メソッドを呼んでいます。
次はここで実際になにをやっているのかを見ていきます。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">RawRecordsBuilder</span> <span class="nf">builder</span><span class="p">(</span><span class="n">records</span><span class="p">,</span> <span class="n">n_columns</span><span class="p">);</span>
<span class="n">builder</span><span class="p">.</span><span class="n">build</span><span class="p">(</span><span class="o">*</span><span class="n">table</span><span class="p">);</span>
</code></pre></div></div>
<p>その前に最後は <code class="language-plaintext highlighter-rouge">records</code> を返していますね。
メソッドの戻り値としての <code class="language-plaintext highlighter-rouge">[[1, "a"], [2, "b"], [3, "c"]]</code> を返している部分ですね。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="n">records</span><span class="p">;</span>
</code></pre></div></div>
<h3 id="rawrecordsbuilderbuild">RawRecordsBuilder#build</h3>
<p><code class="language-plaintext highlighter-rouge">RawRecordsBuilder</code> の実装は下記のようになっています。
こちらも全てを読み解いていくのは大変なので主要な部分を Pick Up して読んでいきます。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ruby/red-arrow/ext/arrow/raw-records.cpp</span>
<span class="k">class</span> <span class="nc">RawRecordsBuilder</span> <span class="o">:</span> <span class="k">private</span> <span class="n">Converter</span><span class="p">,</span> <span class="k">public</span> <span class="n">arrow</span><span class="o">::</span><span class="n">ArrayVisitor</span> <span class="p">{</span>
<span class="nl">public:</span>
<span class="k">explicit</span> <span class="n">RawRecordsBuilder</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">records</span><span class="p">,</span> <span class="kt">int</span> <span class="n">n_columns</span><span class="p">)</span>
<span class="o">:</span> <span class="n">Converter</span><span class="p">(),</span>
<span class="n">records_</span><span class="p">(</span><span class="n">records</span><span class="p">),</span>
<span class="n">n_columns_</span><span class="p">(</span><span class="n">n_columns</span><span class="p">),</span>
<span class="n">is_produce_mode_</span><span class="p">(</span><span class="nb">false</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">build</span><span class="p">(</span><span class="k">const</span> <span class="n">arrow</span><span class="o">::</span><span class="n">Table</span><span class="o">&</span> <span class="n">table</span><span class="p">)</span> <span class="p">{</span>
<span class="n">rb</span><span class="o">::</span><span class="n">protect</span><span class="p">([</span><span class="o">&</span><span class="p">]</span> <span class="p">{</span>
<span class="k">const</span> <span class="k">auto</span> <span class="n">n_rows</span> <span class="o">=</span> <span class="n">table</span><span class="p">.</span><span class="n">num_rows</span><span class="p">();</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int64_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">n_rows</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">record</span> <span class="o">=</span> <span class="n">rb_ary_new_capa</span><span class="p">(</span><span class="n">n_columns_</span><span class="p">);</span>
<span class="n">rb_ary_push</span><span class="p">(</span><span class="n">records_</span><span class="p">,</span> <span class="n">record</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">n_columns_</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="k">const</span> <span class="k">auto</span><span class="o">&</span> <span class="n">chunked_array</span> <span class="o">=</span> <span class="n">table</span><span class="p">.</span><span class="n">column</span><span class="p">(</span><span class="n">i</span><span class="p">).</span><span class="n">get</span><span class="p">();</span>
<span class="n">column_index_</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
<span class="n">row_offset_</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="k">auto</span> <span class="n">array</span> <span class="o">:</span> <span class="n">chunked_array</span><span class="o">-></span><span class="n">chunks</span><span class="p">())</span> <span class="p">{</span>
<span class="n">check_status</span><span class="p">(</span><span class="n">array</span><span class="o">-></span><span class="n">Accept</span><span class="p">(</span><span class="k">this</span><span class="p">),</span>
<span class="s">"[table][raw-records]"</span><span class="p">);</span>
<span class="n">row_offset_</span> <span class="o">+=</span> <span class="n">array</span><span class="o">-></span><span class="n">length</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">Qnil</span><span class="p">;</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>ref: <a href="https://github.com/apache/arrow/blob/main/ruby/red-arrow/ext/arrow/raw-records.cpp#L50-L68">https://github.com/apache/arrow/blob/main/ruby/red-arrow/ext/arrow/raw-records.cpp#L50-L68</a></p>
<p>こちらが、RawRecordsBuilder のコンストラクターになります。
特に複雑なことはやっておらず、引数で受け取った <code class="language-plaintext highlighter-rouge">records</code> と <code class="language-plaintext highlighter-rouge">n_columns</code> を、それぞれ <code class="language-plaintext highlighter-rouge">records_</code> と <code class="language-plaintext highlighter-rouge">n_columns_</code> に格納しています。
C++ には Ruby と違いインスタンス変数の概念がないので変数名の最後に <code class="language-plaintext highlighter-rouge">_</code> をつけるなどコーディングスタイルでの識別を行うことが多いようです。なので <code class="language-plaintext highlighter-rouge">records_</code> と <code class="language-plaintext highlighter-rouge">n_columns_</code> はインスタンス変数としてこれから扱っていきます。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">explicit</span> <span class="nf">RawRecordsBuilder</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">records</span><span class="p">,</span> <span class="kt">int</span> <span class="n">n_columns</span><span class="p">)</span>
<span class="o">:</span> <span class="n">Converter</span><span class="p">(),</span>
<span class="n">records_</span><span class="p">(</span><span class="n">records</span><span class="p">),</span>
<span class="n">n_columns_</span><span class="p">(</span><span class="n">n_columns</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
</code></pre></div></div>
<p>次に、<code class="language-plaintext highlighter-rouge">RawRecordsBuilder#build</code> を見ていきます。
<code class="language-plaintext highlighter-rouge">build</code> メソッド内でも色々やっていそうですね。一つずつ見ていきます。</p>
<p>まず最初にやっているのは、行の数分だけ格納用の領域を確保するということをやっています。
<code class="language-plaintext highlighter-rouge">record</code> ローカル変数に <code class="language-plaintext highlighter-rouge">rb_ary_new_capa</code> を利用しカラムの数ぶんだけの領域を確保した配列を代入しています。
その <code class="language-plaintext highlighter-rouge">record</code> を <code class="language-plaintext highlighter-rouge">rb_ary_push</code> を通して <code class="language-plaintext highlighter-rouge">records_</code> に代入して行ってます。今回の例で言うと <code class="language-plaintext highlighter-rouge">[[], [], []]</code> のような構造が作られたイメージです。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">build</span><span class="p">(</span><span class="k">const</span> <span class="n">arrow</span><span class="o">::</span><span class="n">Table</span><span class="o">&</span> <span class="n">table</span><span class="p">)</span> <span class="p">{</span>
<span class="n">rb</span><span class="o">::</span><span class="n">protect</span><span class="p">([</span><span class="o">&</span><span class="p">]</span> <span class="p">{</span>
<span class="k">const</span> <span class="k">auto</span> <span class="n">n_rows</span> <span class="o">=</span> <span class="n">table</span><span class="p">.</span><span class="n">num_rows</span><span class="p">();</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int64_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">n_rows</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">record</span> <span class="o">=</span> <span class="n">rb_ary_new_capa</span><span class="p">(</span><span class="n">n_columns_</span><span class="p">);</span>
<span class="n">rb_ary_push</span><span class="p">(</span><span class="n">records_</span><span class="p">,</span> <span class="n">record</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// ...</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<p>次は、先ほど作った構造に実際に値を代入をしていく部分になります。
見ていく前にここから少し Arrow 形式のデータ構造を元にしたコードになっています。なので今回全てを説明しませんが、気になる方は<a href="https://youtu.be/bpuJWC9_USY?t=273">こちらの動画</a>を見てみてください。</p>
<p><code class="language-plaintext highlighter-rouge">table.column(i).get()</code> では、表形式なデータ Arrow Table からカラム毎のデータを取得しています。
今回の例で言うと、<code class="language-plaintext highlighter-rouge">number: [1, 2, 3]</code> や <code class="language-plaintext highlighter-rouge">string: ['a', 'b', 'c']</code> に相当する部分になります。最初の loop では <code class="language-plaintext highlighter-rouge">number</code> のみの配列を処理し次の loop
では <code class="language-plaintext highlighter-rouge">string</code> のみの配列を処理するみたいな形です。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">n_columns_</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="k">const</span> <span class="k">auto</span><span class="o">&</span> <span class="n">chunked_array</span> <span class="o">=</span> <span class="n">table</span><span class="p">.</span><span class="n">column</span><span class="p">(</span><span class="n">i</span><span class="p">).</span><span class="n">get</span><span class="p">();</span>
<span class="n">column_index_</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
<span class="n">row_offset_</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>
<p>そして、渡された配列毎に <code class="language-plaintext highlighter-rouge">check_status(array->Accept(this), "[table][raw-records]")</code> の処理を呼んでいます。実際にどんなことをしているのか見ていきましょう。</p>
<ul>
<li>具体的に渡される配列は、<code class="language-plaintext highlighter-rouge">[1, 2, 3]</code> みたいな構造が渡されるイメージです(厳密には違うのですが簡略化のため)</li>
</ul>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="k">auto</span> <span class="n">array</span> <span class="o">:</span> <span class="n">chunked_array</span><span class="o">-></span><span class="n">chunks</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// ここの構造は今回説明しませんが、カラム毎の配列が渡されるイメージでいてください</span>
<span class="n">check_status</span><span class="p">(</span><span class="n">array</span><span class="o">-></span><span class="n">Accept</span><span class="p">(</span><span class="k">this</span><span class="p">),</span>
<span class="s">"[table][raw-records]"</span><span class="p">);</span>
<span class="n">row_offset_</span> <span class="o">+=</span> <span class="n">array</span><span class="o">-></span><span class="n">length</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>今回の大事な部分が <code class="language-plaintext highlighter-rouge">array->Accept(this)</code> になります。ここでは Accept に渡された <code class="language-plaintext highlighter-rouge">VISIT</code> メソッドを呼ぶようになっています。
では、<code class="language-plaintext highlighter-rouge">RawRecordsBuilder</code> で定義されている <code class="language-plaintext highlighter-rouge">VISIT</code> メソッドを実際に見てみましょう。
下記で少しだけ詳しくみると下記のようになっていますが、今回は説明を一旦省略します。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// cpp/src/arrow/array/array_base.cc</span>
<span class="n">Status</span> <span class="n">Array</span><span class="o">::</span><span class="n">Accept</span><span class="p">(</span><span class="n">ArrayVisitor</span><span class="o">*</span> <span class="n">visitor</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">VisitArrayInline</span><span class="p">(</span><span class="o">*</span><span class="k">this</span><span class="p">,</span> <span class="n">visitor</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// cpp/src/arrow/visit_array_inline.h</span>
<span class="k">template</span> <span class="o"><</span><span class="k">typename</span> <span class="nc">VISITOR</span><span class="p">,</span> <span class="k">typename</span><span class="o">...</span> <span class="nc">ARGS</span><span class="p">></span>
<span class="kr">inline</span> <span class="n">Status</span> <span class="nf">VisitArrayInline</span><span class="p">(</span><span class="k">const</span> <span class="n">Array</span><span class="o">&</span> <span class="n">array</span><span class="p">,</span> <span class="n">VISITOR</span><span class="o">*</span> <span class="n">visitor</span><span class="p">,</span> <span class="n">ARGS</span><span class="o">&&</span><span class="p">...</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">array</span><span class="p">.</span><span class="n">type_id</span><span class="p">())</span> <span class="p">{</span>
<span class="n">ARROW_GENERATE_FOR_ALL_TYPES</span><span class="p">(</span><span class="n">ARRAY_VISIT_INLINE</span><span class="p">);</span>
<span class="nl">default:</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">Status</span><span class="o">::</span><span class="n">NotImplemented</span><span class="p">(</span><span class="s">"Type not implemented"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// cpp/src/arrow/visit_array_inline.h</span>
<span class="cp">#define ARRAY_VISIT_INLINE(TYPE_CLASS)
</span> <span class="k">case</span> <span class="n">TYPE_CLASS</span><span class="err">##</span><span class="n">Type</span><span class="o">::</span><span class="n">type_id</span><span class="p">:</span>
<span class="k">return</span> <span class="n">visitor</span><span class="o">-></span><span class="n">Visit</span><span class="p">(</span>
<span class="n">internal</span><span class="o">::</span><span class="n">checked_cast</span><span class="o"><</span><span class="k">const</span> <span class="k">typename</span> <span class="n">TypeTraits</span><span class="o"><</span><span class="n">TYPE_CLASS</span><span class="err">##</span><span class="n">Type</span><span class="o">>::</span><span class="n">ArrayType</span><span class="o">&></span><span class="p">(</span>
<span class="n">array</span><span class="p">),</span>
<span class="n">std</span><span class="o">::</span><span class="n">forward</span><span class="o"><</span><span class="n">ARGS</span><span class="o">></span><span class="p">(</span><span class="n">args</span><span class="p">)...);</span>
</code></pre></div></div>
<h3 id="rawrecordsbuildervisitこの表記は正確ではないですmm">RawRecordsBuilder::VISIT(この表記は正確ではないですmm)</h3>
<p>ここでは、渡された配列の型ごとに適切な関数を呼ぶような形で作られています。いわゆる Visitor パターンというやつですね。(<a href="https://youtu.be/6Zb_jEKKznk?list=PLKb0MEIU7gvTj2_vQJMUK6HUu_R6V9oX5&t=155">Visitor パターンの説明はここでされてます。</a>)
そして実際に visit されて実行されるのは、<code class="language-plaintext highlighter-rouge">convert(array)</code> 関数になります。実はこれが今回の最も重要な部分になります。みていきましょう!</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ruby/red-arrow/ext/arrow/raw-records.cpp</span>
<span class="cp">#define VISIT(TYPE) \
arrow::Status Visit(const arrow::TYPE ## Array& array) override { \
convert(array); \
return arrow::Status::OK(); \
}
</span></code></pre></div></div>
<p>ref: <a href="https://github.com/apache/arrow/blob/main/ruby/red-arrow/ext/arrow/raw-records.cpp#L71-L75">https://github.com/apache/arrow/blob/main/ruby/red-arrow/ext/arrow/raw-records.cpp#L71-L75</a></p>
<h3 id="rawrecordsbuilder-convert">RawRecordsBuilder convert</h3>
<p><code class="language-plaintext highlighter-rouge">convert</code> メソッド内では大きく二つの分岐があります。渡された配列に null となる値があるか否かです。
今回は、よりシンプルな null が存在しない場合の処理( else の部分)を追っていきます。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ruby/red-arrow/ext/arrow/raw-records.cpp</span>
<span class="k">template</span> <span class="o"><</span><span class="k">typename</span> <span class="nc">ArrayType</span><span class="p">></span>
<span class="kt">void</span> <span class="nf">convert</span><span class="p">(</span><span class="k">const</span> <span class="n">ArrayType</span><span class="o">&</span> <span class="n">array</span><span class="p">)</span> <span class="p">{</span>
<span class="k">const</span> <span class="k">auto</span> <span class="n">n</span> <span class="o">=</span> <span class="n">array</span><span class="p">.</span><span class="n">length</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">array</span><span class="p">.</span><span class="n">null_count</span><span class="p">()</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int64_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ii</span> <span class="o">=</span> <span class="n">row_offset_</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">n</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">,</span> <span class="o">++</span><span class="n">ii</span><span class="p">)</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">value</span> <span class="o">=</span> <span class="n">Qnil</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">array</span><span class="p">.</span><span class="n">IsNull</span><span class="p">(</span><span class="n">i</span><span class="p">))</span> <span class="p">{</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">convert_value</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">auto</span> <span class="n">record</span> <span class="o">=</span> <span class="n">rb_ary_entry</span><span class="p">(</span><span class="n">records_</span><span class="p">,</span> <span class="n">ii</span><span class="p">);</span>
<span class="n">rb_ary_store</span><span class="p">(</span><span class="n">record</span><span class="p">,</span> <span class="n">column_index_</span><span class="p">,</span> <span class="n">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int64_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ii</span> <span class="o">=</span> <span class="n">row_offset_</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">n</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">,</span> <span class="o">++</span><span class="n">ii</span><span class="p">)</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">record</span> <span class="o">=</span> <span class="n">rb_ary_entry</span><span class="p">(</span><span class="n">records_</span><span class="p">,</span> <span class="n">ii</span><span class="p">);</span>
<span class="n">rb_ary_store</span><span class="p">(</span><span class="n">record</span><span class="p">,</span> <span class="n">column_index_</span><span class="p">,</span> <span class="n">convert_value</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">i</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>ref: <a href="https://github.com/apache/arrow/blob/main/ruby/red-arrow/ext/arrow/raw-records.cpp#L115-L133">https://github.com/apache/arrow/blob/main/ruby/red-arrow/ext/arrow/raw-records.cpp#L115-L133</a></p>
<p>渡された配列の長さの分だけ下記の処理を繰り返します。
今回の例で言うと <code class="language-plaintext highlighter-rouge">[1, 2, 3]</code> が渡された場合 3 回ループを回すイメージですね。
次にループ内の処理を見ていきます。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="k">auto</span> <span class="n">n</span> <span class="o">=</span> <span class="n">array</span><span class="p">.</span><span class="n">length</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">array</span><span class="p">.</span><span class="n">null_count</span><span class="p">()</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int64_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ii</span> <span class="o">=</span> <span class="n">row_offset_</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">n</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">,</span> <span class="o">++</span><span class="n">ii</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>まず最初に <code class="language-plaintext highlighter-rouge">rb_ary_entry</code> を利用して先ほど行の分だけ空の配列を入れた <code class="language-plaintext highlighter-rouge">records_</code> から、指定した行の空配列を取得してきます。(Ruby で言う array[0] みたいなイメージですね。)
取得した配列を record に格納します。次に、<code class="language-plaintext highlighter-rouge">rb_ary_store</code> を利用して <code class="language-plaintext highlighter-rouge">column_index_</code> で指定した行に <code class="language-plaintext highlighter-rouge">convert_value</code> で変換した値を代入します。(Ruby で言う array[0] = 1 みたいなイメージですね。)
そうすることで列単位で渡されたデータを行単位のデータに追加していくことができます。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">auto</span> <span class="n">record</span> <span class="o">=</span> <span class="n">rb_ary_entry</span><span class="p">(</span><span class="n">records_</span><span class="p">,</span> <span class="n">ii</span><span class="p">);</span>
<span class="n">rb_ary_store</span><span class="p">(</span><span class="n">record</span><span class="p">,</span> <span class="n">column_index_</span><span class="p">,</span> <span class="n">convert_value</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">i</span><span class="p">));</span>
</code></pre></div></div>
<p>具体的な例としては下記のようなステップで列ごとに対象の行のデータに追加していく感じです。
こうすることで、列データごとに処理をまわし結果として行データに変換できるようにしています。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 1列目のデータ: number: [1, 2, 3]
2. 全ての行のデータ: [[1], [2], [3]]
3. 2列目のデータ: string: ['a', 'b', 'c']
4. 全ての行のデータ: [[1, 'a'], [2, 'b'], [3, 'c']]
</code></pre></div></div>
<p>最終的には、変換が終わった <code class="language-plaintext highlighter-rouge">records_</code> つまり <code class="language-plaintext highlighter-rouge">records</code> が結果として返るわけです。
これが、<code class="language-plaintext highlighter-rouge">Arrow::Table#raw_records</code> の内部実装になります。
ここからわかることは、行単位で結果を逐次的に返すためには、既存の列ごとに行っている処理を行ごとに実行する実装をしてあげると良さそうですね。
次回は実際に手を動かして実装していきましょう!</p>最初に Red Data Tools開発者に聞け!Apache Arrow の Ruby バインディングに新機能を追加するシリーズ の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。 今回は Part9 で紹介された内容をまとめていきます。 動画で kou さんがサクッと解説してくださっています。動画で見たい方は上のリンクを参照してください!Apache Arrow の Ruby バインディングに新機能を追加する 実装編 Part22023-07-18T22:56:00+00:002023-07-18T22:56:00+00:00https://otegami.github.io//red-data-tools/2023/07/18/read-ruby-extension-for-arrow<h2 id="最初に">最初に</h2>
<p><a href="https://youtu.be/eZGLDFr4s90?t=1225">Red Data Tools開発者に聞け!第13回 Apache Arrow の Ruby バインディングに新機能を追加するシリーズ</a> の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。
今回は Part7 で紹介された内容をまとめていきます。</p>
<ul>
<li>動画で kou さんがサクッと解説してくださっています。動画で見たい方は上のリンクを参照してください!</li>
</ul>
<h2 id="記事の位置付け">記事の位置付け</h2>
<ol>
<li>追加したい機能を Ruby で実装してイメージを掴む</li>
<li>Ruby が提供する C の API を利用してどのように実装していくのか学ぶ <- <strong>今回の記事はここ</strong></li>
<li>追加対象に関連するコードを読み解いていく</li>
<li>追加機能の実装を行う</li>
</ol>
<h2 id="前回までのあらすじ">前回までのあらすじ</h2>
<p>前回は、「追加したい機能を Ruby で実装してイメージを掴む」ためにイテレータを Ruby レベルではどのように実装するのかをみていきました。今回は、前回実装したコードをもとに Ruby が提供する C の API を利用してどのように実装していくのかを関連するコードを確認しながらみていきましょう。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s1">'arrow'</span>
<span class="k">class</span> <span class="nc">Arrow::Table</span>
<span class="k">def</span> <span class="nf">each_raw_record</span>
<span class="n">raw_records</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">row</span><span class="o">|</span>
<span class="k">yield</span> <span class="n">row</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<h2 id="c-レイヤーでの実装の流れを掴む">C レイヤーでの実装の流れを掴む</h2>
<p>Ruby レベルのイテレータ実装では、大きく 3 つのことを行っていました。</p>
<ul>
<li>拡張ライブラリを <code class="language-plaintext highlighter-rouge">require</code> して読み込む
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>require 'arrow'
</code></pre></div> </div>
</li>
<li>メソッドを定義する
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">each_raw_record</span>
<span class="c1">#...</span>
<span class="k">end</span>
</code></pre></div> </div>
</li>
<li><code class="language-plaintext highlighter-rouge">yield</code> してイテレートする
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">raw_records</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">row</span><span class="o">|</span>
<span class="k">yield</span> <span class="n">row</span>
<span class="k">end</span>
</code></pre></div> </div>
</li>
</ul>
<p>では次に Ruby が提供する C の API を利用してどのように上記の 3 つを実現するのかを見ていきましょう!</p>
<h3 id="拡張ライブラリを-require-して読み込む">拡張ライブラリを <code class="language-plaintext highlighter-rouge">require</code> して読み込む</h3>
<p>Ruby は拡張ライブラリを <code class="language-plaintext highlighter-rouge">require</code> してロードする際に「Init_ライブラリ名」という関数を自動で実行するようになっています。</p>
<ul>
<li>Ruby の公式ドキュメント「<a href="https://docs.ruby-lang.org/en/3.2/extension_ja_rdoc.html#label-C-E3-82-B3-E3-83-BC-E3-83-89-E3-82-92-E6-9B-B8-E3-81-8F">Rubyの拡張ライブラリの作り方</a>」を参照</li>
</ul>
<p>実際に、<code class="language-plaintext highlighter-rouge">red-arrow</code> ではどうなっているのか確認して見ると、<code class="language-plaintext highlighter-rouge">Init_arrow</code> 関数が定義されています。
つまり、Ruby レベルで <code class="language-plaintext highlighter-rouge">require arrow</code> をすると、<code class="language-plaintext highlighter-rouge">Init_arrow</code> が実行されるという流れになります。</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">extern</span> <span class="s">"C"</span> <span class="kt">void</span> <span class="nf">Init_arrow</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>
<ul>
<li>ref: <a href="https://github.com/apache/arrow/blob/main/ruby/red-arrow/ext/arrow/arrow.cpp#L68">https://github.com/apache/arrow/blob/main/ruby/red-arrow/ext/arrow/arrow.cpp#L68</a></li>
</ul>
<p>では次に、C レイヤーで「メソッドを定義する」のはどのように行うのかをみていきます。</p>
<h3 id="メソッドを定義する">メソッドを定義する</h3>
<p>Ruby の C API は、rb で始まる関数を提供しています。
例えば、実際に <a href="https://github.com/apache/arrow/blob/main/ruby/red-arrow/ext/arrow/arrow.cpp#L68-L121">Arrow の拡張機能のコード</a>を見てみると下記のメソッドが利用されています</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">rb_const_get</code></li>
<li><code class="language-plaintext highlighter-rouge">rb_define_method</code></li>
<li><code class="language-plaintext highlighter-rouge">rb_const_get_at</code></li>
<li><code class="language-plaintext highlighter-rouge">rb_intern</code></li>
</ul>
<p>今回注目したいのは、<code class="language-plaintext highlighter-rouge">rb_define_method</code> です。
これは、C レベルで Ruby の関数を定義するために Ruby が用意した C API になります。
実際に利用例を見てみると、<code class="language-plaintext highlighter-rouge">Arrow::Table</code> に <code class="language-plaintext highlighter-rouge">raw_records</code> メソッドを定義しているのがわかります。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">auto</span> <span class="n">cArrowTable</span> <span class="o">=</span> <span class="n">rb_const_get_at</span><span class="p">(</span><span class="n">mArrow</span><span class="p">,</span> <span class="n">rb_intern</span><span class="p">(</span><span class="s">"Table"</span><span class="p">));</span>
<span class="n">rb_define_method</span><span class="p">(</span><span class="n">cArrowTable</span><span class="p">,</span> <span class="s">"raw_records"</span><span class="p">,</span>
<span class="k">reinterpret_cast</span><span class="o"><</span><span class="n">rb</span><span class="o">::</span><span class="n">RawMethod</span><span class="o">></span><span class="p">(</span><span class="n">red_arrow</span><span class="o">::</span><span class="n">table_raw_records</span><span class="p">),</span>
<span class="mi">0</span><span class="p">);</span>
</code></pre></div></div>
<p>ref: <a href="https://github.com/ruby/ruby/blob/master/doc/extension.rdoc#method-and-singleton-method-definition-">Rubyt C API: rb_define_method</a></p>
<p>では次に、C レイヤーで「<code class="language-plaintext highlighter-rouge">yield</code> してイテレートする」のはどのように行うのかをみていきます。</p>
<h2 id="yield-してイテレートする">yield してイテレートする</h2>
<p>実は、Ruby が <code class="language-plaintext highlighter-rouge">rb_define_method</code> C API を提供したいたのと同様に <code class="language-plaintext highlighter-rouge">yield</code> に関しても、<code class="language-plaintext highlighter-rouge">rb_yield</code> という API を提供してくれています。
なので、こちらを利用してあげれば <code class="language-plaintext highlighter-rouge">yield</code> して結果を逐次的に返すことができます。</p>
<p>ref: <a href="https://github.com/ruby/ruby/blob/master/doc/extension.rdoc#control-structure-">Rubyt C API: rb_yield</a></p>
<p>ここまでで大まかに C API を利用した実装の流れが追えたので、次の記事では実際に実装な知識を得るために内部実装を読み解いていきます。</p>最初に Red Data Tools開発者に聞け!第13回 Apache Arrow の Ruby バインディングに新機能を追加するシリーズ の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。 今回は Part7 で紹介された内容をまとめていきます。 動画で kou さんがサクッと解説してくださっています。動画で見たい方は上のリンクを参照してください!Apache Arrow の Ruby バインディングに新機能を追加する 実装編 Part12023-06-30T00:18:32+00:002023-06-30T00:18:32+00:00https://otegami.github.io//red-data-tools/2023/06/30/implement-ruby-binding-for-arrow<h2 id="最初に">最初に</h2>
<p><a href="https://www.youtube.com/watch?v=eZGLDFr4s90">Red Data Tools開発者に聞け!第13回 Apache Arrow の Ruby バインディングに新機能を追加するシリーズ</a> の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。
今回は Part7 で紹介された内容をまとめていきます。</p>
<ul>
<li>動画で kou さんがサクッと解説してくださっています。動画で見たい方は上のリンクを参照してください!</li>
</ul>
<h2 id="前回までのあらすじ">前回までのあらすじ</h2>
<p>Apache Arrow の Ruby バインディングが手元で動作するようになったので、遂に Ruby バインディングに新機能を追加するフェーズまでやってきました!
では、実際にどんな機能を追加したく、どのように進めていくのかを確認していきます。</p>
<h2 id="どんな機能を追加したいの">どんな機能を追加したいの?</h2>
<p><code class="language-plaintext highlighter-rouge">Arrow::Table#raw_records</code>という、Arrow::Table オブジェクトから作成した表データを Ruby の配列形式に変換するメソッドがあります。今回は、この配列への変換をイテラブルにする<code class="language-plaintext highlighter-rouge">Arrow::Table#raw_each_record</code>という機能を追加したいです。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">></span><span class="w"> </span>require <span class="s1">'arrow'</span>
<span class="gp">></span><span class="w"> </span>arrow <span class="o">=</span> Arrow::Table.new<span class="o">(</span>number: <span class="o">[</span>1, 2, 3], string: <span class="o">[</span><span class="s1">'a'</span>, <span class="s1">'b'</span>, <span class="s1">'c'</span><span class="o">])</span>
<span class="gp">=></span><span class="w">
</span><span class="gp">#</span><Arrow::Table:0x10a9d8ba8 <span class="nv">ptr</span><span class="o">=</span>0x12da8bd80>
<span class="go"> number string
0 1 a
1 2 b
2 3 c
</span><span class="gp">></span><span class="w"> </span>arrow.raw_records
<span class="gp">=></span><span class="w"> </span><span class="o">[[</span>1, <span class="s2">"a"</span><span class="o">]</span>, <span class="o">[</span>2, <span class="s2">"b"</span><span class="o">]</span>, <span class="o">[</span>3, <span class="s2">"c"</span><span class="o">]]</span>
</code></pre></div></div>
<h3 id="どんな時に嬉しいのか">どんな時に嬉しいのか?</h3>
<p>どんな時にイテラブルに処理すると嬉しいのかという疑問があると思います。
今回の例のように、データ量が少ない場合にはあまり問題になりません。しかし、不断 Arrow が取り扱うデータは大量であり、全てを一度に配列に変換すると処理が終わるまでに時間が掛かるという課題が存在します。</p>
<p>具体例として、ニューヨーク市の1ヶ月分のタクシー利用データ(100万件以上のデータ)を考えてみましょう。全体のデータを分析する必要はなく、最初の1000件だけを確認したいとします。全データの変換を待つのは時間がかかりますが、イテラブルな変換を利用すれば、最初の1000件のデータだけを短時間で取り出すことが可能になります。</p>
<ul>
<li>ref: <a href="https://github.com/red-data-tools/red-datasets-parquet/pull/11">Added New York city’s Taxi and Limousine Commission For Hire Vehicle trip support</a></li>
</ul>
<p>つまり、この機能があると嬉しい時は、「大量のデータの中から一部だけを効率よく分析したい」時となります。</p>
<h2 id="どのように進めていくのか">どのように進めていくのか</h2>
<p>実際になぜ必要なのかを共有できたので、実際に開発していく方針を考えてみましょう!
今回は、初めての Apache Arrow の Ruby バインディングへの機能追加なので下記の手順で行なっていきます。</p>
<ol>
<li>追加したい機能を Ruby で実装してイメージを掴む <- <strong>今回の記事はここ</strong></li>
<li>Ruby が提供する C の API を利用してどのように実装していくのか学ぶ</li>
<li>追加対象に関連するコードを読み解いていく</li>
<li>追加機能の実装を行う</li>
</ol>
<h3 id="1-追加したい機能を-ruby-で実装してイメージを掴む">1. 追加したい機能を Ruby で実装してイメージを掴む</h3>
<p><code class="language-plaintext highlighter-rouge">Arrow::Table#raw_records</code>をイテラブルに実行する<code class="language-plaintext highlighter-rouge">Arrow::Table#raw_each_record</code>を Ruby で簡単に実装してみます。</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># /arrow/ruby/red-arrow/sample-each.rb</span>
<span class="nb">require</span> <span class="s1">'arrow'</span>
<span class="k">class</span> <span class="nc">Arrow::Table</span>
<span class="k">def</span> <span class="nf">each_raw_record</span>
<span class="n">raw_records</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">row</span><span class="o">|</span>
<span class="k">yield</span> <span class="n">row</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="no">Arrow</span><span class="o">::</span><span class="no">Table</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">number: </span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="ss">string: </span><span class="p">[</span><span class="s1">'a'</span><span class="p">,</span> <span class="s1">'b'</span><span class="p">,</span> <span class="s1">'c'</span><span class="p">]).</span><span class="nf">each_raw_record</span> <span class="p">{</span> <span class="o">|</span><span class="n">row</span><span class="o">|</span> <span class="nb">p</span> <span class="n">row</span> <span class="p">}</span>
</code></pre></div></div>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">% bundle exec env DYLD_LIBRARY_PATH=$</span>DYLD_LIBRARY_PATH:/tmp/local/lib ruby <span class="nt">-I</span> ext/arrow sample-each.rb
<span class="go">[1, "a"]
[2, "b"]
[3, "c"]
</span></code></pre></div></div>
<p>上記のように Ruby でイテレータを作成するのは <code class="language-plaintext highlighter-rouge">yield</code> を利用することで簡単に作成することができます。
今実装した例を元に、Ruby バインディングに機能追加をしてあげたら良さそうです。</p>
<p>大雑把には下記のフローで機能追加を実現しようと考えています。</p>
<ol>
<li>既存の <code class="language-plaintext highlighter-rouge">Arrow::Table#raw_records</code> を利用して、変換が終わり次第一つずつ <code class="language-plaintext highlighter-rouge">yield</code> してあげる形で、<code class="language-plaintext highlighter-rouge">Arrow::Table#raw_each_record</code> を実装する。</li>
<li>次に既存の <code class="language-plaintext highlighter-rouge">Arrow::Table#raw_records</code> は一度に全てを変換してしまうので、一つずつ変換する機能を追加する。</li>
</ol>
<p>次の記事では、今回実装イメージがついたので、実際に機能を追加する部分のコードを理解をするところから始めます。</p>最初に Red Data Tools開発者に聞け!第13回 Apache Arrow の Ruby バインディングに新機能を追加するシリーズ の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。 今回は Part7 で紹介された内容をまとめていきます。 動画で kou さんがサクッと解説してくださっています。動画で見たい方は上のリンクを参照してください!Incompatible Library Version Error2023-06-14T12:54:00+00:002023-06-14T12:54:00+00:00https://otegami.github.io//red-data-tools/2023/06/14/incompatible-library-version<h2 id="最初に">最初に</h2>
<p><a href="https://www.youtube.com/watch?v=63vTddvnvQw">Red Data Tools開発者に聞け!第12回 Apache Arrow の Ruby バインディングに新機能を追加するシリーズ</a> の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。
今回は Part7 で紹介された内容をまとめていきます。</p>
<ul>
<li>kou さんがサクッと解説してくださっている動画は、<a href="https://www.youtube.com/watch?v=63vTddvnvQw&t=320s">こちら</a>になります。</li>
</ul>
<h2 id="遭遇した問題">遭遇した問題</h2>
<p>今回は、テスト実行時に発生した <code class="language-plaintext highlighter-rouge">incompatible library version</code> エラーになります。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">% bundle exec env DYLD_LIBRARY_PATH=$</span>DYLD_LIBRARY_PATH:/tmp/local/lib <span class="si">$(</span>rbenv which ruby<span class="si">)</span> <span class="nb">test</span>/run-test.rb
<span class="go">/Users/otegami/dev/project/arrow/ruby/red-arrow/lib/arrow/loader.rb:146:in `require': incompatible library version - /Users/otegami/dev/project/arrow/ruby/red-arrow/ext/arrow/arrow.bundle (LoadError)
</span></code></pre></div></div>
<h2 id="対応策">対応策</h2>
<p><code class="language-plaintext highlighter-rouge">arrow.bundle</code> をビルドした Ruby のバージョンでテストを実行することで解決できます。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">% bundle exec env DYLD_LIBRARY_PATH=$</span>DYLD_LIBRARY_PATH:/tmp/local/lib <span class="si">$(</span>rbenv which ruby<span class="si">)</span> <span class="nb">test</span>/run-test.rb
</code></pre></div></div>
<h3 id="何が起きているのか">何が起きているのか?</h3>
<p>実行している <code class="language-plaintext highlighter-rouge">Ruby</code> が <code class="language-plaintext highlighter-rouge">arrow.bundle</code> が使っている<code class="language-plaintext highlighter-rouge">libruby</code>に対し、「ABI」(アプリケーションバイナリインターフェース)の互換性がなくエラーとなっている。</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">ABI</code> とは
<ul>
<li>バイナリレベルでの相互作用を規定しているもの(バイナリレベルのインターフェイス)</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">libruby</code> とは
<ul>
<li>Rubyプログラミング言語のコア機能を提供するライブラリです</li>
<li>普段使っている ruby コマンドは、libruby を呼び出してスクリプトを実行しているだけである</li>
</ul>
</li>
</ul>
<h2 id="互換性のある-libruby-は何なのか確認する">互換性のある <code class="language-plaintext highlighter-rouge">libruby</code> は何なのか確認する</h2>
<p><code class="language-plaintext highlighter-rouge">otool</code>コマンドを利用して、<code class="language-plaintext highlighter-rouge">arrow.bundle</code> と互換性のある libruby を調査します。</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">otool</code> は、実行可能ファイルフォーマットである <code class="language-plaintext highlighter-rouge">Mach-O</code>(Mach Object)ファイル形式を調査するためのコマンドです。</li>
<li><code class="language-plaintext highlighter-rouge">Mach-O</code> は、コンパイラが生成するオブジェクトファイル並びに実行ファイルのフォーマットみたいです。(理解できていないので後日調べる)</li>
<li><code class="language-plaintext highlighter-rouge">-L</code> は引数に与えられたライブラリが利用する共有ライブラリの一覧を表示します</li>
</ul>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% otool -L ext/arrow/arrow.bundle
ext/arrow/arrow.bundle:
/Users/otegami/.rbenv/versions/3.2.0/lib/libruby.3.2.dylib (compatibility version 3.2.0, current version 3.2.0)
</span></code></pre></div></div>
<p>上記の結果から、<code class="language-plaintext highlighter-rouge">libruby.3.2.dylib</code> に依存していることがわかります。
では、現在利用している <code class="language-plaintext highlighter-rouge">Ruby</code> はどのパージョンの <code class="language-plaintext highlighter-rouge">libruby</code> に依存しているのか確認してみます。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">% otool -L $</span><span class="o">(</span>rbenv which ruby<span class="o">)</span>
<span class="go">/Users/otegami/.rbenv/versions/3.0.3/bin/ruby:
/Users/otegami/.rbenv/versions/3.0.3/lib/libruby.3.0.dylib (compatibility version 3.0.0, current version 3.0.3)
</span></code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">libruby.3.0.dylib</code> に依存していることがわかります。<code class="language-plaintext highlighter-rouge">libruby</code> のバージョンは <code class="language-plaintext highlighter-rouge">Ruby</code> のバージョンと互換性があるので、
今回は、<code class="language-plaintext highlighter-rouge">libruby.3.2.dylib</code> を利用する <code class="language-plaintext highlighter-rouge">Ruby v3.2.0</code> で実行してあげるとよさそうです。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% rbenv global 3.2.0
% ruby -v
ruby 3.2.0 (2022-12-25 revision a528908271) [arm64-darwin20]
</span><span class="gp">% bundle exec env DYLD_LIBRARY_PATH=$</span>DYLD_LIBRARY_PATH:/tmp/local/lib ruby <span class="nb">test</span>/run-test.rb
<span class="go">Loaded suite test
Started
</span><span class="c">......................................
</span><span class="go">Finished in 1.123513 seconds.
-----------------------------------------------------------------------------------------------------------------------------------
1610 tests, 1610 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
-----------------------------------------------------------------------------------------------------------------------------------
1433.01 tests/s, 1433.01 assertions/s
</span></code></pre></div></div>
<p>無事にテストが通りました!
今までは、incompatible library version エラーが発生した場合、手探りで Ruby のバージョンを変更して問題を解決していましたが、今後は、libruby のバージョンを確認し、適切なバージョンの Ruby を選択することで問題を回避できます。
また、他のライブラリでも依存関係を調べながら解決できるでしょう!</p>最初に Red Data Tools開発者に聞け!第12回 Apache Arrow の Ruby バインディングに新機能を追加するシリーズ の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。 今回は Part7 で紹介された内容をまとめていきます。 kou さんがサクッと解説してくださっている動画は、こちらになります。DYLD Environment Variables2023-06-13T11:15:00+00:002023-06-13T11:15:00+00:00https://otegami.github.io//red-data-tools/2023/06/13/dyld-environment-variables<h2 id="最初に">最初に</h2>
<p><a href="https://www.youtube.com/watch?v=ClaRIi5fBag">Red Data Tools開発者に聞け!第11回 Apache Arrow の Ruby バインディングに新機能を追加するシリーズ</a> の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。
今回は Part6 で紹介された内容をまとめていきます。</p>
<ul>
<li>kou さんがサクッと解説してくださっている動画は、<a href="https://youtu.be/ClaRIi5fBag?t=748">こちら</a>になります。</li>
</ul>
<h2 id="遭遇した問題">遭遇した問題</h2>
<p>今回は、Red Arrow のテスト実行時に遭遇した下記のエラーになります。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% pwd
/arrow/ruby/red-arrow
% bundle exec rake test
(null)-WARNING **: Failed to load shared library 'libarrow-glib.1300.dylib' referenced by the typelib: dlopen(libarrow-glib.1300.dylib, 0x0009): tried: 'libarrow-glib.1300.dylib' (no such file), '/usr/local/lib/libarrow-glib.1300.dylib' (no such file), '/usr/lib/libarrow-glib.1300.dylib' (no such file), '/Users/otegami/dev/project/arrow/ruby/red-arrow/libarrow-glib.1300.dylib' (no such file), '/usr/local/lib/libarrow-glib.1300.dylib' (no such file), '/usr/lib/libarrow-glib.1300.dylib' (no such file)
</span></code></pre></div></div>
<h2 id="対応策">対応策</h2>
<p>下記のように実行することで問題を解決できます。その理由については以下で詳しく説明します。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">% bundle exec env DYLD_LIBRARY_PATH=$</span>DYLD_LIBRARY_PATH:/tmp/local/lib <span class="si">$(</span>rbenv which ruby<span class="si">)</span> <span class="nb">test</span>/run-test.rb
</code></pre></div></div>
<h3 id="何が起きているのか">何が起きているのか?</h3>
<p><code class="language-plaintext highlighter-rouge">DYLD_LIBRARY_PATH</code> を探索したが、共有ライブラリ(動的ライブラリ)の<code class="language-plaintext highlighter-rouge">libarrow-glib.1300.dylib</code> が見つからず、エラーになっています。</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">DYLD_LIBRARY_PATH</code> とは、MacOS が共有ライブラリのパスを管理する環境変数になります。</li>
</ul>
<p>今回は<code class="language-plaintext highlighter-rouge">libarrow-glib.1300.dylib</code>が存在するパスを指定する必要がありそうです。</p>
<h3 id="dyld_library_path-に-libarrow-glib1300dylib-のパスを指定する">DYLD_LIBRARY_PATH に <code class="language-plaintext highlighter-rouge">libarrow-glib.1300.dylib</code> のパスを指定する</h3>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% ls /tmp/local/lib | grep libarrow-glib.1300.dylib
libarrow-glib.1300.dylib
</span></code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">/tmp/local/lib</code> 配下に <code class="language-plaintext highlighter-rouge">libarrow-glib.1300.dylib</code> は存在することがわかったので、環境変数を指定してテストを実行してみます。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">% DYLD_LIBRARY_PATH=$</span>DYLD_LIBRARY_PATH:/tmp/local/lib bundle <span class="nb">exec </span>rake <span class="nb">test</span>
<span class="go">(null)-WARNING **: Failed to load shared library 'libarrow-glib.1300.dylib' referenced by the typelib: dlopen(libarrow-glib.1300.dylib, 0x0009): tried: 'libarrow-glib.1300.dylib' (no such file), '/usr/local/lib/libarrow-glib.1300.dylib' (no such file), '/usr/lib/libarrow-glib.1300.dylib' (no such file), '/Users/otegami/dev/project/arrow/ruby/red-arrow/libarrow-glib.1300.dylib' (no such file), '/usr/local/lib/libarrow-glib.1300.dylib' (no such file), '/usr/lib/libarrow-glib.1300.dylib' (no such file)
</span></code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">DYLD_LIBRARY_PATH</code> の探索パスに指定した <code class="language-plaintext highlighter-rouge">/tmp/local/lib/</code> が含まれていなさそうです。
どうやら環境変数での指定が効いていなさそうです。なぜなんでしょうか?</p>
<h3 id="macos-上で-dyld_library_path-は特別な環境変数である">MacOS 上で <code class="language-plaintext highlighter-rouge">DYLD_LIBRARY_PATH</code> は特別な環境変数である</h3>
<p>試しに、Ruby が <code class="language-plaintext highlighter-rouge">DYLD_LIBRARY_PATH</code> 環境変数を認識しているか確認してみましょう。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% DYLD_LIBRARY_PATH=/tmp/local/lib ruby -e 'pp ENV' | grep DYLD_LIBRARY_PATH
</span><span class="gp">#</span><span class="w"> </span>何も参照されず、存在しない
</code></pre></div></div>
<p>環境変数名を <code class="language-plaintext highlighter-rouge">XDYLD_LIBRARY_PATH</code> に変更してあげると認識します。どうやら環境変数 <code class="language-plaintext highlighter-rouge">DYLD_LIBRARY_PATH</code> が特別な動きをしているように見えます。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% XDYLD_LIBRARY_PATH=/tmp/local/lib ruby -e 'pp ENV' | grep XDYLD_LIBRARY_PATH
</span><span class="gp"> "XDYLD_LIBRARY_PATH"=></span><span class="s2">"/tmp/local/lib"</span>,
</code></pre></div></div>
<p>実は、DYLD_LIBRARY_PATH はサブプロセスには継承されないという仕様が存在します。
なぜなら、実行時にどのライブラリを読み込むかを指定できるため、仮に指定できてしまうと実行中のプログラムをインターセプトできてしまうような問題が起きてしまいます。
下記の Doc を参考にすると MacOS 側で読み込める用に設定を変えることもできそうですが、セキュリティ的によろしくなさそうなので、今回は利用しません。</p>
<ul>
<li>ref: <a href="https://developer.apple.com/forums/thread/9233">DYLD_LIBRARY_PATH and make</a></li>
<li>ref: <a href="https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_allow-dyld-environment-variables">Allow DYLD Environment Variables Entitlement</a></li>
<li>ref: <a href="https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation">Disable Library Validation Entitlement</a></li>
</ul>
<p>では、テストをどうやってメインプロセスで実行するかを考えるために、まず現状どのように実行されているのかをみていきましょう。</p>
<h3 id="ruby-command-はサブプロセスで実行されている">ruby command はサブプロセスで実行されている?</h3>
<p>ruby command の実体を確認してみましょう。次に示すファイルがそれに相当すると思われます。</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">type</code> command は、<code class="language-plaintext highlighter-rouge">which</code> commnad よりも幅広い情報を提供します。(エイリアスが使われている場合は、その内容を報告してくれるなど)
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% type ruby
ruby is /Users/otegami/.rbenv/shims/ruby
</span></code></pre></div> </div>
</li>
</ul>
<p>次にファイルの中身がなんなのかを <code class="language-plaintext highlighter-rouge">file</code> command で確認してみます。
どうやら Shell Script で書かれているようです。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% file =ruby
/Users/otegami/.rbenv/shims/ruby: Bourne-Again shell script text executable, ASCII text
</span></code></pre></div></div>
<p>ファイルの中身は下記のようになっています。
どうやらこれは rbenv をラップして実行する Shell Script のようです。
これでは、サブプロセスになってしまうので、直接実行してあげるようにしてあげる必要がありそうです。</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% <span class="nb">cat</span> <span class="o">=</span>ruby
<span class="c">#!/usr/bin/env bash</span>
<span class="nb">set</span> <span class="nt">-e</span>
<span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$RBENV_DEBUG</span><span class="s2">"</span> <span class="o">]</span> <span class="o">&&</span> <span class="nb">set</span> <span class="nt">-x</span>
<span class="nv">program</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">0</span><span class="p">##*/</span><span class="k">}</span><span class="s2">"</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$program</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"ruby"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
for </span>arg<span class="p">;</span> <span class="k">do
case</span> <span class="s2">"</span><span class="nv">$arg</span><span class="s2">"</span> <span class="k">in</span>
<span class="nt">-e</span><span class="k">*</span> <span class="p">|</span> <span class="nt">--</span> <span class="p">)</span> <span class="nb">break</span> <span class="p">;;</span>
<span class="k">*</span>/<span class="k">*</span> <span class="p">)</span>
<span class="k">if</span> <span class="o">[</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$arg</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nb">export </span><span class="nv">RBENV_DIR</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">arg</span><span class="p">%/*</span><span class="k">}</span><span class="s2">"</span>
<span class="nb">break
</span><span class="k">fi</span>
<span class="p">;;</span>
<span class="k">esac</span>
<span class="k">done
fi
</span><span class="nb">export </span><span class="nv">RBENV_ROOT</span><span class="o">=</span><span class="s2">"/Users/otegami/.rbenv"</span>
<span class="nb">exec</span> <span class="s2">"/opt/homebrew/bin/rbenv"</span> <span class="nb">exec</span> <span class="s2">"</span><span class="nv">$program</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
</code></pre></div></div>
<p>直接実行するために <code class="language-plaintext highlighter-rouge">rbenv which ruby</code> の実体を確認してみると、バイナリのようです。
つまり、この実行可能なバイナリを直接実行すれば、シェルによって実行されるので環境変数をそのまま渡せそうです。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">% file =$</span><span class="o">(</span>rbenv which ruby<span class="o">)</span>
<span class="go">/Users/otegami/.rbenv/versions/3.2.0/bin/ruby: Mach-O 64-bit executable arm64
</span></code></pre></div></div>
<p>無事に <code class="language-plaintext highlighter-rouge">DYLD_LIBRARY_PATH</code> が参照でき、テストが通りました!</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">% bundle exec env DYLD_LIBRARY_PATH=$</span>DYLD_LIBRARY_PATH:/tmp/local/lib <span class="si">$(</span>rbenv which ruby<span class="si">)</span> <span class="nb">test</span>/run-test.rb
<span class="c">......................................
</span><span class="go">Finished in 1.032504 seconds.
-----------------------------------------------------------------------------------------------------------------------------------
1610 tests, 1610 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
-----------------------------------------------------------------------------------------------------------------------------------
1559.32 tests/s, 1559.32 assertions/s
</span></code></pre></div></div>
<p>念の為、環境変数 <code class="language-plaintext highlighter-rouge">DYLD_LIBRARY_PATH</code> に指定した値を確認してみると、無事に参照できていそうです!</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">% bundle exec env DYLD_LIBRARY_PATH=$</span>DYLD_LIBRARY_PATH:/tmp/local/lib <span class="si">$(</span>rbenv which ruby<span class="si">)</span> <span class="nt">-e</span> <span class="s1">'pp ENV'</span> | <span class="nb">grep </span>DY
<span class="gp">"DYLD_LIBRARY_PATH"=></span><span class="s2">":/tmp/local/lib"</span>,
</code></pre></div></div>最初に Red Data Tools開発者に聞け!第11回 Apache Arrow の Ruby バインディングに新機能を追加するシリーズ の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。 今回は Part6 で紹介された内容をまとめていきます。 kou さんがサクッと解説してくださっている動画は、こちらになります。Static link と Dynamic Link2023-06-06T13:17:25+00:002023-06-06T13:17:25+00:00https://otegami.github.io//red-data-tools/2023/06/06/shared-link-static-link<h2 id="最初に">最初に</h2>
<p><a href="https://www.youtube.com/watch?v=MEAOzM5UtEc">Red Data Tools開発者に聞け!第6回 Apache ArrowのRubyバインディングに新機能を追加するシリーズ</a> の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。
今回は Part1 で紹介された内容をまとめていきます。</p>
<ul>
<li>kou さんがサクッと解説してくださっている動画は、<a href="https://youtu.be/MEAOzM5UtEc?t=623">こちら</a>になります。</li>
</ul>
<h2 id="遭遇した問題">遭遇した問題</h2>
<p>今回は、Apache Arrow をビルドする中で遭遇した下記のエラーになります。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% cmake -S cpp -B cpp.build --preset ninja-debug-maximal -DCMAKE_INSTALL_PREFIX=/tmp/local
CMake Error at src/arrow/flight/CMakeLists.txt:48 (message):
Must build Arrow statically to link Flight tests statically
</span></code></pre></div></div>
<h3 id="何が起きているのか">何が起きているのか?</h3>
<p>C++ のソースコードをビルドしようとして、必要なライブラリが正しくリンクできないために発生しています。
ただ、リンクが何かわかっておらず、よく分かっていないので次はリンクについてみていきます!</p>
<h3 id="リンクとは">リンクとは?</h3>
<p>リンクは、C や C++ がライブラリを利用するためのプロセスです。Rubyでライブラリを利用するときには <code class="language-plaintext highlighter-rouge">require</code> を使いますが、C や C++ ではリンクというプロセスを通じてライブラリを利用します。
リンクには主に2つの形式があります。<code class="language-plaintext highlighter-rouge">Static link</code> と <code class="language-plaintext highlighter-rouge">Dynamic link</code> です。</p>
<h3 id="static-link-と-dynamic-link"><code class="language-plaintext highlighter-rouge">Static link</code> と <code class="language-plaintext highlighter-rouge">Dynamic link</code></h3>
<p>Static link と Dynamic link の違いは、同じライブラリを複数のプログラムが利用する違いです。</p>
<h4 id="dynamic-link">Dynamic link</h4>
<p>Ruby で考えると、複数の gem(ライブラリ)が同じ gem を利用する場合、例えば Rails で言うと 1 つの Active Support の実体を他の gem が共有して利用する状態が Dynamic link に相当します。なので Active Support のバージョンを更新すると、共有して利用していた他のライブラリ全てが影響を受けます。</p>
<h4 id="static-link">Static link</h4>
<p>一方、各 gem がそれぞれ Active Support を持っている状態は、Static link に近いと考えられます。それぞれのライブラリが独自のActive Supportを保持しているため、他のライブラリが保持している Active Support を更新しても、それが他のライブラリに影響を及ぼすことはありません。</p>
<ul>
<li>実際に Ruby で Static Link をやっているライブラリっているの? Rubygems がやっています。</li>
</ul>
<h3 id="対応策">対応策</h3>
<p>正しくライブラリがリンクされるように修正する必要があります。
ただ、今回の Ruby バインディングの開発では、スタティックにリンクする必要や対象のライブラリは必要ではないので、エラーが起きているライブラリを使わずにビルドすることで回避します。具体的には下記の 2 つのオプションをつけます。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-DARROW_FLIGHT=OFF
-DARROW_BUILD_TESTS=OFF
</code></pre></div></div>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% cmake -S cpp -B cpp.build --preset ninja-debug-maximal -DARROW_FLIGHT=OFF -DARROW_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/tmp/local
CMake Error at src/arrow/flight/CMakeLists.txt:48 (message):
Must build Arrow statically to link Flight tests statically
</span></code></pre></div></div>最初に Red Data Tools開発者に聞け!第6回 Apache ArrowのRubyバインディングに新機能を追加するシリーズ の中で、kou さんが解説してくださった内容を簡単にまとめるシリーズになります。 今回は Part1 で紹介された内容をまとめていきます。 kou さんがサクッと解説してくださっている動画は、こちらになります。Apache Arrow の Ruby バインディングに新機能を追加する 準備編2023-05-31T14:59:18+00:002023-05-31T14:59:18+00:00https://otegami.github.io//apache-arrow/red-data-tools/2023/05/31/prepare-for-implementing-red-arrow<h2 id="何をやりたいか">何をやりたいか</h2>
<ul>
<li>Apache Arrow の Rubyバインディング(Red Arrow)に新機能を追加したい。</li>
<li>具体的にはこちらの <a href="https://github.com/apache/arrow/issues/33749">[Ruby] Add Arrow::RecordBatch#each_raw_record</a>を解決したい。</li>
</ul>
<h3 id="登場人物">登場人物</h3>
<ul>
<li>Apache Arrow
<ul>
<li>実装言語: C++</li>
<li>効率的なデータ分析を可能にする、言語に依存しない列指向メモリフォーマットを提供するツール</li>
</ul>
</li>
<li>Red Arrow
<ul>
<li>実装言語: Ruby, C++</li>
<li>Apache Arrow を Ruby から使えるようにする Ruby の拡張ライブラリ</li>
</ul>
</li>
<li>CMake
<ul>
<li>CMakeはクロスプラットフォームで柔軟性の高いビルドシステムです</li>
<li>Apache Arrow C++ のコンフィグレーションに利用されます</li>
</ul>
</li>
<li>Meson
<ul>
<li>高速でユーザーフレンドリーなクロスプラットフォームのビルドシステム</li>
<li>Apache Arrow GLib のコンフィグレーションに利用されます</li>
</ul>
</li>
<li>Ninja
<ul>
<li>高速なビルドシステムで、大規模なソフトウェアプロジェクトのコンパイルを効率的に行います</li>
<li>CMakeやMesonなどのビルドシステムと組み合わせて、ビルドの設定と実行を行います</li>
<li>Apache Arrow C++ と Apache Arrow GLib のビルドに利用されます</li>
</ul>
</li>
</ul>
<h2 id="準備としてやること">準備としてやること</h2>
<p>Red Arrow の機能追加に着手するための開発環境の構築を行なっていく。
下記のライブラリに依存しているのでインストールを行なっていく。</p>
<ol>
<li>Apache Arrow C++ のインストール</li>
<li>Apache Arrow GLib のインストール</li>
<li>Red Arrow のビルド</li>
</ol>
<p>Ref: https://github.com/apache/arrow/tree/main/ruby/red-arrow#development</p>
<h3 id="apache-arrow-c-のインストール">Apache Arrow C++ のインストール</h3>
<p>基本的には下記に記されている手順通りに Arrow C++ のビルドを進めていく。
+α で教えていただいこともメモとして追記します。</p>
<ul>
<li>https://arrow.apache.org/docs/developers/cpp/building.html</li>
</ul>
<p>インストールする際には大きく 3 つのステップがあります。</p>
<ol>
<li>コンフィグレーション
<ul>
<li>ビルドに必要な依存パッケージの準備やビルド手順を定義したファイルを生成する</li>
</ul>
</li>
<li>ビルド
<ul>
<li>コンフィグレーションで生成されたファイルを元にビルドする</li>
<li>具体的には、ソースコードのコンパイルとリンクをする</li>
</ul>
</li>
<li>インストール
<ul>
<li>ビルドした成果物をインストールする</li>
</ul>
</li>
</ol>
<h4 id="事前準備">事前準備</h4>
<p>ビルドに必要なライブラリを Homebrew を利用していインストールします。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% git clone https://github.com/apache/arrow.git
% cd arrow
% brew update && brew bundle --file=cpp/Brewfile
</span></code></pre></div></div>
<h4 id="コンフィグレーション">コンフィグレーション</h4>
<p>ビルドに必要な依存パッケージの準備(場所の把握やインストール)やビルド手順を定義したファイルを生成していきます。
今回は、普通にコンフィグレーションするだけではなくいくつかのオプションを追記しています。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% cmake -S cpp -B ./cpp.build --preset ninja-debug-maximal -DARROW_CUDA=OFF -DARROW_SKYHOOK=OFF -DCMAKE_INSTALL_PREFIX=/tmp/local
</span></code></pre></div></div>
<ul>
<li><code class="language-plaintext highlighter-rouge">-S cpp </code>
<ul>
<li>コンフィグレーションする際に利用するソースディレクトリを指定</li>
<li>今回は、<code class="language-plaintext highlighter-rouge">cpp</code> 配下を利用し、コンフィグレーションしていきます</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">-B ./cpp.build</code>
<ul>
<li>コンフィグレーションしビルドする際に利用する成果物を配置するディレクトリを指定</li>
<li><code class="language-plaintext highlighter-rouge">xxx.build</code> の命名は、<a href="https://github.com/kou">kou</a> さんのオススメです
<ul>
<li>ビルド元がわかりやすい && ビルド用ディレクトリであることがわかりやすい</li>
</ul>
</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">--preset ninja-debug-maximal</code>
<ul>
<li>どのようにコンフィグレーションするかを指定できるオプションが提供されています</li>
<li>今回は、ビルドツールに Ninja を利用し、全てを有効化にしたデバッグビルドを行う設定にしています</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">-DCMAKE_INSTALL_PREFIX=/tmp/local</code>
<ul>
<li>ビルドした際の成果物のインストール先を指定します</li>
<li>今回は、<code class="language-plaintext highlighter-rouge">/tmp/local</code> を指定しています</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">-DARROW_CUDA=OFF -DARROW_SKYHOOK=OFF</code>
<ul>
<li>Arrow C++ が提供する特定の機能に対するフラグになります</li>
<li>今回は、<code class="language-plaintext highlighter-rouge">CUDA</code> との連携機能と <code class="language-plaintext highlighter-rouge">Skyhook</code> 機能を無効にしています
<ul>
<li>ビルド時にエラーになったため(今回拡張したい機能では不要なため許容しています)</li>
</ul>
</li>
<li>ref: https://arrow.apache.org/docs/developers/cpp/building.html#optional-components</li>
</ul>
</li>
</ul>
<h4 id="ビルド">ビルド</h4>
<p>コンフィグレーションで準備されたビルド環境を元にビルドを行なっていきます。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% cmake --build ./cpp.build
</span></code></pre></div></div>
<ul>
<li><code class="language-plaintext highlighter-rouge">--build ./cpp.build</code>
<ul>
<li>ビルド対象のディレクトリを指定します</li>
<li>今回は、<code class="language-plaintext highlighter-rouge">cpp.build</code> 配下を指定しています
<ul>
<li>なぜなら、コンフィグレーション時に <code class="language-plaintext highlighter-rouge">-B</code> オプションで指定したため</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="インストール">インストール</h4>
<p>ビルドされた成果物をインストールしていきます。
今回は、コンフィグレーション時に指定した <code class="language-plaintext highlighter-rouge">-DCMAKE_INSTALL_PREFIX=/tmp/local</code> にインストールします。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% ninja -C cpp.build install
</code></pre></div></div>
<ul>
<li><code class="language-plaintext highlighter-rouge">-C cpp.build</code>
<ul>
<li><code class="language-plaintext highlighter-rouge">Ninja</code> を実行するディレクトリを指定できます</li>
<li>今回は、ビルドした <code class="language-plaintext highlighter-rouge">cpp.build</code> を対象にしてインストールをします</li>
</ul>
</li>
</ul>
<p>ここまでで、Apache Arrow C++ のインストールが完了です。お疲れ様です!</p>
<h3 id="apache-arrow-glib-のインストール">Apache Arrow GLib のインストール</h3>
<p>基本的には、Arrow C++ と同様の下記の 3 つのステップでインストールしていきます。
下記の情報を元に進めていきます。</p>
<ul>
<li>https://github.com/apache/arrow/blob/main/c_glib/README.md</li>
</ul>
<ol>
<li>コンフィグレーション</li>
<li>ビルド</li>
<li>インストール</li>
</ol>
<h4 id="事前準備-1">事前準備</h4>
<p>ビルドに必要なライブラリを Homebrew を利用していインストールします。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% brew update && brew bundle --file=c_glib/Brewfile
</code></pre></div></div>
<h4 id="コンフィグレーション-1">コンフィグレーション</h4>
<p>ビルドに必要な依存パッケージの準備(場所の把握やインストール)やビルド手順を定義したファイルを生成していきます。
今回は、普通にコンフィグレーションするだけではなくいくつかのオプションを追記しています。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% meson setup ./c_glib.build ./c_glib --prefix /tmp/local --debug --pkg-config-path /tmp/local/lib/pkgconfig
</span></code></pre></div></div>
<ul>
<li><code class="language-plaintext highlighter-rouge">setup ./c_glib.build ./c_glib</code>
<ul>
<li>setup は、ビルド用の成果物を配置するディレクトリと、コンフィグレーションする元になるディレクトリを指定します</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">--prefix /tmp/local</code>
<ul>
<li>ビルドした際の成果物のインストール先を指定します</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">--debug</code>
<ul>
<li>ビルド時にデバック情報を生成するようにコンパイラに指示します</li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">--pkg-config-path /tmp/local/lib/pkgconfig</code>
<ul>
<li>pkg-config がライブラリを探す際に探索するパスを指定できます</li>
<li>今回は、依存ライブラリとして Arrow C++ を利用するので、前述のインストール先を参照しています</li>
</ul>
</li>
</ul>
<h4 id="ビルド-1">ビルド</h4>
<p><code class="language-plaintext highlighter-rouge">Meson</code> でコンフィグレーションされたビルド環境を元にビルドを行なっていきます。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% ninja -C c_glib.build
</span></code></pre></div></div>
<ul>
<li><code class="language-plaintext highlighter-rouge">-C cpp.build</code>
<ul>
<li><code class="language-plaintext highlighter-rouge">Ninja</code> を実行するディレクトリを指定できます</li>
<li>今回は、コンフィグレーションで準備したビルドディレクトリ <code class="language-plaintext highlighter-rouge">c_glib.build</code> を対象にして、ビルドします</li>
</ul>
</li>
</ul>
<h4 id="インストール-1">インストール</h4>
<p>ビルドされた成果物をインストールしていきます。
今回は、コンフィグレーション時に指定した <code class="language-plaintext highlighter-rouge">/tmp/local</code> にインストールします。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% ninja -C c_glib.build install
</span></code></pre></div></div>
<ul>
<li>オプションに関しては、上記で説明しているので省くね</li>
</ul>
<p>ここまでで、Apache Arrow GLib のインストールが完了です。お疲れ様です!</p>
<h4 id="red-arrow-のビルド">Red Arrow のビルド</h4>
<p>前述の Apache Arrow C++ と Apache Arrow GLib のインストール と同様の手順で進めていきます。
今回は、インストールする必要がないので、2 ステップになります。</p>
<ol>
<li>コンフィグレーション</li>
<li>ビルド</li>
</ol>
<h4 id="事前準備-2">事前準備</h4>
<p>Red Arrow のビルドに必要な gem をインストールしていきます。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">% cd ruby;</span><span class="w"> </span>bundle <span class="nb">install</span><span class="p">;</span>
<span class="gp">% cd red-arrow;</span><span class="w"> </span>bundle <span class="nb">install</span>
</code></pre></div></div>
<h4 id="コンフィグレーション-2">コンフィグレーション</h4>
<p><code class="language-plaintext highlighter-rouge">ext/arrow/</code> 配下の <code class="language-plaintext highlighter-rouge">extconf.rb</code> を元にコンフィグレーションを行います。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">% cd ext/arrow;</span><span class="w"> </span>ruby extconf.rb
</code></pre></div></div>
<h4 id="ビルド-2">ビルド</h4>
<p>コンフィグレーションによって生成された Makefile を元にビルドを行います。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% make
</span></code></pre></div></div>
<h4 id="テスト">テスト</h4>
<p>最後に Red Arrow が正常にビルドされたかをテストします。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">% pwd
/arrow/ruby/red-arrow
% bundle exec rake test
</span></code></pre></div></div>
<p>無事にテストが完了したら、Red Arrow のビルドですおめでとうございます!</p>何をやりたいか Apache Arrow の Rubyバインディング(Red Arrow)に新機能を追加したい。 具体的にはこちらの [Ruby] Add Arrow::RecordBatch#each_raw_recordを解決したい。