盛れる!ベンチマーク!


はじめに

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

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 より replacesplit の組み合わせの方が速かったのでそちらを採用しました


    • 単純な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


バージョン

dmd --version

DMD32 D Compiler v2.087.0

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



実行コマンド

dmd splits_d.d -O -release -inline -boundscheck=off

app.exe


LDC


バージョン

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


実行コマンド

ldc2 splits_d.d -O3 -release -boundscheck=off

app.exe


Node

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


バージョン

node --version

v10.16.0


実行コマンド

node splits.js



Python(pypy3)

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


バージョン

pypy3 --version

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



実行コマンド

pypy3 splits.py



ベンチマークを盛る

盛るっていうよりなんだこれ?という結果ですが、何をしたかといえばD言語の特徴である CTFE(コンパイル時関数評価)です。

D言語の関数には通称 CTFEable と呼ばれる分類があり、一定の条件を満たすとコンパイル時に計算を実行でき、結果を定数として保持できます。

C++でも constexpr がありますし、ぼちぼちメジャーな機能ではあります。

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

(というかdmdはなんでゼロにならないんでしょうかね…?)


やったこと

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


変更前

auto words = "asdff\nastgrw3h\r\nwtegole\rkserlhge3t\nearsgh\nergh\rsagr\r\nerghe\r";

// 中略
wordList = words.splitLines();


変更後

enum words = "asdff\nastgrw3h\r\nwtegole\rkserlhge3t\nearsgh\nergh\rsagr\r\nerghe\r";

// 中略
enum temp = words.splitLines();
wordList = temp;


まとめ


  • 盛らなくてもD言語が1番速かった(よかった…)


    • 改行で分割するのは std.stringsplitLines で安定。



    • ベンチマークを書くときは変に気を回す必要なし


      • enumやグローバル変数の初期化式を書かない限りCTFEされない(条件は公式ドキュメントに書いてありそう)

      • コンパイル時に評価することを期待する場合、enumで1回変数に落とすのが確実





  • pypy3はやはり速い。JITは正義。

  • NodeのRegexは正規表現の中では異様に速い…裏で何かやってそう…

  • pypy3もNodeも処理単体で見れば普通にdmdより速い(まじか)


あとがき

ぶっちゃけD言語のCTFEは「任意の式がCTFEできるかどうかチェックして、できるならやる」という仕組みだと思っていました。

なので最初にソース見たときの感想は「なんでこれで実行時間測れてるの?」です。

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

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

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

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