簡単にPythonスクリプトから実行ファイルを作れるNUITKA-Utilities hinted-compilationの使い方

# はじめに
Pythonスクリプトを他人に渡したいとき、相手のPCでも環境構築するのは手間です。可能なら実行ファイルにしたいと思います。それを実現する便利なモジュールとして、PyinstallerやNuitkaがあります。後者はCに変換してからコンパイルを行い、実行ファイルの容量が小さい、実行速度が速いといった特徴があるそうです。

Nuitkaはコンパイルした後、実行してImportErrorが出たら修正するなど面倒なことがあります(Pyinstallerも)。そこでNUITKA-Utilitiesのhinted-compilationを用いて、Pythonスクリプトを一度走らせて使用したモジュールを把握してからコンパイルすることで失敗が減ります。とても簡単に使えて便利だったのでPyinstallerとの比較を交えて紹介したいと思います。

# 参考
こちらでNUITKA-Utilitiesを知りました。hinted-compilation以外のスクリプトの使い方はこちらをご参考にしてください。
[Qiita:NUITKA-Utilitiesで簡単Pythonコンパイル](https://qiita.com/dinosauria123/items/abbbc01362cf41d6b9d8)

# 1. NUITKA-Utilitiesの準備
NUITKA-Utilitiesをダウンロードします。[GitHub:NUITKA-Utilities](https://github.com/Nuitka/NUITKA-Utilities)
予めnuitka, pysimplegui, MinGWのインストールを行います。Anaconda環境の場合は、以下の通りです。

    conda install -c conda-forge nuitka
    conda install -c conda-forge pysimplegui

MinGWのインストールは少し注意が必要で、インストール済みのPythonのアーキテクチャと合わせる必要があります。コマンドプロンプトにPythonと打ち込んで確認します。

    C:\>python
    Python 3.7.4 (default, Aug  9 2019, 18:34:13) [MSC v.1915 64 bit (AMD64)] :: Anaconda, 
    Inc. on win32

[Let'sプログラミング:MinGW-w64のダウンロードとインストール](https://www.javadrive.jp/cstart/install/index6.html)を参考にMinGWをインストールします。
上記の例ではPythonが64bitなので、MinGWのインストーラのArchitectureをx86_x64に変更します。
![MinGWinstaller.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455608/caa90492-d99d-84cc-4f44-2ee1e595acc9.png)

PATHがちゃんと通っているか確認します。

    C:\>gcc --version
    gcc (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
    Copyright (C) 2018 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

別のバージョンがもともとあるなどでPATHを設定したくないときは、MinGWのインストール先のmingw-w64.batを実行すれば良いです。

# 2. NUITKA-Utilities hinted-compilationの使い方
説明のために、コンパイルしたいスクリプトファイルの名前をyourscript.pyとします。

1. NUITKA-Utilities\hinted-compilationディレクトリに移動して、以下のようにスクリプトを実行します。
スクリプトが実行され、使用されたモジュールがjsonファイルに書き出されます。

        python get-hints.py yourscript.py

2. 続いて以下のようにスクリプトを実行します。このときyourscript.pyと先程のjsonファイルは同じ階層に置きます。

        python nuitka-hints.py yourscript.py

これだけでPythonスクリプトファイルから実行ファイルが作れます。ファイルを一つにまとめたい場合は、onefile-maker-windows.pyを使用します。

# 3. ファイル容量、実行速度・起動速度の比較
## コード
SPI3で出てきた問題を題材にコードを書きました。これを例に実行速度と起動速度を計測します。

```python
import numpy as np
import time

def coin_prob(N=1000000):
    """
    10円1枚、5円玉2枚、1円5枚を投げて、
    表が出たコインの金額を足す。
    15円になる確率はいくらか。
    """
    coins = np.array([10,5,5,1,1,1,1,1])
    count=0
    for _ in range(N):
        heads_or_tails = np.random.randint(0,2,len(coins)) #0か1
        if np.sum(coins*heads_or_tails) == 15:
            count += 1
    print("確率は{}".format(count/N))

def main():
    """
    10回の平均時間と標準偏差を求める。
    """
    elapsed_times = []
    for _ in range(10):
        start = time.time()
        coin_prob()
        end = time.time()
        elapsed_times.append(end-start)

    ave_time = np.average(elapsed_times)
    std_time = np.std(elapsed_times)
    print("処理時間={0}±{1}".format(ave_time,std_time))

if __name__ == "__main__":
    start_all = time.time()
    main()
    end_all = time.time()
    print("総経過時間{}".format(end_all-start_all))
```

Pyinstaller、Nuitka双方とも一つのファイルにまとめるオプションはつけていません。

余談ですが、Pyinstaller、Nuitkaともにすんなり行かず、少し苦しみました。
Pyinstallerはこちらの記事を参考に解決しました。
[Qiita:Pyinstallerを使ってPythonコードから生成した実行ファイルについて、実行時エラーModuleNotFoundErrorを回避](https://qiita.com/kanedaq/items/e65507878c52ad67d002)
Nuitkaはmkl関係でうまく行かずこちらで解決。
[ぬうぱんの備忘録:conda 環境下で mkl のロードに失敗するときの対処法あれこれ](http://nu-pan.hatenablog.com/entry/2018/10/12/232502)

## ファイル容量
はじめにNuitkaはファイル容量が小さいと言いましたが、そうでもありませんでした。
これから改良が続いて、余計なモジュールをインポートしなくなるといいですね。

|Original|Pyinstaller|Nuitka|
|:------:|:---------:|:----:|
|1.02KB |569MB|623MB|


## 実行速度
コイン投げ試行1セットの処理時間として、10回の平均と標準偏差を示します。
本旨に外れますが、Numba jitも比較に入れました。高速化を求めるならこちらを使ったほうが良いです。

| |Original|Pyinstaller|Nuitka|Numba jit|
|:--:|:------:|:---------:|:----:|:---:|
|Time s|18.44±0.54|18.30±0.26|4.275±0.076|0.2299±0.1281|
|Gain| 1.000 |1.008|4.314|80.21|

## 起動速度
Windows Server 2003 Resource Kit Toolsのtimeit.exeでプログラムの起動から終了までの時間を計測し、プログラム内部でのmain関数の処理時間との差として、3回計測を行いました。

| |Original|Pyinstaller|Nuitka|
|:--:|:------:|:---------:|:----:|
|Time ms|213.3±2.8|235.8±2.2|88.39±1.72|
|Gain|1.000|0.905|2.414|

# まとめ
ファイル容量を除いて、実行速度・起動速度の点では、Pyinstallerより優れた性能がでました。Pyinstallerよりはエラーが少なく、簡単なのでおすすめです。