FireDucks vs Polars 42勝24敗！ あなたはどっちを使ってpandasを高速化する！？

こちらは [NEC デジタルテクノロジー開発研究所 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/nec-dt-labs)の24日目の記事です．
このAdvent Calendarでは我々の開発するFireDucksを紹介する記事がいくつかありましたが，本日はFireDucksと他の高速データフレームライブラリの比較を行いたいと思います．

## pandas and others


データフレームと言えば[pandas](https://pandas.pydata.org/)だと言うほどに，圧倒的にpandasの利用者が多いと思いますが，10年以上前から開発されているこの老舗ライブラリを置き換えようといくつかのデータフレームライブラリが登場しています．その中でも，特別なハードウェア（GPUやマルチノード環境）が必要なく，pandasユーザーに使いやすいのは，[Modin](https://github.com/modin-project/modin)と[FireDucks](https://fireducks-dev.github.io/ja/)でしょう．この二つはpandasとAPI互換を謳ってるので，importの変更程度でほぼそのままで利用することができます．

また，最近人気急上昇中なライブラリは[Polars](https://pola.rs/)でしょうか．こちらはpandasとはAPI互換でないので，pandasユーザーにとっては書き換えや学習が必要となりますが，"Blazingly fast DataFrames"ということで高速性が売りで，開発も活発に行われているようです（APIについても賛否両論あるようですね）．

ということで，今回はpandas, Modin, FireDucks, Polarsの比較を行ってみたいと思います．

## ベンチマークプログラム

さて，性能比較を行うにはどのベンチマークを使うかが非常に重要なのですが，今回は
Polarsの開発者達が公開している[polars-tpch](https://github.com/pola-rs/tpch)というベンチマークを用いることにしました．Polarsはpandas互換ではないためどうしても実装差による影響が入ってしまうので，我々が実装するよりもPolars開発者が実装したものが良いだろうと思ってこのベンチマークにしました．

[TPC-Hベンチマーク](https://www.tpc.org/tpch/)はTPCという著名なベンチマーク集の一つで，22種類のクエリと呼ばれるデータの集計や分析関連の処理からなります．polars-tpchには，この22クエリ分のpolars実装が含まれているのですが，pandas, modin用には8クエリしかないので，今回は我々が評価のためにpandasで実装していたクエリをpolars-tpchに組み込んでpandas, modin, FireDucksの評価に利用しました．これらはimportの変更だけでコードはほぼ共有です．

マシンは24コアXeon，メモリ256GBを用いました．評価の設定の詳細はAppendixをご覧ください．

## 予選: pandas, Modin, Polars, FireDucksの比較

TPC-HはScale Factorというパラメータによってデータセットのサイズを変更することができるのですが，以下のグラフはsf=10という10GB程度のデータセットで評価した結果です．pandasを1とした場合の速度比の対数グラフで，1以上（棒が上に伸びている）ものがpandasより速いことを示しています．

![20231225-vs-pandas.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/946075/985ea911-cbc7-c9a5-33a9-780352d13df3.png)

PolarsとFireDucksは全22クエリでpandasを上回っており，最大では60倍，88倍と大きな速度向上が得られており，平均でも12倍と17倍と10倍以上となっています．一方，Modinは最大では8.6倍のクエリがあるものの，pandasより遅いものもあり平均は1.8倍でした．Modinはもしかすると，性能を出すには何か特別な設定などが必要なのかもしれませんが，今回はpolars-tpchの動かし方に従っただけで，特に特別なことはしていません．

さて，sf=10ではPolarsやFireDucksは大半のクエリが1秒以下で終わってしまい測定誤差の影響もあるので，より大きいデータセットで評価したいと思います．ただし，pandasやModinは測定に時間がかかりすぎて大変なので，ここまでとしたい思います．

## 決勝: FireDucks vs Polars

データセットをsf=10, 20, 50と大きくしていったときの結果が以下のグラフになります．今度はpolarsを1としたFireDucksの性能向上を示しており，1より大きいとFireDucksのほうが性能が良いことを示しています．対数グラフですので，上と下に伸びてる棒の長さがどちらがどれくらい良いかを示しています．

![20231225-fireducks-vs-polars.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/946075/1e6cf9f0-3659-4924-82ee-04f56d27bb84.png)

まとめると以下の様な結果となっています．

- sf=10: FireDucksの13勝9敗（平均は1.4倍）
- sf=20: FireDucksの14勝8敗（平均は1.4倍）
- sf=50: FireDucksの15勝7敗（平均は1.6倍）

全66戦ではFireDucksの42勝24敗で勝率64%でした．日本シリーズに例えると4勝2敗相当なので，今回はFireDucksの勝ち！ということにしたいと思います．

## 結果分析

FireDucksとしては全勝を目指したいので，少し分析してみたいと思います．以下はFireDucksのトレース機能を使って測定したsf=50のときの全22クエリ合計での処理時間の内訳のトップ10です．

```
                          duration sec ratio     count
== kernel ==
fireducks.join                  21.794   38.36%         57
fireducks.groupby_agg           13.793   24.28%         22
fireducks.filter                 8.038   14.15%         49
fireducks.lt.vector.scalar       2.306    4.06%         10
fireducks.ge.vector.scalar       1.453    2.56%         15
fireducks.le.vector.scalar       1.177    2.07%         10
fireducks.copy                   1.071    1.88%          1
fireducks.eq.vector.scalar       0.944    1.66%         30
fireducks.lt.vector.vector       0.522    0.92%          4
fireducks.str_contains           0.510    0.90%          3
```

上位三つのgroupby, join，filter処理が8割弱を占めています．各クエリ毎に見ても概ね同じ傾向で，どのクエリもこの三つの処理が支配的となっています．というような状況でもクエリによって，FireDucksが良かったり，Polarsが良かったりするので，例えばgroupbyはFireDucksが速いと言った単純なAPIレベルでの勝ち負けを言うことはできず，利用されるデータの特徴などに依るようです．実際にFireDucksは複数のGroupByのアルゴリズムを実装し，データによって変えると言った工夫もしています．

https://qiita.com/ysyk-ohno/items/ddaaeca630d741a78f14

全勝を目指すにはこういう工夫を重ねていく必要がありそうです．各クエリの詳細を分析しながら，今後もFireDucksの改善を継続していきたいと思います．

## 終わりに

今回はpolars-tpchベンチマークを用いたデータフレームライブラリの性能比較を行いました．現時点ではFireDucksがちょっとリードという結果ですが，ライバル達も活発に開発されているので，今後も頑張っていきたいと思います．

データフレームライブラリは今ホットな分野で，この記事で使ったもの以外にもDask, Vaex, PySpark, cudfなどがあります．今回はシングルノード・インメモリでの性能評価を行いましたが，Out-of-core（メモリ乗り切らないような巨大データ）や分散環境での性能，pandas互換性を重視するかどうかなど様々な観点があり，各ライブラリはそれぞれ特徴を持っています．そんな中でFireDucksは，pandasチルドレンの白眉を目指して，pandas互換性とシングルノード性能を重視して，この分野の盛り上がりに貢献していきたいと思っています．

最後になりましたが，FireDucksのバグ報告，Q&A，リクエストなどは以下で受け付けておりますので，何かお気づきの点がありましたら是非ご投稿ください．

https://github.com/fireducks-dev/fireducks

それでは良いお年を！

## Appendix: 測定条件詳細

TPC-Hはそんなに複雑な処理はしていないので，IO（CSVやparquetの読み込み）を測定に入れると，そこばかりに時間がかかってしまうので，今回はIO以外を測定範囲としています（polars-tpchは元からそうなっています）．測定はそれぞれ3回行い最速値を採用しています．

入力データの形式にはparquetを用いました（csvだと読み込みに時間がかかるので）．parquetはメタ情報などを埋め込めるので，今回は念のためparquetの作成は第三者のpyarrowで行いました．

評価に用いた各ライブラリのバージョンは以下になります．測定時点の最新バージョンです．

- pandas: 2.1.4
- Modin: 0.26.0
- Polars: 0.20.2
- FireDucks: 0.9.3

評価に用いたサーバー

- CPU: Intel(R) Xeon(R) Gold 5317 CPU @ 3.00GHz x 2sockets （合計48HWスレッド）
- メインメモリ: 256GB