Pythonでの高速化の選択肢

[Python プログラマのスキル標準化のための項目](https://qiita.com/nonbiri15/items/6461aa4dd82183fa7014)

の目的のため、高速化アプローチについて概観するためのメモを作成する。

### 想定する読者
python の中級者
pypy, Numba などの上級者向けのことは書いてありません。

## 自作モジュールは必要なのか
- その分野で使われだしているOSSのモジュールはないか？
- 車輪の再発明をしなくてよい。
- OSSのモジュールで標準化されていれば、次の作業は不要になる可能性が高い
    - CPU,　GPUのサポート
    - マルチコアを使った高速化のサポート
- 自分たちが着目している分野のOSSでどういうライブラリが使われているのかを確認しよう。
- そのライブラリを使えば、自作ライブラリを書く量が減る。
- 既存のライブラリに１つ機能を追加したほうが、全体がすっきりする。
- 既存のgithubのリポジトリをforkして実装を作り、動作検証後のものを本家にPRを上げる。
- （そうすれば、自分がメンテナンスし続けなければならないライブラリが減る。）
以下は自作モジュールが必要な前提で書く。

## 処理時間を計測する
- 例 cv2.getTickCount() を使う
timeit
[timeit --- 小さなコードスニペットの実行時間計測](https://docs.python.org/ja/3/library/timeit.html)
[Pythonのtimeitモジュールで処理時間を計測](https://note.nkmk.me/python-timeit-measure/)

## cProfile で処理時間の内訳を見よう。

`-m cProfile -s cumtime `を追加するだけ
- 関数の呼び出し回数は妥当ですか？
例：画像の正規化処理などを無駄に繰り返している。

```
python3 -m cProfile -s cumtime myprog.py
```
### 画像処理・画像認識の場合には、毎回の処理時間の方が重要
- 初期化処理
- 毎回の処理
- 終了処理
これらのうち、画像処理・画像認識の場合には、毎回の処理時間の方が重要。
##### 最近の機械学習の分野では、ひっそりとデータファイルを自動でダウンロードしていることがあります。
- 毎回のプログラムの実行の中でダウンロードするのではなく、環境構築の際にデータをダウンロードするように書き換えてはどうでしょう。



## 無駄に遅くしているもの
- matplotlib での描画
- 学習済みのモデルファイルのダウンロード
    - 環境構築の際に一度だけ実行すればよいように書き換える。

- 学習済みのモデルファイルのtensorRTへの書き換えを立ち上げに毎回行なってしまう
    - 環境構築の際に一度だけ実行すればよいように書き換える。
- 複数人が利用する場合には変換済みのものをダウンロードする。

- for 文を使って画素毎に処理しているコード

[numpyで明示的にループを書くと極端に遅くなる](https://qiita.com/nonbiri15/items/ef97b84832055ab807fb)
[画像処理で2重ループをなるべく書くな](https://qiita.com/nonbiri15/items/9d4296b3a79fad38bf76)

- generator で十分なところをlistにするな。
```.py:
for p in glob.glob("**/*.jpg"):
    print(p)
```
ならば、jpgファイルを全数探し終わってなくても、表示を開始する。
```.py:
for p in sorted(glob.glob("**/*.jpg")):
    print(p)
```
とすると、全数探して、並び替えを完了してからでないと表示が始まらない。



- list.extend()で十分なところを+で代入をし直すな。
悪い例
```.py:
a = [1, 2, 3]
a = a + [4, 5, 6]
```
良い例
```.py:
a = [1, 2, 3]
a.extend([4, 5, 6])
```

さらには、配列の大きさが予めわかっている場合には、最初に`np.zeros(shape, dtype=np.float)` などとして領域を確保してから、
該当の行を代入していくということで、領域の確保に関する処理時間を減らせることがあります。


- Pythonはなにも考えないと、シングルスレッド・シングルコアで動作する。
- multiスレッドで動くライブラリを使う
[threading --- スレッドベースの並列処理](https://docs.python.org/ja/3/library/threading.html)
[Trio – a friendly Python library for async concurrency and I/O](https://github.com/python-trio/trio)

- Cythonを使う
[Cython ―Cとの融合によるPythonの高速化](https://www.oreilly.co.jp/books/9784873117270/)

- C++の数値計算ライブラリをOpenMPのpragmaを使って書き換える。
[並行コンピューティング技法 ―実践マルチコア/マルチスレッドプログラミング](https://www.oreilly.co.jp/books/9784873114354/)

```
#pragma omp parallel
```
をキーワードに検索してみてください。

- numpyでの実装をpytorchでの実装に書き換えて、GPUを使っての動作を高速化させる。
ChatGPTでも次の質問に答えて例を書いてくれます。
「numpy をpytorchに書き換える例を示してください。」

- CPU-GPU 間のメモリ転送を減らす。
- deploy 環境に合わせたモデルに変換する。
例: pytorch -> TensorRT
例： pyTorch -> onnx
    
## 数値データを保存する際にバイナリモードの形式があれば利用する。
数値データをasciiデータファイルで保存し、
asciiデータファイルを数値データとして読み込むのは
処理が重くなります。

対象のデータファイルにバイナリモードがあれば、
バイナリデータでデータを保存し、それを読み込むようにします。

例：点群データのply ファイルにもASCIIとバイナリの2つのバージョンが存在します。
利用するツールやライブラリがバイナリモードがあれば、
その分、処理が速くなります。


## opencv-python のbuild 条件を確認する。
OpenCVのモジュールは、build 条件によって性能が著しく変化します。
CPUの種類に応じた最適化が入っているか
Video I/O: はどうなっているのか
などです。

 GStreamer: 
を使いたければ、CMakeでGStreamerを有効化する設定にして、cmake, make しなければなりません。

- [OpenCVのビルド情報を確認するgetBuildInformation()](https://note.nkmk.me/python-opencv-getbuildinformation/)

## 必ず使うとは限らないライブラリのimportは後回しにする。
スクリプトの動作改善するためには、
必ず使うとは限らないライブラリのimportは後回しにするのも手だ。

## 付記: docker build のキャッシュが有効になるDockerfileの記述順序
Dockerfile の記述のうち、変更を生じやすいものを後ろにする。
変更を生じない部分は、前回のdocker build の結果が利用されるので、変更部分以降しか処理されない。

## 参考記事
[Pythonプログラムが遅い！高速化したい！そんな時は...](https://qiita.com/sotasato/items/cc36a532ba6487dd3dba)