FireDucksのご紹介

こちらは [NEC デジタルテクノロジー開発研究所 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/nec-dt-labs) 4日目の記事です。FireDucksはすでに[一日目](https://qiita.com/shu_ohm1/items/b0a35cfb4fce5b71c715)に登場しておりますが，改めてご紹介したいと思います．

FireDucksを一言で紹介するとpandasの高速版です．pandasとAPI互換ですので，何も考えずに置き換えて使ってもらうことを想定しています．つまり，置き換えてエラーが出たり，結果が変わったり，遅くなったりすることなく，たいていの場合は10倍くらい速くなることを目標としています．これまで数時間かかるから夜に動かしていたものが日中に何回も動かせたり，コマンド打って10秒くらい待ってたものが一瞬で終わったりということをやりたいと思っています．

## pandasとの速度比較

現状（といっても二か月前くらいですが）の性能は以下の様になっています．これはpandasに対してFireDucksがどれくらい速いかを示しています（1.0だとpandasと同じ実行時間で，1.0より大きいとpandasより速くなったことを示しています）．平均は5倍くらいで目標の10倍には達しておりませんが，良いものでは10倍を超えるものがあるといったところです．

![画像](https://fireducks-dev.github.io/images/pandas-tpcx-bb.webp)

この測定に使ったプログラムは，[TPCx-BB](https://www.tpc.org/tpcx-bb/)というベンチマーク集を元にしています．TPCx-BBには30種類の様々なデータ分析クエリが含まれているのですが，それを我々のほうでpandasで実装したものを使いました．多くの処理はpandasで実装できていますが，中にはscikit-learnが支配的になっているものもあり，あまりpandasと性能差がでていないクエリの原因となっています．TPCx-BBはデータセットのサイズを変えることができるのですが，今回はScaleFactor=10と呼ばれる10GB程度のデータセットを用いました．

なお，FireDucksはマルチスレッド化による高速化も行っていますので，性能はマシンのコア数に依存しますが，この測定では24コア（12コアx2ソケット）を利用しています（Xeon Gold 5317 x2）．また，この測定ではpandasは2.0.1，FireDucksは0.7.1を使いました（測定時の最新版）．

## FireDucksの実行の内訳

さて，FireDucksでどんな処理が実際に速くなってるのかを示すために，FireDucksで実行した場合のトレースを集計したものを示したいと思います．こちらは最新版のFireDuck 0.8.8でとったものですが，以下の様になっています．

FireDucksにはkernel実行とfallback実行の二つがあります．kernel実行とは，FireDucksが内部で持ってるマルチスレッド化された要素処理（joinとかgroupbyとか）を実行した場合で，fallback実行とは現時点ではFireDucksのカーネルがサポートしていないために，FireDucksが内部的にpandasを呼び出して実行した場合を示しています．

以下はTPCx-BBの全クエリの合計実行時間が194秒で，そのうちkernel実行が73秒，fallback実行が11秒あることを示しています（その他も半分くらいあるのですが，これはscikit-learnの時間などです）．



```
elapsed:        194.255 sec
kernels           72.665 sec  37.41%     7207
fallbacks         10.997 sec   5.66%      637
                                                            duration sec ratio     count
== kernel ==
fireducks.read_csv                                                24.733   12.73%        100
fireducks.to_pandas.frame.metadata                                11.477    5.91%        133
fireducks.join                                                    10.522    5.42%         99
fireducks.sort_values                                              6.014    3.10%         29
fireducks.groupby_agg                                              5.215    2.68%         45
fireducks.drop_duplicates                                          4.476    2.30%         15
fireducks.from_pandas.frame.metadata                               2.512    1.29%        117
fireducks.filter                                                   1.963    1.01%        147
fireducks.or.vector.vector                                         0.964    0.50%         14
fireducks.dropna                                                   0.733    0.38%          3
== fallback ==
fallback:Series.apply                                              7.711    3.97%         30
fallback:get_dummies                                               2.466    1.27%          2
fallback:RangeIndex.to_series                                      0.190    0.10%          1
fallback:Series.shift                                              0.169    0.09%          4
fallback:_LocIndexer.__setitem__                                   0.150    0.08%         18
fallback:to_datetime                                               0.084    0.04%          7
fallback:Series.astype                                             0.067    0.03%         10
fallback:DataFrame.take                                            0.036    0.02%          2
fallback:DataFrame.__getitem__                                     0.031    0.02%          6
fallback:DataFrame.fillna                                          0.018    0.01%          1
```

`== kernel ==`の下に書かれているのが，kernel実行で行った処理のトップ10です．read_csv，join，sort_values, groupby_aggなどの処理がカーネル実行されています．これらはFireDucksのカーネルの名前ですが，概ねpandasのメソッドに対応しているので，イメージできるかと思います．（我々の知る限り）pandasは同じようなトレース機能がないので，カーネル単位でのpandasとの定量的な比較データが今はないのですが，上のグラフで示した速度向上はこれらのカーネルの高速化によって達成されていると言えます．

`== fallback ==`の下に書かれているのが，fallback実行でのトップ10です．これらの処理は残念ながらまだFireDucksで高速化できてない処理なのですが，全実行時間の5.6%ですので，大きなネックとはなっていません．ただし，fallback実行を行うためには，FireDucksのデータ形式を一度pandasのデータ形式に変換し，またfallback結果を逆に変換すると言った処理が必要になります．この処理がkernel実行のところにある`fireducks.to_pandas.frame.metadata`と`fireducks.from_pandas.frame.metadata`になります．これらも含めるとfallbackに関わる時間は全体の13%ほどとなります．fallbackつぶしは継続的に行っていますので，少しずつ改善して行きたいと思います．

## fallbackについて

fallback実行されるかどうかはメソッドによって決まっているわけではなく，メソッドの引数によってもkernel実行だったり，fallback実行だったりが変わります．残念ながら現在はfallback実行されるのかどうかはドキュメントなどにはなっておらず，実行してみないと分からないのですが，プログラム実行時に環境変数で`FIREDUCKS_FLAGS=-Wfallback`を指定することでfallbackが起こったときにWarningを出すことができます．fallbackが起こったソース行と理由が表示されますので，fallbackが起こらないようにユーザープログラム側で工夫して頂いたり，[こちらのIssues](https://github.com/fireducks-dev/fireducks)に報告して頂ければ，頑張って対応していきたいと思います．

また，トレースは，`FIREDUCKS_FLAGS=--trace=3`によってとることができます．実行終了後trace.jsonというファイルがカレントディレクトリに作られます．これはWebブラウザのchrome（`chrome://tracing/`）で見ることができますので，どんな処理に実行時間がかかってるかを見てみるのも面白いかと思います．

## おわりに

今回はマルチスレッド化による高速化について触れましたが，FireDucksは実行時コンパイラを使った最適化も行っております．FireDucksの高速化の仕組みや使い方などは以下の公式サイトをご覧下さい．きっとこのAdvent Calendarでも記事が出てくると思いますので，ご期待ください．

https://fireducks-dev.github.io/ja/

FireDucksは利用者のフィードバックを受けながらアクティブに開発中で，毎週のように更新版をリリースしています．リリース情報などは<https://twitter.com/fireducksdev>で発信しておりますので，こちらもよろしくお願いします．