Mandelbrot ベンチマーク(C, PHP, HHVM, Ruby, Python, PyPy, and Kinx)

# Mandelbrot ベンチマーク

- 対象 ... C, PHP, HHVM, Ruby, Python, PyPy, and 我らが Kinx

> PyPy できるようになったので追加してみた。その結果はいかに？！

## はじめに

この記事 [【PHP8】PHPでJITが使えるようになる](https://qiita.com/rana_kualu/items/ba312d2789bd228f887a) でのベンチマークを拝見させていただきまして。

PHP8 に JIT が入るとかで Mandelbrot のベンチマークをしているのですが、は！、これは我らが Kinx 君も `native` メソッドでブイブイ言わせられるやつでは！、との気持ちから試しにベンチマークしてみよう、という趣旨のイベントです。

記事にしたということは、**勝負になった** ということでもあります。というか予想以上。やったね！

ちなみに、Kinx って何？という方は以下をご参照ください。

* 参考
    * 最初の動機 ... [スクリプト言語 KINX（ご紹介）](https://qiita.com/Kray-G/items/ca08b6fb40d15dd0ec76)
        * 個別記事へのリンクは全てここに集約してあります。
    * リポジトリ ... [https://github.com/Kray-G/kinx](https://github.com/Kray-G/kinx)
        * Pull Request 等お待ちしております。 

## 前提条件

### ベンチマークの内容

[ここ（https://gist.github.com/dstogov/12323ad13d3240aee8f1）](https://gist.github.com/dstogov/12323ad13d3240aee8f1)に各種ベンチマークプログラムが掲載されていますが、これをベースにします。

環境が異なるため同じ結果にはなりませんので、いくつかの言語で全部計測します。各種言語のバージョンはオリジナル記事に正確には合わせられませんでしたが、近いもので計測してオリジナルの記事と比較してみます。

ただし、[元記事](https://qiita.com/rana_kualu/items/ba312d2789bd228f887a) でも触れられているように、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 はこんな感じ。

```c:mandelbrot.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:mandelbrot.php
<?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 のソースコード。

```ruby:mandelbrot.rb
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 も一緒です。

```python:mandelbrot.py
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 通常版のソースコード。

```coffeescript:mandelbrot.kx
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` と付けるだけでいけたよ。

```coffeescript:mandelbrotn.kx
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) もコンパイル時間分のペナルティは見えている。

さて、[元記事](https://qiita.com/rana_kualu/items/ba312d2789bd228f887a) の結果と比較してみよう。今回のベンチマークでは出力も抑止しているのだが、環境の差が大きい。比率的な傾向は似ていると思っ...たのだが、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](https://qiita.com/Kray-G/items/e44c39ddb2e17659a0a4) を使ってみました。上記の数値は `average` の所に出てきた数値です。

```coffeescript:mandbench.kx
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()));
```