盛れる！ベンチマーク！

# はじめに

TwitterでD言語の日本語情報増やしたいなーとか言っていたところ、タイミングよく文字列操作で良い記事があったのでちょっと読んでいました。

- **文字列を改行で確実に分割する**
    - https://qiita.com/yosgspec/items/8a3bda5341396ece2005

Pythonも速いんだなーとか思いつつ、この言語の並びならD言語が1番じゃない…？と思ったので、ベンチマーク部分について軽く追試しつつ盛れるだけ盛ってやろうと思った次第です。（盛ってはいけない）

ちなみに比較対象は、元から結果が上位だった Python(pypy3) と JavaScript(Node) に絞りますのでご了承ください
（たまたま手元で計測が容易だったものともいう）

# 前置き
計測にあたり元のソースコードは何も変更していませんのでベースはそちら参照してください。
https://github.com/yosgspec/splits

pypy3やnodeの引数は詳しくないので最適化っぽいものは何もやっていません。

というか言語のタイプが違いすぎるのであまり意味のある比較ではないという認識はあります。
それでもそれなりに肉薄してるので、これはそういうコンテンツなのだと思っていただければ幸いです。

何度でも書くけどネタ記事だよ！

# 計測
諸々書いてしまうと盛れないので先に結果発表です。

## 結果
- 傾向だけ見れればいいかということで、元のコードをローカル環境でそれぞれ10回計測しました
- 元々各言語で3種類の結果がありましたが、そのなかで一番速かったパターンを採用しています
- D言語はdmdとldc2という2つのコンパイラで実行しています

結果を表にまとめると以下の通りでした。
単位はミリ秒(msecs)で、????というのが盛ったやつです。詳細は後述します。

## 結果データ

| #  | dmd | ldc2 | pypy3 | Node | dmd + ???? | ldc2 + ???? |
|:--:|--:|--:|--:|--:|--:|--:|--:|
|  1 | 335 | 174 | 198 | 272 | 78 | 0 |
|  2 | 297 | 166 | 195 | 280 | 87 | 0 |
|  3 | 301 | 184 | 194 | 274 | 81 | 0 |
|  4 | 298 | 198 | 199 | 273 | 88 | 0 |
|  5 | 297 | 173 | 192 | 279 | 79 | 0 |
|  6 | 298 | 174 | 198 | 275 | 79 | 0 |
|  7 | 302 | 176 | 201 | 275 | 80 | 0 |
|  8 | 297 | 171 | 195 | 286 | 78 | 0 |
|  9 | 315 | 179 | 193 | 282 | 84 | 0 |
| 10 | 295 | 178 | 198 | 287 | 88 | 0 |

## 集計値

| | dmd | ldc2 | pypy3 | Node | dmd + ???? | ldc2 + ???? |
|:--:|--:|--:|--:|--:|--:|--:|
| 平均 | 303.5 | 177.3 | 196.3 | 278.3 | 82.2 | 0 |
| 中央値 | 298 | 175 | 196.5 | 277 | 80.5 | 0 |
| 最大値 | 335 | 198 | 201 | 287 | 78 | 0 |
| 最小値 | 295 | 166 | 192 | 272 | 88 | 0 |
| 標準偏差 | 12.44 | 6.60 | 2.91 | 5.38 | 4.16 | 0 |

盛らなくてもldc2が速かった！
そして盛ったdmdは速かったpypy3より倍以上速い！ldc2に至っては無限倍速い！すごい！！！
（素のdmd…お前…負けたんか…）

## 補足
- pypy3では `splitlines` より `replace` と `split` の組み合わせの方が速かったのでそちらを採用しました
    - 単純な`splitelines` のほうは平均で約370msecsでした。元記事より遅いのでなんか変ですね？そういうもの？


## 計測方法
### 環境
使ったのは Surface Book2 の Windows10 環境です。

CPUは、 第 8 世代 インテル® Core™ i7-8650U クアッド コア プロセッサ、4.2GHz Max Turbo
スペック：https://www.microsoft.com/ja-jp/p/surface-book-2/8mcpzjjcc98c?activetab=pivot:techspecstab

### D言語
主要なコンパイラが3つありますが、今回は公式コンパイラのdmdとLLVMベースのldc2の2つを使います。
語弊がありますが、dmdはビルドが速い公式コンパイラ、ldc2はLLVMベースの実行が速いコンパイラです。

