Rayとは
RayはPythonにおける分散並列処理を高速かつシンプルに書けるフレームワークで、既存のコードを並列化することも容易な設計となっています。
Rayを使うことでmultiprocessingなどに比べ簡単にプロセスレベルの並列処理を記述することができます。Rayは2021年9月現在MacOSとLinuxをサポートしています。Windowsのサポートは実験段階であり、開発中です。
ローカル環境を構築する予定でしたが、既知のバグがあるらしく動かなかったので、仕方なくDocker環境を構築します。
環境
macOS Big Sur 11.5.2
docker 20.10.7
python 3.7.7 (dockerコンテナ内)
M1MacにDockerDesktopをインストール
M1MacにDockerDesktopをインストールするには、上のリンクを踏んでダウンロードする必要があります。
さらに、Rosetta2もインストールしなければならないため、公式ページの指示に従って以下のコマンドをターミナルで叩きます。
softwareupdate --install-rosetta
これでDockerが使用可能になります。
ちなみに、2021年8月31日にDocker社がDocker Desktopを有料化することを発表したようですね。個人開発なら無料で使えますが、面倒なことになりました。
RayのDocker ImageをPullする
DockerHubから公式のImageをローカルにPullします。
docker pull rayproject/ray
コンテナ内でjupyter notebookを実行する
まず、Docker Imageからコンテナを作成し、中に入ります。
docker run --shm-size=512M -p 8888:8888 -it rayproject/ray
公式ページによると、shm-sizeには512M~2G程度の値を設定するようです。
また、jupyter notebookを使用する関係でポートを公開しています。
コンテナ上のポートとローカルのポートを接続するルールは、-p [ローカルのポート]:[コンテナのポート] です。
コンテナ内に入ったら、jupyter をインストール後、サーバを立てます。
pip install jupyter
jupyter notebook --ip=0.0.0.0 --allow-root
Docker上でjupyter notebookを実行する方法は以下を参考にしました。
ブラウザでnotebookにアクセス
ローカルPCのブラウザでコンテナで起動したnotebookにアクセスします。
ブラウザに下記URLを打ち込みます。
localhost:8888
するとJupyter Notebookの画面に遷移してPassword or token:
と入力が求められます。
入力するためのtokenはコンテナ上でnotebookを起動したときに以下のようなURLが出るのでそこから取得します。
Copy/paste this URL into your browser when you connect for the first time,
to login with a token:
http://5217807fea51:8888/?token=601c330c097e59cd7b69dcaa6ea336218583a492e4684bde&token=601c330c097e59cd7b69dcaa6ea336218583a492e4684bde
上の場合、601c330c097e59cd7b69dcaa6ea336218583a492e4684bde
をPassword or token:
に入力すればいつものnotebookの画面に遷移します。
サンプルコードを動かし動作確認を行う
チュートリアルの解説に関しては以下のページが神です。
サンプルコードがエラーなく実行できれば成功です。
import ray
import time
# ray.init() のように明示的に指定しなかった場合自動的にリソース数が決定されます
ray.init(num_cpus=4)
# 時間計測をより正確にする都合上Rayの起動を少し待つ
time.sleep(1)
@ray.remote
def func(x):
time.sleep(5)
return x
begin_time = time.time()
res1, res2 = func.remote(1), func.remote(2)
print(ray.get(res1), ray.get(res2)) # 出力: 1 2
# ray.getはreturnをリストで受けとることもできる
print(ray.get([res1, res2])) # 出力: [1, 2]
end_time = time.time()
print(end_time - begin_time) # 5秒ぐらい
実行結果例
1 2
[1, 2]
5.051432132720947
終わりに
標準モジュールのmultiprocessingと比べるとコードが非常に簡単です。まだまだ発展途上のフレームワークですが、いずれ「マルチプロセスといえばRay」というようになっていくでしょう。
なお、コードを速くするという意味では、マルチプロセス化するのは最後の手段です。まずは既存のコードを整理し、充分にリファクタリングしたいところです。