PyTorch C++ VS Python（2020年度版）

**PyTorch**には，Python版だけではなく，C++版のAPIがリリースされています．
同じライブラリでも，コンパイラ言語「C++」とインタプリタ言語「Python」を実行した時にどちらがどのくらい速いかは，非常に気になるところです．
そこで今年も，**<font color="Red">C++とPythonの速度比較</font>**をやってみました！

[前回](https://qiita.com/koba-jon/items/274e5e4970da72216f73)（2019年度）の続きです．
1年前は，特定の環境下では，Pythonのほうが速い場合がありました．
そこで今年も，（TorchScriptを使わない場合の）訓練と推論の時間を計測していきます．

また，今回は，オートエンコーダだけではなく，さまざまなモデルで比較してみました！

実験で使ったソースコード（C++）：https://github.com/koba-jon/pytorch_cpp
→つい最近，YOLOv2もアップロードしました！

# 実験の条件

## 実験環境

- OS: Ubuntu 20.04
- CUDA: 10.2
- cuDNN: 7.6.5
- CPU: Core i7-8700（12スレッド）
- GPU: GeForce GTX 1070

## パッケージ

PyTorch公式：https://pytorch.org/

今回は，以下のパッケージをダウンロードしました．

### Python版

- PyTorch Build: **Stable (1.8.0)**
- Your OS: **Linux**
- Package: **Pip**
- Language: **Python**
- Compute Platform: **CUDA 10.2**

### C++版

- PyTorch Build: **Stable (1.8.0)**
- Your OS: **Linux**
- Package: **LibTorch**
- Language: **C++/Java**
- Compute Platform: **CUDA 10.2**
- Run this Command: **Download here (cxx11 ABI)**

## モデル

### 全結合層のみ
- Autoencoder（1次元）

### 畳み込み層のみ
- Discriminator（DCGANより）

### 畳み込み層＋転置畳み込み層
- Autoencoder（2次元）
- DCGAN

### 畳み込み層＋プーリング層
- SegNet

### 畳み込み層＋プーリング層＋全結合層
- AlexNet
- ResNet（50層）

## 決定論的 or 非決定論的 （GPUオンリー）

GPUを使用する場合，cuDNNの挙動を変えることによって，速度が速くなったり遅くなったりします．
従って，この違いも速度比較に追加します．
ここでは，「再度プログラムを実行して全く同じ結果が得られる場合」は「決定論的」，そうでない場合は「非決定論的」とします．

### Python版

- 決定論的

```Python:deterministic.py
seed = 0
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
np.random.seed(seed)
random.seed(seed)
torch.backends.cudnn.deterministic = True  # 速度が低下する代わりに決定論的
torch.backends.cudnn.benchmark = False     # 速度が低下する代わりに決定論的
```

- 非決定論的

```Python:non_deterministic.py
torch.backends.cudnn.deterministic = False  # 非決定論的である代わりに高速化
torch.backends.cudnn.benchmark = True       # 画像サイズが変化しない場合に高速化
```

### C++版

- 決定論的

```C++:deterministic.cpp
int seed = 0;
torch::manual_seed(seed);
torch::globalContext().setDeterministicCuDNN(true);  // 速度が低下する代わりに決定論的
torch::globalContext().setBenchmarkCuDNN(false);  // 速度が低下する代わりに決定論的
```

- 非決定論的

```C++:non_deterministic.cpp
std::random_device rd;
torch::manual_seed(rd());
torch::globalContext().setDeterministicCuDNN(false);  // 非決定論的である代わりに高速化
torch::globalContext().setBenchmarkCuDNN(true);  // 画像サイズが変化しない場合に高速化
```

## ライブラリの使用状況

使用したライブラリは，以下の通りです．

||Python|C++|
|:------------------:|:----------:|:--------:|
|コマンドライン引数|argparse|boost::program_options|
|モデルの設計|torch.nn|torch::nn|
|前処理（transform）|torchvision.transforms|自作（OpenCV使用）|
|データセットの取得（datasets）|torchvision.datasets（Pillow使用）|自作（OpenCV使用）|
|データローダー（dataloader）|torch.utils.data.DataLoader|自作（OpenMP使用）|
|損失関数（loss）|torch.nn|torch::nn|
|最適化手法（optimizer）|torch.optim|torch::optim|
|誤差逆伝搬法（backward）|torch.Tensor.backward()|torch::Tensor::backward()|
|プログレスバー|tqdm|自作|

※ちなみに，C++において，クラス分類以外でPyTorchをやる場合は，データのロードとビジュアライズ周りの多くは，おそらく自分で作ることになるので覚悟しておきましょう．

## プログラミング言語間で統一させた項目

基本的には，Pythonで有るライブラリがC++には無いといったように，どうしようもない箇所以外は，ほとんど同じと思ってもらって構いません．
また，GitHubのプログラムから変えてないと思ってもらって結構です．
ソースコード（C++）：https://github.com/koba-jon/pytorch_cpp

- データの次元（サイズ）
- データセットの種類（訓練・推論）
- 最適化手法
- モデルの構造
- モデルの初期化方法
- データのロード方法
- データセットのシャッフル方法

## 計測方法

Python: time
C++: std::chrono

- 訓練（Training）

1秒当たりの反復数（iteration数：データロード，順伝搬，逆伝搬，ビジュアライズなどを含む）を計測しました．
C++のプログラムを実行した場合，1[epoch]目が遅く，2[epoch]目から通常の速度になる場合があったため，（特に全結合層のみの場合）2[epoch]目以降の時間をもとに算出しました．

例えば，1[epoch]だけ処理するのに10[s]かかり，1[epoch]が1500[iteration]だったとします．
その場合は，`1500[iteration] / 10[s] = 150[iteration/s]`となります．

- 推論（Inference）

1秒当たりのデータ処理数（順伝搬のみ）を計測しました．
これは，テストデータ全ての順伝搬の平均時間をもとに算出しました．

例えば，順伝搬1回分の処理に平均して0.005[s]かかったとします．
その場合は，`1[forward] / 0.005[s] = 200[forward/s]`となります．

# 実験結果

### 全結合層のみ

データセット：[正規分布データセット](https://github.com/koba-jon/normal_distribution_dataset)
入力データの形状：[N,D]＝[64,300]

- **Autoencoder（1次元）**

訓練：1秒当たりの反復数

<table>
  <tr>
    <th rowspan="2"></th>
    <th rowspan="2">CPU（Core i7-8700）</th>
    <th colspan="2">GPU（GeForce GTX 1070）</th>
  </tr>
  <tr>
    <th>決定論的</th>
    <th>非決定論的</th>
  </tr>
  <tr>
    <td>Python[iteration/s]</td>
    <td>86.83</td>
    <td>97.69</td>
    <td>97.69</td>
  </tr>
  <tr>
    <td>C++[iteration/s]</td>
    <td>312.6</td>
    <td>312.6</td>
    <td>312.6</td>
  </tr>
  <tr>
    <td>速度向上（Python→C++）</td>
    <td>×3.6</td>
    <td>×3.2</td>
    <td>×3.2</td>
  </tr>
</table>

推論：1秒当たりのデータ処理数

<table>
  <tr>
    <th rowspan="2"></th>
    <th rowspan="2">CPU（Core i7-8700）</th>
    <th colspan="2">GPU（GeForce GTX 1070）</th>
  </tr>
  <tr>
    <th>決定論的</th>
    <th>非決定論的</th>
  </tr>
  <tr>
    <td>Python[forward/s]</td>
    <td>3703.7</td>
    <td>2272.73</td>
    <td>2380.95</td>
  </tr>
  <tr>
    <td>C++[forward/s]</td>
    <td>9090.91</td>
    <td>4000</td>
    <td>4166.67</td>
  </tr>
  <tr>
    <td>速度向上（Python→C++）</td>
    <td>×2.45</td>
    <td>×1.76</td>
    <td>×1.75</td>
  </tr>
</table>

![p1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374669/4af4d3a9-b944-201e-d565-ee41250a8d41.png)

### 畳み込み層のみ

データセット：[MNIST](http://yann.lecun.com/exdb/mnist/)
入力データの形状：[N,C,H,W]＝[64,1,64,64]

- **Discriminator（DCGANより）**

訓練：1秒当たりの反復数

<table>
  <tr>
    <th rowspan="2"></th>
    <th rowspan="2">CPU（Core i7-8700）</th>
    <th colspan="2">GPU（GeForce GTX 1070）</th>
  </tr>
  <tr>
    <th>決定論的</th>
    <th>非決定論的</th>
  </tr>
  <tr>
    <td>Python[iteration/s]</td>
    <td>5.24</td>
    <td>27.59</td>
    <td>39.08</td>
  </tr>
  <tr>
    <td>C++[iteration/s]</td>
    <td>4.51</td>
    <td>26.8</td>
    <td>36.08</td>
  </tr>
  <tr>
    <td>速度向上（Python→C++）</td>
    <td>×0.86</td>
    <td>×0.97</td>
    <td>×0.92</td>
  </tr>
</table>

推論：1秒当たりのデータ処理数

<table>
  <tr>
    <th rowspan="2"></th>
    <th rowspan="2">CPU（Core i7-8700）</th>
    <th colspan="2">GPU（GeForce GTX 1070）</th>
  </tr>
  <tr>
    <th>決定論的</th>
    <th>非決定論的</th>
  </tr>
  <tr>
    <td>Python[forward/s]</td>
    <td>222.72</td>
    <td>2040.82</td>
    <td>2000</td>
  </tr>
  <tr>
    <td>C++[forward/s]</td>
    <td>204.92</td>
    <td>3571.43</td>
    <td>3225.81</td>
  </tr>
  <tr>
    <td>速度向上（Python→C++）</td>
    <td>×0.92</td>
    <td>×1.75</td>
    <td>×1.61</td>
  </tr>
</table>

![p2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374669/0bd4ae4c-96ec-9aa6-78aa-616bc6c32c09.png)

### 畳み込み層＋転置畳み込み層

データセット：[CelebA](http://mmlab.ie.cuhk.edu.hk/projects/CelebA.html)
入力データの形状：[N,C,H,W]＝[64,3,64,64]

- **Autoencoder（2次元）**

訓練：1秒当たりの反復数

<table>
  <tr>
    <th rowspan="2"></th>
    <th rowspan="2">CPU（Core i7-8700）</th>
    <th colspan="2">GPU（GeForce GTX 1070）</th>
  </tr>
  <tr>
    <th>決定論的</th>
    <th>非決定論的</th>
  </tr>
  <tr>
    <td>Python[iteration/s]</td>
    <td>1.14</td>
    <td>9.56</td>
    <td>14.39</td>
  </tr>
  <tr>
    <td>C++[iteration/s]</td>
    <td>1.05</td>
    <td>9.16</td>
    <td>13.44</td>
  </tr>
  <tr>
    <td>速度向上（Python→C++）</td>
    <td>×0.92</td>
    <td>×0.96</td>
    <td>×0.93</td>
  </tr>
</table>

推論：1秒当たりのデータ処理数

<table>
  <tr>
    <th rowspan="2"></th>
    <th rowspan="2">CPU（Core i7-8700）</th>
    <th colspan="2">GPU（GeForce GTX 1070）</th>
  </tr>
  <tr>
    <th>決定論的</th>
    <th>非決定論的</th>
  </tr>
  <tr>
    <td>Python[forward/s]</td>
    <td>89.85</td>
    <td>1136.36</td>
    <td>1111.11</td>
  </tr>
  <tr>
    <td>C++[forward/s]</td>
    <td>95.79</td>
    <td>1851.85</td>
    <td>1785.71</td>
  </tr>
  <tr>
    <td>速度向上（Python→C++）</td>
    <td>×1.07</td>
    <td>×1.63</td>
    <td>×1.61</td>
  </tr>
</table>

![p3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374669/442a4385-f652-2519-62e3-ba4377b36991.png)

- **DCGAN**

訓練：1秒当たりの反復数

<table>
  <tr>
    <th rowspan="2"></th>
    <th rowspan="2">CPU（Core i7-8700）</th>
    <th colspan="2">GPU（GeForce GTX 1070）</th>
  </tr>
  <tr>
    <th>決定論的</th>
    <th>非決定論的</th>
  </tr>
  <tr>
    <td>Python[iteration/s]</td>
    <td>0.94</td>
    <td>6.43</td>
    <td>9.53</td>
  </tr>
  <tr>
    <td>C++[iteration/s]</td>
    <td>0.83</td>
    <td>6.28</td>
    <td>9.08</td>
  </tr>
  <tr>
    <td>速度向上（Python→C++）</td>
    <td>×0.88</td>
    <td>×0.98</td>
    <td>×0.95</td>
  </tr>
</table>

推論：1秒当たりのデータ処理数

<table>
  <tr>
    <th rowspan="2"></th>
    <th rowspan="2">CPU（Core i7-8700）</th>
    <th colspan="2">GPU（GeForce GTX 1070）</th>
  </tr>
  <tr>
    <th>決定論的</th>
    <th>非決定論的</th>
  </tr>
  <tr>
    <td>Python[forward/s]</td>
    <td>92</td>
    <td>1063.83</td>
    <td>515.46</td>
  </tr>
  <tr>
    <td>C++[forward/s]</td>
    <td>117.65</td>
    <td>1515.15</td>
    <td>598.8</td>
  </tr>
  <tr>
    <td>速度向上（Python→C++）</td>
    <td>×1.28</td>
    <td>×1.42</td>
    <td>×1.16</td>
  </tr>
</table>

![p4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374669/79911fc4-af6d-9e16-2ac8-89b0b502b6a0.png)

### 畳み込み層＋プーリング層

データセット：[VOC2012](http://host.robots.ox.ac.uk/pascal/VOC/voc2012/)
入力データの形状：[N,C,H,W]＝[64,3,64,64]

- **SegNet**

訓練：1秒当たりの反復数

<table>
  <tr>
    <th rowspan="2"></th>
    <th rowspan="2">CPU（Core i7-8700）</th>
    <th colspan="2">GPU（GeForce GTX 1070）</th>
  </tr>
  <tr>
    <th>決定論的</th>
    <th>非決定論的</th>
  </tr>
  <tr>
    <td>Python[iteration/s]</td>
    <td>0.51</td>
    <td>4.56</td>
    <td>6.83</td>
  </tr>
  <tr>
    <td>C++[iteration/s]</td>
    <td>0.41</td>
    <td>4.1</td>
    <td>5.86</td>
  </tr>
  <tr>
    <td>速度向上（Python→C++）</td>
    <td>×0.81</td>
    <td>×0.9</td>
    <td>×0.86</td>
  </tr>
</table>

推論：1秒当たりのデータ処理数

<table>
  <tr>
    <th rowspan="2"></th>
    <th rowspan="2">CPU（Core i7-8700）</th>
    <th colspan="2">GPU（GeForce GTX 1070）</th>
  </tr>
  <tr>
    <th>決定論的</th>
    <th>非決定論的</th>
  </tr>
  <tr>
    <td>Python[forward/s]</td>
    <td>47.55</td>
    <td>401.61</td>
    <td>331.13</td>
  </tr>
  <tr>
    <td>C++[forward/s]</td>
    <td>53.02</td>
    <td>735.29</td>
    <td>490.2</td>
  </tr>
  <tr>
    <td>速度向上（Python→C++）</td>
    <td>×1.12</td>
    <td>×1.83</td>
    <td>×1.48</td>
  </tr>
</table>

![p5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374669/e5b4fce8-5791-1884-75a8-8260f2aedce4.png)

### 畳み込み層＋プーリング層＋全結合層

データセット：[MNIST](http://yann.lecun.com/exdb/mnist/)
入力データの形状：[N,C,H,W]＝[64,1,224,224]

- **AlexNet**

訓練：1秒当たりの反復数

<table>
  <tr>
    <th rowspan="2"></th>
    <th rowspan="2">CPU（Core i7-8700）</th>
    <th colspan="2">GPU（GeForce GTX 1070）</th>
  </tr>
  <tr>
    <th>決定論的</th>
    <th>非決定論的</th>
  </tr>
  <tr>
    <td>Python[iteration/s]</td>
    <td>0.67</td>
    <td>6.17</td>
    <td>8.45</td>
  </tr>
  <tr>
    <td>C++[iteration/s]</td>
    <td>0.56</td>
    <td>5.27</td>
    <td>6.9</td>
  </tr>
  <tr>
    <td>速度向上（Python→C++）</td>
    <td>×0.84</td>
    <td>×0.85</td>
    <td>×0.82</td>
  </tr>
</table>

推論：1秒当たりのデータ処理数

<table>
  <tr>
    <th rowspan="2"></th>
    <th rowspan="2">CPU（Core i7-8700）</th>
    <th colspan="2">GPU（GeForce GTX 1070）</th>
  </tr>
  <tr>
    <th>決定論的</th>
    <th>非決定論的</th>
  </tr>
  <tr>
    <td>Python[forward/s]</td>
    <td>49.68</td>
    <td>1219.51</td>
    <td>1176.47</td>
  </tr>
  <tr>
    <td>C++[forward/s]</td>
    <td>52.38</td>
    <td>1785.71</td>
    <td>1694.92</td>
  </tr>
  <tr>
    <td>速度向上（Python→C++）</td>
    <td>×1.05</td>
    <td>×1.46</td>
    <td>×1.44</td>
  </tr>
</table>

![p6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374669/94789f23-cbdb-2bb2-15bc-c75055709c5e.png)

- **ResNet（50層）**

訓練：1秒当たりの反復数

<table>
  <tr>
    <th rowspan="2"></th>
    <th rowspan="2">CPU（Core i7-8700）</th>
    <th colspan="2">GPU（GeForce GTX 1070）</th>
  </tr>
  <tr>
    <th>決定論的</th>
    <th>非決定論的</th>
  </tr>
  <tr>
    <td>Python[iteration/s]</td>
    <td>0.15</td>
    <td>1.58</td>
    <td>1.74</td>
  </tr>
  <tr>
    <td>C++[iteration/s]</td>
    <td>0.13</td>
    <td>1.52</td>
    <td>1.68</td>
  </tr>
  <tr>
    <td>速度向上（Python→C++）</td>
    <td>×0.85</td>
    <td>×0.96</td>
    <td>×0.97</td>
  </tr>
</table>

推論：1秒当たりのデータ処理数

<table>
  <tr>
    <th rowspan="2"></th>
    <th rowspan="2">CPU（Core i7-8700）</th>
    <th colspan="2">GPU（GeForce GTX 1070）</th>
  </tr>
  <tr>
    <th>決定論的</th>
    <th>非決定論的</th>
  </tr>
  <tr>
    <td>Python[forward/s]</td>
    <td>16.82</td>
    <td>234.74</td>
    <td>221.24</td>
  </tr>
  <tr>
    <td>C++[forward/s]</td>
    <td>17.38</td>
    <td>367.65</td>
    <td>341.3</td>
  </tr>
  <tr>
    <td>速度向上（Python→C++）</td>
    <td>×1.03</td>
    <td>×1.57</td>
    <td>×1.54</td>
  </tr>
</table>

![p7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374669/2a4073bb-7a08-1842-e423-326786ec97ea.png)


# まとめ

## 「C++」と「Python」どっちが速いか？

**訓練**：全層「全結合層」の場合はC++の方が非常に速く，「畳み込み層」「転置畳み込み層」がある場合はPythonの方が少しだけ速い．
**推論**：全てのネットワークにおいて，C++の方がかなり速い．

- 全層「全結合層」の場合は，確実にC++の方が良い．
- それ以外を使う場合は，コーディング時間も考えて基本的にPythonで問題ない．
- できるなら，訓練だけ「Python」，推論だけ「C++」が良い．（ただ，この使い方ならTorchScriptを使った方が良さそう...）

## 新たに得られた知見

- 全層「全結合層」の推論の場合は，CPUだけ（GPUなし）の方が良い．（できればC++が良い）
- 「畳み込み層」「転置畳み込み層」がある場合は，GPUを使った方が圧倒的に良い．
- 実験結果の再現性を確保しなくていいなら，訓練は「非決定論的」，推論は「決定論的」でやるのが最も良い．（推論を「非決定論的」にしても速度の恩恵を受けないため）

# 参考URL

- https://pytorch.org/cppdocs/
- https://github.com/koba-jon/pytorch_cpp
- https://qiita.com/koba-jon/items/274e5e4970da72216f73