ちなみにどちらも2019年7月6日時点でそれぞれ最新版です。

#### dmd
```bash:バージョン
dmd --version
DMD32 D Compiler v2.087.0

Copyright (C) 1999-2019 by The D Language Foundation, All Rights Reserved written by Walter Bright
```

```bash:実行コマンド
dmd splits_d.d -O -release -inline -boundscheck=off
app.exe
```

#### LDC
```bash:バージョン
ldc2 --version
LDC - the LLVM D compiler (1.16.0):
  based on DMD v2.086.1 and LLVM 8.0.0
  built with LDC - the LLVM D compiler (1.16.0)
  Default target: x86_64-pc-windows-msvc
  Host CPU: skylake
  http://dlang.org - http://wiki.dlang.org/LDC
```

```bash:実行コマンド
ldc2 splits_d.d -O3 -release -boundscheck=off
app.exe
```

### Node

2019/07/06時点のLTS版 v10.16.0 です。最新版は試していません。

```bash:バージョン
node --version
v10.16.0
```

```bash:実行コマンド
node splits.js
```

### Python（pypy3）
Windows向けのpypy3を拾ってきて使いました。

```bash:バージョン
pypy3 --version

Python 3.6.1 (784b254d6699, Apr 16 2019, 12:10:48)
[PyPy 7.1.1-beta0 with MSC v.1910 32 bit]
```

```bash:実行コマンド
pypy3 splits.py
```

# ベンチマークを盛る
盛るっていうよりなんだこれ？という結果ですが、何をしたかといえばD言語の特徴である `CTFE`（コンパイル時関数評価）です。

D言語の関数には通称 `CTFEable` と呼ばれる分類があり、一定の条件を満たすとコンパイル時に計算を実行でき、結果を定数として保持できます。
C++でも `constexpr` がありますし、ぼちぼちメジャーな機能ではあります。

`CTFEable` な任意の式は定数として保持できるので、この手のベンチマークならちょっと書き換えるだけで実行時間はほぼゼロになるだろう、という魂胆です。
（というかdmdはなんでゼロにならないんでしょうかね…？）

## やったこと

というわけで書き換えたのは2行です。変数宣言と計算のところをenumにするだけ。

```d:変更前
auto words = "asdff\nastgrw3h\r\nwtegole\rkserlhge3t\nearsgh\nergh\rsagr\r\nerghe\r";
// 中略
wordList = words.splitLines();
```

```d:変更後
enum words = "asdff\nastgrw3h\r\nwtegole\rkserlhge3t\nearsgh\nergh\rsagr\r\nerghe\r";
// 中略
enum temp = words.splitLines();
wordList = temp;
```

# まとめ

- 盛らなくてもD言語が1番速かった（よかった…）
    - 改行で分割するのは `std.string` の `splitLines` で安定。
        - ドキュメント： https://dlang.org/phobos/std_string.html#.KeepTerminator
    - ベンチマークを書くときは変に気を回す必要なし
        - enumやグローバル変数の初期化式を書かない限りCTFEされない（条件は公式ドキュメントに書いてありそう）
        - コンパイル時に評価することを期待する場合、`enum`で1回変数に落とすのが確実
- pypy3はやはり速い。JITは正義。
- NodeのRegexは正規表現の中では異様に速い…裏で何かやってそう…
- pypy3もNodeも処理単体で見れば普通にdmdより速い（まじか）

# あとがき

ぶっちゃけD言語のCTFEは「任意の式がCTFEできるかどうかチェックして、できるならやる」という仕組みだと思っていました。
なので最初にソース見たときの感想は「なんでこれで実行時間測れてるの？」です。

今回の結果から、ちゃんとCTFEするにはenumに1回入れる必要があることがわかりました。

「ベンチマークしたいときは普通に書いておけば大丈夫」ということですが、逆に言えば「何かの式で一部がCTFEされることはない」ので「定数っぽいところは1回enum挟んだほうが無難」ということになりそうです。（要追試）

ローカルのプログラムをいくつか見てみたところ、長い式を普通に書いてるところが結構見つかったので、要所にenum挟んでやればまだまだ速くできる余地がありそうです。

というわけでいろいろがんばっていきます！