Docker上のPython環境で並列処理してみたら、CPU数の設定で意外と手間取った

### 使用環境
OS：Windows10 (Home Edition)
CPU数 : 4
メモリ容量：16GB

DockerにてUbuntu 18.04.1環境のコンテナを作成し、その上で実行した。

### 普通に実行
```Python
import numpy as np

def sum_from_one(N):
    sum = 0
    for i in range(1, N+1):
        sum += i

    return sum

if __name__ == "__main__":
    sum_list = []
    repeat_list = np.random.permutation(np.arange(1, 10001))
    for i in repeat_list:
        sum_list.append(sum_from_one(i))```
```
実行結果

```
%%timeit
    ...: %run nojoblib.py
2.27 s ± 15.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
```

### joblibを使ってみる
＜参考＞
https://qiita.com/Yuhsak/items/1e8533343cf5458e2e08

```Python
from joblib import Parallel, delayed
import numpy as np

def sum_from_one(N):
    sum = 0
    for i in range(1, N+1):
        sum += i

    return sum

if __name__ == "__main__":

    repeat_list = np.random.permutation(np.arange(1, 10001))

    r = Parallel(n_jobs=-1)([delayed(sum_from_one)(i) for i in repeat_list])
```

実行結果

```
In [21]: %%timeit
    ...: %run joblib_test.py
2.13 s ± 13.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
```

… あれ？ほとんど変わらない？？？

タスクマネージャーを見ても、joblibありなしともにCPU使用率が最大でも25%程度。
むしろ、25%で頭打ちになっている感さえあったので、
https://linux.just4fun.biz/?%E9%80%86%E5%BC%95%E3%81%8DUNIX%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89/CPU%E3%82%B3%E3%82%A2%E6%95%B0%E3%82%92%E8%AA%BF%E3%81%B9%E3%82%8B
を参考に認識コア数を調べてみたところ、

```
# cat /proc/cpuinfo | grep processor
processor       : 0
```

はい、本来4つあるうちの一つしか使われてませんでした。

どうやらDockerの設定の問題のようだったので、いったんdocker-machineを終了した後でこちらのサイト
https://qiita.com/niisan-tokyo/items/2d7d21aeb4e25f7a7bbe
を参考に、VirtualBoxマネージャーから設定を変更した。
上記ページでは`システム>マザーボード>メインメモリ`で最大メモリ容量を調整していましたが、
今回は`システム>プロセッサー>プロセッサー数`で最大使用コア数を4まで上げました。
![スクリーンショット 2018-11-18 16.07.41.png](https://qiita-image-store.s3.amazonaws.com/0/210366/8be7b949-6372-9961-8abb-0ef91d4b5ce5.png)
…なんか、「無効な設定が見つかりました」って出てるけど、初期設定に戻しても出てくるから気にしない。
ついでに最大メモリも10GBくらいまで増やしておく。

これでもう一回Docker Quickstart Terminalを立ち上げ、コンテナをもう一回起動して使用CPU数を確かめてみたところ、

```
# cat /proc/cpuinfo | grep processor
processor       : 0
processor       : 1
processor       : 2
processor       : 3
```

今度はちゃんと認識されてました。

改めて先ほどのプログラムを走らせます。
繰り返し回数が10000回程度だと並列化の効果が少しわかりにくかったので100000回にしました。
（ただし、計測のiterationは一回に）

```
# joblibなし
In [1]: %run -t nojoblib.py

IPython CPU timings (estimated):
  User   :     206.52 s.
  System :       1.38 s.
Wall time:     207.92 s.

# joblib使用
In [2]: %run -t joblib_test.py

IPython CPU timings (estimated):
  User   :       4.12 s.
  System :       0.04 s.
Wall time:      62.30 s.
```
無事実行時間が約1/3まで減りました。

1/4にならないのはI/O待ちとかその辺でしょうか？

### multiprocessingモジュール
ついでに、multiprocessingモジュールも使ってみました。

```Python
from multiprocessing import Pool
import multiprocessing as multi
import numpy as np

def sum_from_one(N):
    sum = 0
    for i in range(1, N+1):
        sum += i

    return sum

if __name__ == "__main__":

    repeat_list = np.random.permutation(np.arange(1, 100001))

    p = Pool(multi.cpu_count())
    p.map(sum_from_one, repeat_list)
    p.close()
```

```
In [1]: %run -t multiprocess_test.py

IPython CPU timings (estimated):
  User   :       0.40 s.
  System :       0.16 s.
Wall time:      62.38 s.
```

実行時間はたいしてjoblibと変わらないみたいです。

# 参考
https://qiita.com/yukiB/items/203a6248c2d466b80d38