PyPyの実行環境をDockerで構築する

## はじめに
### PyPyとは？
Pythonの実装のひとつ。
Pythonモジュールをふつうに実行できます。素のPythonで走らせるよりも圧倒的に高速で動作します。（この記事の最後で簡単な速度比較をします）
一方で、利用できるライブラリには限界があるようです。正確に把握していないのですが、サードパーティライブラリはほぼ使えないと思ったほうがよいというイメージ（後述しますがnumpyは使えるようです）。標準ライブラリは使えます。
したがって、標準ライブラリで済むシンプルなPythonスクリプトを速く実行したい、というときに便利です。

### なぜDocker？
ローカルマシンの環境を汚したくないので仮想環境を作りたいのですが、PyPyでの仮想環境の作り方についてあまり情報が見つかりませんでした。
最近Dockerの勉強をしていることや、WindowsのPython環境の動作が怪しいと思うこともあって、じゃあDockerで環境作ろうと思いました。

### 環境
- Windows 10 Home （バージョン20H2 ビルド19042.685）
- WSL2でDockerが使える環境

最近はWindows10 HomeでもWSL2でDockerが動くようになって非常に使いやすくなりました。WSL2のインストール方法はたとえば[Microsoftの公式ページ](https://docs.microsoft.com/ja-jp/windows/wsl/install-win10)を参照してください。その後、Docker Desktopの設定でWSL2を使うよう指定すればOKです。

Macでもほぼまったく同じ手順でできるはずです。

## 手順
では、PyPyの実行環境をDockerで構築する手順を説明していきます。

### ディレクトリ構成
以下のようにディレクトリを構成します。

```
│  docker-compose.yml
└─src
        main.py
```

### ファイル作成
`docker-compose.yml`ファイルを以下のように作成します。

```docker-compose.yml
version: '3'

services:
  pypy:
    image: pypy:3
    volumes:
      - ./src/:/src
    working_dir: /src
    tty: true
```
サービス名を```pypy```としていますが、何でもよいです。あとでこの名前でコンテナに入るので、自分にとって分かりやすいようにプロジェクト名をつければ良いと思います。
`image`でDockerHubから[PyPyのイメージ](https://hub.docker.com/_/pypy)を持ってきています。
`volumes`でホストPCのsrcディレクトリをコンテナのsrcディレクトリにマウントしています。これでホストPCの編集がコンテナに反映されます。
`working_dir`でsrcディレクトリを指定することで、あとでコンテナに入るときにここに入ることができます。
`tty`をtrueにすることで、コンテナを立ち上げたあと起動しっぱなしとなり、コンテナに入ることができます。

```main.py```は以下のようにHello worldするだけです。

```main.py
print("Hello, world!)
```


### 実行
`docker-compose.yml`のあるディレクトリでコマンドプロンプトを開き、以下のコマンドでコンテナを立ち上げます。

```
>docker-compose up -d
```
初回はちょっと時間がかかります。
オプション`-d`をつけることでバックグラウンド実行となり、このあとのコマンド操作が可能になります。

コンテナが立ち上がったら、以下のコマンドでコンテナに入ります。


```
>docker-compose exec pypy bash
```

ここで、引数```pypy```はdocker-compose.ymlファイルでサービス名としてつけたものです。
引数`bash`はコンテナに入ったあとbashで操作を行うために必要です。というかこの引数がないとエラーになります。

コンテナ内のsrcディレクトリに入ったので、直下に`main.py`があります。
pypyコマンドで実行できます。

```
# pypy main.py
Hello, world!
```

### ソースの編集
srcディレクトリをマウントしているので、ホストPCでの編集結果がコンテナにも反映されます。
たとえば下記のようにホストPCでmain.pyを編集し、コンテナで再度実行すると、たしかに反映されていることが分かります。

```main.py
print("Hello, world!hogehoge)
```
```
# pypy main.py
Hello, world!hogehoge
```
### ライブラリの管理
ライブラリはpipでインストールできます。たとえばコンテナ内で下記コマンドでnumpyをインストールできます。

```
# pip install numpy
```

※Pythonで使えるライブラリを全てPyPyで使えるわけではないらしいですが、どうやらnumpyは普通に使えるみたいです

以下のようにnumpyを実行するスクリプトを書いて試してみると、たしかに実行できています。

```np_test.py
import numpy as np

print(np.array([1, 2, 3]))
```

```
# pypy np_test.py
[1 2 3]
```

### コンテナ終了
使い終わったらexitコマンドでコンテナから抜けます。

```
# exit
```

コンテナは立ち上げっぱなしでもいいですが、使い終わったら落としておくとよいと思います。

```
>docker-compose down
```
使うときはもう一度```docker-compose up -d```します。2回目以降はホストPCにイメージが残っているので、素早く立ち上がります。

## 補足
###なぜdocker-compose？
`docker-compose up`コマンドで一発でコンテナを立ち上げたかったから。
もちろんDockerfileを作ってビルドしてからdocker runしてもいいのですが、そうするとコマンドに色々引数をつけなきゃいけなくなって面倒だと思ったので。
[DockerHubのPyPyイメージのページ](https://hub.docker.com/_/pypy)ではそのようなやり方が説明されています。

### PyPyの速度検証
ついでなので、PythonとPyPyの速度を比べてみます。
以下の記事を参考に、39番目のフィボナッチ数を求めるスクリプトを書いて速度を比較してみました。

[【まとめ】フィボナッチ数だけで50ぐらいのプログラム言語に精通したつもりになる](https://qiita.com/y_irabu/items/604b0987aa7c8ec52c65#%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0%E3%81%A0%E3%81%91%E3%81%A7%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E8%A8%80%E8%AA%9E%E3%81%AB%E7%B2%BE%E9%80%9A%E3%81%97%E3%81%9F%E3%81%A4%E3%82%82%E3%82%8A%E3%81%AB%E3%81%AA%E3%82%8B)

```fib.py
import time

def fib(n):
    if n < 2 :
        return n
    else:
        return fib(n-2) + fib(n-1)

# 39番目のフィボナッチ数を求めるまでの時間を計測する
s = time.time()
print(fib(39))
e = time.time()
print("time: " + str(e - s) + "sec")
```

結果は以下のようになりました。

- PyPy: 0.958sec （バージョン7.3.3）
- Python: 28.868sec (バージョン3.7.3）

圧倒的にPyPyが速いですね。[元記事](https://qiita.com/y_irabu/items/604b0987aa7c8ec52c65#%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0%E3%81%A0%E3%81%91%E3%81%A7%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E8%A8%80%E8%AA%9E%E3%81%AB%E7%B2%BE%E9%80%9A%E3%81%97%E3%81%9F%E3%81%A4%E3%82%82%E3%82%8A%E3%81%AB%E3%81%AA%E3%82%8B)ではもっと色々な言語の速度が比較されていますので、ご参考までに。

## おわりに
PyPyは速度が速くて便利だと思うのですが、いまいち情報が少ないです。だれかのお役に立てれば幸いです。
私は[秘境集落探索ツール](https://search-isolated-villages-2.herokuapp.com/)というものを作成しまして、これのデータ前処理にPyPyを使ったのですが、素のPythonでは永遠に終わらないような処理がすぐに終わって感動しました。よければこのツールも遊んでやってください（宣伝）。

（追記）続編（？）として「[PyPyとPythonの実行環境をDockerで構築する](https://qiita.com/otomo6sm/items/e6278981a7706b764f1e)を公開しました。今回はPyPyの実行環境のみを扱いましたが、この記事ではPyPyと素のPythonを併用できる環境について解説しています。