Mandelbrot ベンチマーク
- 対象 ... C, PHP, HHVM, Ruby, Python, PyPy, and 我らが Kinx
PyPy できるようになったので追加してみた。その結果はいかに?!
はじめに
この記事 【PHP8】PHPでJITが使えるようになる でのベンチマークを拝見させていただきまして。
PHP8 に JIT が入るとかで Mandelbrot のベンチマークをしているのですが、は!、これは我らが Kinx 君も native
メソッドでブイブイ言わせられるやつでは!、との気持ちから試しにベンチマークしてみよう、という趣旨のイベントです。
記事にしたということは、勝負になった ということでもあります。というか予想以上。やったね!
ちなみに、Kinx って何?という方は以下をご参照ください。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
前提条件
ベンチマークの内容
ここ(https://gist.github.com/dstogov/12323ad13d3240aee8f1)に各種ベンチマークプログラムが掲載されていますが、これをベースにします。
環境が異なるため同じ結果にはなりませんので、いくつかの言語で全部計測します。各種言語のバージョンはオリジナル記事に正確には合わせられませんでしたが、近いもので計測してオリジナルの記事と比較してみます。
ただし、元記事 でも触れられているように、PHP だけ ズル してます。それを避けるとともに、実際問題として I/O 性能も計測の邪魔なので、出力部分はざっくり削除することにします。しかし(逆説が続く)、C に関しては出力を無くすとどうも最適化しすぎてベンチマークにならないので、 C は最適化無しでいきます。一種のハンディキャップ的な。それでも速い。
計測時間について
実行開始後に、各言語で用意されたタイマーを使って実行時間を計測しています。で、気づいたのですが、つまりソース解析してコンパイルする時間が含まれていません。C はしょうがないけど。
何が違和感だったかというと、HHVM は体感的に遅いのに、最終的に出力される実行時間が速い のです。具体的には、HHVM は体感的には PHP より遅いけど数値は PHP より良い、という結果です。
出力について
出力はさせた場合は全部以下の形が出力されることを確認済み。我が Kinx(native) 君も期待値通りの働きをしてくれましたよ。
*
*
*
*
*
***
*****
*****
***
*
*********
*************
***************
*********************
*********************
*******************
*******************
*******************
*******************
***********************
*******************
*******************
*********************
*******************
*******************
*****************
***************
*************
*********
*
***************
***********************
* ************************* *
*****************************
* ******************************* *
*********************************
***********************************
***************************************
*** ***************************************** ***
*************************************************
***********************************************
*********************************************
*********************************************
***********************************************
***********************************************
***************************************************
*************************************************
*************************************************
***************************************************
***************************************************
* *************************************************** *
***** *************************************************** *****
****** *************************************************** ******
******* *************************************************** *******
***********************************************************************
********* *************************************************** *********
****** *************************************************** ******
***** *************************************************** *****
***************************************************
***************************************************
***************************************************
***************************************************
*************************************************
*************************************************
***************************************************
***********************************************
***********************************************
*******************************************
*****************************************
*********************************************
**** ****************** ****************** ****
*** **************** **************** ***
* ************** ************** *
*********** ***********
** ***** ***** **
* * * *
ベンチマーク
さて、本題のベンチマークです。
まずはプログラムコードから。
C
gcc のバージョン。
$ gcc --version
gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
Copyright (C) 2017 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.
C はこんな感じ。
#include <stdio.h>
#include <sys/time.h>
#define BAILOUT 16
#define MAX_ITERATIONS 1000
int mandelbrot(double x, double y)
{
double cr = y - 0.5;
double ci = x;
double zi = 0.0;
double zr = 0.0;
int i = 0;
while(1) {
i ++;
double temp = zr * zi;
double zr2 = zr * zr;
double zi2 = zi * zi;
zr = zr2 - zi2 + cr;
zi = temp + temp + ci;
if (zi2 + zr2 > BAILOUT)
return i;
if (i > MAX_ITERATIONS)
return 0;
}
}
int main (int argc, const char * argv[]) {
struct timeval aTv;
gettimeofday(&aTv, NULL);
long init_time = aTv.tv_sec;
long init_usec = aTv.tv_usec;
int x,y;
for (y = -39; y < 39; y++) {
//printf("\n");
for (x = -39; x < 39; x++) {
volatile int i = mandelbrot(x/40.0, y/40.0);
//if (i==0)
// printf("*");
//else
// printf(" ");
}
}
//printf ("\n");
gettimeofday(&aTv,NULL);
double query_time = (aTv.tv_sec - init_time) + (double)(aTv.tv_usec - init_usec)/1000000.0;
printf ("C Elapsed %0.3f\n", query_time);
return 0;
}
PHP/HHVM
PHP のバージョン。
$ php --version
PHP 7.2.24-0ubuntu0.18.04.6 (cli) (built: May 26 2020 13:09:11) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.2.24-0ubuntu0.18.04.6, Copyright (c) 1999-2018, by Zend Technologies
HHVM のバージョン。
$ hhvm --version
HipHop VM 3.21.0 (rel)
Compiler: 3.21.0+dfsg-2ubuntu2
Repo schema: ebd0a4633a34187463466c1d3bd327c131251849
PHP と HHVM は同じソースコードです。
<?php
define("BAILOUT",16);
define("MAX_ITERATIONS",1000);
class Mandelbrot
{
function Mandelbrot()
{
$d1 = microtime(1);
for ($y = -39; $y < 39; $y++) {
for ($x = -39; $x < 39; $x++) {
$this->iterate($x/40.0,$y/40.0);
}
}
$d2 = microtime(1);
$diff = $d2 - $d1;
printf("PHP Elapsed %0.3f\n", $diff);
}
function iterate($x,$y)
{
$cr = $y-0.5;
$ci = $x;
$zr = 0.0;
$zi = 0.0;
$i = 0;
while (true) {
$i++;
$temp = $zr * $zi;
$zr2 = $zr * $zr;
$zi2 = $zi * $zi;
$zr = $zr2 - $zi2 + $cr;
$zi = $temp + $temp + $ci;
if ($zi2 + $zr2 > BAILOUT)
return $i;
if ($i > MAX_ITERATIONS)
return 0;
}
}
}
$m = new Mandelbrot();
?>
Ruby
Ruby のバージョン。
$ ruby --version
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux-gnu]
Ruby のソースコード。
BAILOUT = 16
MAX_ITERATIONS = 1000
class Mandelbrot
def initialize
#puts "Rendering"
for y in -39...39 do
#puts
for x in -39...39 do
i = iterate(x/40.0,y/40.0)
#if (i == 0)
# print "*"
#else
# print " "
#end
end
end
end
def iterate(x,y)
cr = y-0.5
ci = x
zi = 0.0
zr = 0.0
i = 0
while(1)
i += 1
temp = zr * zi
zr2 = zr * zr
zi2 = zi * zi
zr = zr2 - zi2 + cr
zi = temp + temp + ci
return i if (zi2 + zr2 > BAILOUT)
return 0 if (i > MAX_ITERATIONS)
end
end
end
time = Time.now
Mandelbrot.new
#puts
puts "Ruby Elapsed %f" % (Time.now - time)
Python/PyPy
Python のバージョン。
$ python --version
Python 2.7.15+
PyPy のバージョン。
$ pypy --version
Python 2.7.13 (5.10.0+dfsg-3build2, Feb 06 2018, 18:37:50)
[PyPy 5.10.0 with GCC 7.3.0]
Python のソースコード。PyPy も一緒です。
import sys, time
stdout = sys.stdout
BAILOUT = 16
MAX_ITERATIONS = 1000
class Iterator:
def __init__(self):
#print 'Rendering...'
for y in range(-39, 39):
#stdout.write('\n')
for x in range(-39, 39):
i = self.mandelbrot(x/40.0, y/40.0)
#if i == 0:
#stdout.write('*')
#else:
#stdout.write(' ')
def mandelbrot(self, x, y):
cr = y - 0.5
ci = x
zi = 0.0
zr = 0.0
i = 0
while True:
i += 1
temp = zr * zi
zr2 = zr * zr
zi2 = zi * zi
zr = zr2 - zi2 + cr
zi = temp + temp + ci
if zi2 + zr2 > BAILOUT:
return i
if i > MAX_ITERATIONS:
return 0
t = time.time()
Iterator()
print 'Python Elapsed %.02f' % (time.time() - t)
Kinx/Kinx(native)
Kinx のバージョン。
$ kinx -v
kinx version 0.9.2
ただし、native 内に const 定数が正しく伝搬されないバグが 0.9.2 リリースにあったので、一部修正したものを利用しました...。コミットは済です。
Kinx 通常版のソースコード。
const BAILOUT = 16;
const MAX_ITERATIONS = 1000;
function mandelbrot(x, y) {
var cr = y - 0.5;
var ci = x;
var zi = 0.0;
var zr = 0.0;
var i = 0;
while (true) {
i++;
var temp = zr * zi;
var zr2 = zr * zr;
var zi2 = zi * zi;
zr = zr2 - zi2 + cr;
zi = temp + temp + ci;
if (zi2 + zr2 > BAILOUT)
return i;
if (i > MAX_ITERATIONS)
return 0;
}
}
var tmr = new SystemTimer();
var x,y;
for (y = -39; y < 39; y++) {
#System.print("\n");
for (x = -39; x < 39; x++) {
var i = mandelbrot(x/40.0, y/40.0);
#if (i==0)
# System.print("*");
#else
# System.print(" ");
}
}
#System.print("\n");
System.print("Kinx Elapsed %0.3f\n" % tmr.elapsed());
Kinx(native) のソースコード。計算結果から型が分かる場合は書かなくても良いので、引数に :dbl
と付けるだけでいけたよ。
const BAILOUT = 16;
const MAX_ITERATIONS = 1000;
native mandelbrot(x:dbl, y:dbl) {
var cr = y - 0.5;
var ci = x;
var zi = 0.0;
var zr = 0.0;
var i = 0;
while (true) {
i++;
var temp = zr * zi;
var zr2 = zr * zr;
var zi2 = zi * zi;
zr = zr2 - zi2 + cr;
zi = temp + temp + ci;
if (zi2 + zr2 > BAILOUT)
return i;
if (i > MAX_ITERATIONS)
return 0;
}
}
var tmr = new SystemTimer();
var x,y;
for (y = -39; y < 39; y++) {
#System.print("\n");
for (x = -39; x < 39; x++) {
var i = mandelbrot(x/40.0, y/40.0);
#if (i==0)
# System.print("*");
#else
# System.print(" ");
}
}
#System.print("\n");
System.print("Kinx(native) Elapsed %0.3f\n" % tmr.elapsed());
結果
結果表示です。10回やった平均。速かった順に並べなおす。
すみません、計測プログラムに問題があり、再度計測しなおしました。
言語 | バージョン | 計測時刻(秒,10回平均) | time(real),10回平均 |
---|---|---|---|
C | 7.4.0 | 0.018 | 0.046 |
PyPy | 5.10.0 | 0.020 | 0.122 |
Kinx(native) | 0.9.2 | 0.048 | 0.107 |
HHVM | 3.21.0 | 0.068 | 0.552 |
PHP | 7.2.24 | 0.182 | 0.241 |
Ruby | 2.5.1 | 0.365 | 0.492 |
Kinx | 0.9.2 | 0.393 | 0.457 |
Python | 2.7.15 | 0.564 | 0.601 |
やった! 体感では PHP より遅い HHVM よりも速い結果が出たぜ。あと、通常版のほうでも スゲー速いと思っていた Ruby VM にも匹敵する速さ だったので単純に嬉しい。
というか、PyPy が異常に速ぇ。格の違いを感じたよ。素晴らしい。
HHVM の体感的な遅さも表に表れている。コンパイル時間が長いのでしょう。言語仕様上、仕方のないことです。Kinx(native) もコンパイル時間分のペナルティは見えている。
さて、元記事 の結果と比較してみよう。今回のベンチマークでは出力も抑止しているのだが、環境の差が大きい。比率的な傾向は似ていると思っ...たのだが、HHVM だけなんか変だな。デフォルトで JIT on のはずなので、JIT on と比較。逆に元記事の JIT off 版の結果は遅すぎて何が正しいのかよくわからん。他は総じて環境的に今のほうが 約2倍 速いという結果に。
言語 | バージョン | 計測時刻(秒,10回平均) | 元記事結果 | 元記事バージョン |
---|---|---|---|---|
C | 7.4.0 | 0.018 | 0.022 | 4.9.2 |
PyPy | 5.10.0 | 0.020 | ||
Kinx(native) | 0.9.2 | 0.048 | ||
HHVM | 3.21.0 | 0.068 | 0.030 | 3.5.0 |
PHP | 7.2.24 | 0.182 | 0.281 | 7 |
Ruby | 2.5.1 | 0.365 | 0.684 | 2.1.5 |
Kinx | 0.9.2 | 0.393 | ||
Python | 2.7.15 | 0.564 | 1.128 | 2.7.8 |
おわりに
ベンチマーク楽しいですね、勝負になっているときは。native
はなかなか手がついていませんが、こうしてみると Kinx の一つの特徴(個性)でもあるので、大切に育てていきたいですね。頑張ろう。
ではまた次回。
P.S.
ちなみに、計測に使ったスクリプトはこれです。最近実装した Process を使ってみました。上記の数値は average
の所に出てきた数値です。
using Process;
var count = 10;
var command = [$$[1], $$[2]];
var r = [];
var re = /[0-9]+\.[0-9]+/;
for (var i = 0; i < count; ++i) {
var result = "";
var [r1, w1] = new Pipe();
var p1 = new Process(command, { out: w1 }).run();
w1.close();
while (p1.isAlive() || r1.peek() > 0) {
var buf = r1.read();
if (buf.length() < 0) {
System.println("Error...");
return 1;
} else if (buf.length() > 0) {
result += buf;
} else {
// System.println("no input...");
}
}
re.reset(result);
if (re.find()) {
r.push(Double.parseDouble(re.group[0].string));
}
}
var total = r.reduce(&(r, e) => r + e);
System.println("total : %8.3f" % total);
System.println("count : %8d" % r.length());
System.println("average: %8.3f" % (total / r.length()));