Let's 計測
alpineでC言語依存モジュールを pip install すると激重になる話 のおまけ
素直にpip installした場合に、どれくらい時間がかかるか試してみた。
手元にあった機械学習用のrequirements.txt
で実行してみると以下のようなログがでてきたので
Building wheels for collected packages: backcall, h5py, kiwisolver, matplotlib, numpy, pandas, pathspec, Pillow, PyYAML, pyzmq, regex, retrying, scikit-learn, scipy, tornado, typed-ast
開発時にも使用頻度高そうなモジュールだけピックアップして計測することにした。
- 実行してみるリスト
- numpy
- pandas
- Pillow
- scipy
- scikit-learn
- h5py
- matplotlib
- regex
実験環境
手元で実行できる環境が二種類あったので両方とも試してみる。
- case1: MacBookAir 2015 13inch intel-corei5 8GB DDR3 ・ docker desktop
- case2: AMD Ryzen9 3900X, M.2 ASM2NE6500GTTD, 8x2GB DDR4 ・ ubuntu18.04(WSL2 integration)
計測方法
timeコマンドを頭につける事で実行時間を計測する。
どちらのケースも同一のdockerfileで実行しており、RUN
命令文中にtime pip install hoge
を埋め込む形を取っている。
FROM python:3.8-alpine3.11
RUN apk update \
&& apk add --virtual .build --no-cache openblas-dev lapack-dev freetype-dev \
gfortran libxml2 g++ gcc zip unzip cmake make \
libpng-dev openssl-dev musl libffi-dev python3-dev libxslt-dev \
libxml2-dev jpeg-dev \
&& apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/edge/testing hdf5-dev
RUN pip install --upgrade --no-cache-dir pip setuptools wheel && \
time pip install --no-cache-dir Cython && \
...
こんな感じ。
docker build
中に出力されるログをまとめたのが次の表となる。
結果
※ Cythonはalpineでもコンパイル不要のモジュールであり、Cython-0.29.16-py2.py3-none-any.whl
のような.whl
形式で降ってきます。非依存モジュールとの比較としてここに載せています。1
module / time | case1 | case2 | ||||
---|---|---|---|---|---|---|
real | user | sys | real | user | sys | |
Cython | 3.07s | 2.33s | 0.42s | 1.52s | 0.99s | 0.07s |
numpy | 5m 28.27s | 7m 6.90s | 22.11s | 2m 11.15s | 3m 23.69s | 6.05s |
pandas | 31m 46.58s | 30m 36.25s | 0m 53.83s | 14m 8.24s | 13m 53.10s | 15.88s |
Pillow | 50.81s | 44.09s | 5.99s | 24.79s | 19.88s | 1.69s |
scipy | 30m 45.99s | 36m 29.33s | 1m 45.89s | 12m 52.81s | 17m 54.87s | 42.58s |
scikit-learn | 14m 38.63s | 14m 3.37s | 28.98s | 6m 33.10s | 6m 24.84s | 10.11s |
h5py | 3m 45.58s | 3m 34.79s | 9.30s | 1m 45.87s | 1m 42.42s | 4.18s |
matplotlib | 2m 51.50s | 2m 35.53s | 13.59s | 1m 21.77s | 1m 13.70s | 6.75s |
regex | 30.52s | 28.84s | 1.24s | 13.75s | 13.06s | 0.34s |
おっっっっそい
ものっっっっっそい、おっっっっっっそい
case1だとnumpyだけでも5分かかるし、pandas・scipyに至っては30分を超えている。scikit-learnに至ってはnumpy・scipyに依存しているので実際のところ一時間は覚悟しないといけない。
挙げた全部合わせると一時間半超だ。
これがどういう事になるか。一日8時間作業すると仮定すると
一日たったの5回程度しかデプロイする事ができないことになる
気軽に開発環境に最新ブランチを充てたり、軽微なバグ修正の確認すら困難になる。作業時間も含めると更に減る。
一応、メモリが倍になると処理速度も倍になるので時間の短縮はできなくもない。
awsを使ったりしているのならメモリ量が多いインスタンスタイプを選択することで時短は可能だ。
しかし、性能的にcase1の二倍以上のcase2だと半分以下の時間で済むとはいえ、全部で40分ほどかかっているのは事実。
一日あたり最高で12回ほどのデプロイしかできない。
ちなみに、表の上から順番にpip install
していったので一部モジュールの実行時間には依存モジュールのインストール実行時間も含まれる。
kiwisolverもtar.gz
で降ってきているようなのでmatplotlib単体はもう少し早いはず。
Successfully built pandas
Installing collected packages: six, python-dateutil, pytz, pandas
Successfully installed pandas-0.25.3 python-dateutil-2.8.1 pytz-2019.3 six-1.14.0
------
Successfully built scikit-learn
Installing collected packages: joblib, scikit-learn
Successfully installed joblib-0.14.1 scikit-learn-0.22.2.post1
-----
Successfully built matplotlib kiwisolver
Installing collected packages: cycler, kiwisolver, pyparsing, matplotlib
Successfully installed cycler-0.10.0 kiwisolver-1.2.0 matplotlib-3.2.1
まとめ
軽いものでも1個あたり1~2分かかるレベルなので、なるべくC言語依存モジュールはビルドしないで済む運用を考える必要がある。condaを使う、は今無しで
余談pip
pip install
時はモジュールのwheel化が完了した直後に、再度whlを展開し直して必要な生成物をまるっとsite-package
直下に移動する。pythonで書かれたコードからビルドされた共有ライブラリ郡*.so
も含めて全てだ。
alpineのようにpython3.8
のバイナリが/usr/local/bin
直下にいるとすると/usr/local/lib/python3.8/site-packages/
に移動する。
再展開は手間のように見えるがこれはPEPに則った安全なモジュールの導入法である。PEP491にもそう書かれている2
試しに、ビルド済みのwhlファイルをunzip
して.so
ファイルを検索してみる。
./numpy/linalg/lapack_lite.cpython-38-x86_64-linux-gnu.so
./numpy/linalg/_umath_linalg.cpython-38-x86_64-linux-gnu.so
./numpy/core/_operand_flag_tests.cpython-38-x86_64-linux-gnu.so
...
それぞれのwhlファイルをpip install
で導入後にライブラリパスに対して.so
ファイルを検索してみると、モジュール毎にビルド済みファイルが存在しているのを確認できる。
/usr/local/lib/python3.8/site-packages/numpy/linalg/lapack_lite.cpython-38-x86_64-linux-gnu.so
/usr/local/lib/python3.8/site-packages/numpy/linalg/_umath_linalg.cpython-38-x86_64-linux-gnu.so
/usr/local/lib/python3.8/site-packages/numpy/core/_operand_flag_tests.cpython-38-x86_64-linux-gnu.so
...
/usr/local/lib/python3.8/site-packages/pandas/io/sas/_sas.cpython-38-x86_64-linux-gnu.so
/usr/local/lib/python3.8/site-packages/pandas/_libs/hashtable.cpython-38-x86_64-linux-gnu.so
...
これでようやくpythonでimportできる訳だが、
裏を返せばpythonがモジュールを検索するパスに生成物さえ居れば使用できる。
なので元記事の回避法では、python関連以外は後に入れること前提で/usr/local/
直下をdockerのマルチステージビルドを使ってえいやでコピペしている。
参考文献
- Wheel files : What is the meaning of “none-any” in protobuf-3.4.0-py2.py3-none-any.whl
- PEP 425 -- Compatibility Tags for Built Distributions
- PEP 491 -- The Wheel Binary Package Format 1.9
-
wheelのありがたさとAnacondaへの要望
- wheel・PEP関連ですごく参考にしました。
- ちなみに現在だと
conda skeleton
でPyPiのwheelを導入できる- ただし、conda独自にビルドし直して導入している模様。個人的には ソウジャナイ感
- anacondaにsentencepieceをwheelファイル経由でインストールする
- ちなみに現在だと
- wheel・PEP関連ですごく参考にしました。