Speeding up Python

# Python

* Multi - Thred [1]
	* 同一CPUでメモリを共有
	* メモリの書き込みや読み込み (I/O処理)でタイミングの処理が重要
* Multi - Process
	* それぞれのCPUでメモリは共有されない

pythonでは，グローバルインタプリタロック (GIL)によって，1プロセッサ当り1スレッドの実行に制限される．このため計算処理の多いプログラムにマルチスレッドを設定しても，予想通りにパフォーマンスが上がらないことがある [2]．
よって，コードを走らせる前に，スレッド数とコア数を確認した方が良い．

GILを回避するためには、mpi4pyを使うか、Cで書かれている高速ライブラリのNumpy, Scipyを使用する関数内でGILの効かない並列化を行うのが効率が良い．
I/Oに時間がかかっているプログラムに対しては、GILの影響はそれほど出ないので、マルチスレッドに適している。

スレッド数とコア数を確かめるプログラム [3]：

```python
import psutil
import os
import multiprocessing as mp

def ThreadNum():
    nthreads = psutil.cpu_count(logical=True)
    ncores = psutil.cpu_count(logical=False)
    nthreads_per_core = nthreads // ncores
    try:
        nthread_available= len(os.sched_getaffinity(0))
    except AttributeError:
        nthread_available=mp.cpu_count()
    
    ncores_available = nthreads_available // nthreads_per_core
    print(f'{nthreads=}')
    print(f'{ncores=}')
    print(f'{nthreads_per_core=}')
    print(f'{nthreads_available=}')
    print(f'{ncores_available=}')

```

## CuPy
CuPyはNumPyとSciPyで記述されたCPU用のプログラムをGPUで動かすためのツール．
簡単な移植作業により，GPUの高い計算性能を生かして高速に計算することができる．

CuPyのNumPyとの主な違いは以下の通り [4]。

* numpyをcupyに変える
* hostメモリーをdeviceメモリーを転送するには```cupy.array```関数を使用 (```array```関数と```asarray```関数は同じ)
* * deviceメモリーをhostメモリーに転送するには```cupy.asnumpy```関数を使用 
* CuPyではカーネル起動回数をなるべく少なくする

ベクトル及び行列計算における，NumPy, SciPy, CuPyの例は他に[ここ](https://hpcworld.jp/techcolumn/cupy-python-gpu%E3%81%A7%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E9%AB%98%E9%80%9F%E5%8C%96%E3%82%92%E3%82%88%E3%82%8A%E8%BA%AB%E8%BF%91%E3%81%AB/)を参照．

##  Numba for CUDA GPUs
* NVIDIA社のグラフィックスボード (GPU)を用いて高速に計算することができる
* C版のCUDAとほぼ同様のプログラムになる
* CuPy同様，カーネル起動回数をなるべく少なくする

## Numba
jit化するもので，並列化を別のライブラリで行う (例えば[この例](https://qiita.com/Yuricst/items/a400e49e714cb105e415)だとRay)．
間違えやすいポイントがある[5]

## 2次元配列
2次元配列の計算方法には各種あり，方法によって計算時間に大きな差がでる．例えば，

*  配列演算
	*  プログラムが簡単になる
	*  計算時間が短縮され，プロードキャストという技術を用いるとプログラムがより簡単になる
*  Numba
	* for文のままで高速化できる
	* 配列演算と併用することもできる

## cuDF
Pandasの強い版？

## multiprocessing
並列処理を行うpythonの標準ライブラリ [6][7]

## concurrent.future
Pythonの標準ライブラリで，

* ThreadPoolExecutor：スレッドベース
* ProcessPoolExecutor：プロセスベース
の2つがある [8]．

## Joblib
並列処理をはじめとする，以下の機能を提供．シンプルで簡単らしい [9][10]．

* 並列化
* メモ化：計算結果を記録しておくことにより，重複する引数による関数の再計算を防ぎ，特に同じ引数が頻出する場面で高速化可能
* 直列化 (シリアライズ)：大容量の計算結果を圧縮し，保存・取り出す

また，コード量が減り，

* 子プロセスで吐いたエラーも表示してくれる
* Ctrl+cで終了した時に子プロセスも終了してくれる
* 自動で全体の進捗を表示してくれるオプションがある

という利点もある [11]．

ただし，クラスメソッドに適用すると，オーバーヘッドにより遅くなることが報告されている [12]．

## Ray
multiprocessingの代わりになってくれるよう．
分散並列処理をPythonicなコードで実現できる [13]．


## Tips
* 並列計算のオーバーヘッドにより計算効率が落ち，下位のnumpyのレベルでのマルチスレッド数を制限しておく必要があるときは，[これ](https://qiita.com/yymgt/items/b7f151ee99fb830ca64c)をみる．ただし，Visual Studio Codeでの実行を想定しているよう．

## まとめ？
* for文を書かない
* ライブラリを探す
* ドキュメントを読む
* NumPyが使えるときはNumPyを使い，NumPyが使えないときはNumbaを使用する

# Refecences
[1] [Pythonの並行処理を理解したい [マルチスレッド編]](https://zenn.dev/ryo_kawamata/articles/python-concurrent-thread)
[2] [Pythonで並列処理のすすめ](https://zenn.dev/forrep/articles/5da59f28a7732b)
[3] [Python 並列化　Thred vs Process](https://note.com/dean_ediburgh/n/n5e8c8f6fbf94)
[4] [高速化プログラミング入門 (Python版)](http://emoss.starfree.jp/tutorial_python/chap1.htm)
[5] [Python を Numba で高速化するときの間違えやすいポイントまとめ](https://qiita.com/tariaki/items/83a9115d672fe9c3becb)
[6] [pythonで並列化入門 (multiprocessing.Pool)](https://qiita.com/studio_haneya/items/1cf192a0185e12c7559b)
[7] [multiprocessing](https://docs.python.org/3/library/multiprocessing.html)
[8] [Python concurrent.futureを用いた並列処理のさせ方メモ](https://note.com/mega_gorilla/n/n3fe706a9a950)
[9] [Joblibの様々な便利機能を把握する](https://qiita.com/Tatejimaru137/items/c3aabd17196543fdfd20)
[10] [Python, Joblibでシンプルな並列処理（joblib.Parallel）](https://note.nkmk.me/python-joblib-parallel-usage/)
[11] [[Python] Joblibでお手軽並列処理](https://qiita.com/Yuhsak/items/1e8533343cf5458e2e08)
[12] [クラスメソッドをjoblibしたらかえって遅くなった話](https://zenn.dev/mosamosa/articles/3ca3d38f90bea0)
[13] [Pythonの分散並列処理ライブラリRayの使い方](https://horomary.hatenablog.com/entry/2020/09/06/015813)