PythonのExcel出力時のメモリ使用量を抑える
pyexcelizeは内部的にgoのexcelizeを利用した、Excel出力時のメモリ使用量を抑えることができるライブラリです。OpenPyXLなどを使ったExcel出力でメモリ使用量が1GBを超えてしまうような場合にお勧めできます。
OpenPyXLの問題点
ファイルの読み込みを伴うExcelの書き込みで省メモリ化にする手段がありません。
大抵の場合、Excel出力は空の状態から作成するため特に問題が起きません。しかし、ピボットテーブルやグラフなどの情報を予めExcelに書いておき、後からデータを出力したいケースがあります。
OpenPyXLは生成するファイルの50倍のメモリを使用することがアナウンスされています。回避するにはOptimised Modesのwrite-onlyモードが使えますが、これは既に作られたExcelファイルを読み込む場合には使用できません。
この問題だけでAWSのインスタンスタイプを高額なものに変更するかどうかの判断に迫られました。
Goのexcelizeライブラリで検証
Pythonの既存ライブラリでは問題を解消できる見込みがなかったため、他の言語で調査したところGoのexcelizeがカバーしている機能が多かったです。性能について調査したところ、StreamWriterを使えばPythonと比べて非常に高速で省メモリ、かつ、ピボットテーブルやグラフなどは読み込んだ状態でそのまま出力できることがわかりました。
これにより、Excel出力時の消費メモリが4GBから80MBまで下げれました。
逆に、下記のようなセル単位、または行単位に値を書き込むAPIを大量に呼ぶとOpenPyXLよりメモリ消費することがわかりました。
- https://xuri.me/excelize/ja/sheet.html#SetSheetRow
- https://xuri.me/excelize/ja/cell.html#SetCellValue
Pythonから呼べるように
Goを使えば良いのは分かったのですが、会社のチームでGoに慣れ親しんでいる人が少なかったのでPythonからexcelizeを呼べるパッケージを作ることにしました。
余談:当初poetryでパッケージを作ろうと思っていたのですが、goのビルドを含める方法がわからなかったため、setup.pyを書く方式に変えました。
他の回避方法
- write-onlyモードでプログラムで全部書く。
- Excel使わない。csv使う。
課題など
- GoのファイルポインタなどをPythonに渡すときにポインタの型変換がうまく出来ずに値が丸められてしまった。cgoに詳しくなくて断念しました。
- インタフェースが荒削り。OpenPyXLの使いやすさまで近づけたいです。
- CI環境ができていない。Githubが使いこなせていません。
Discussion