定数を返す函数はstaticでキャッシュすべきか?

結論
----

* PHPは可能な限り新しいバージョンを利用せよ
* [静的変数](http://php.net/manual/ja/language.variables.scope.php#language.variables.scope.static)か[静的プロパティ](http://php.net/manual/ja/language.oop5.static.php)でキャッシュするのが良い
* メソッドと函数(クラスに属すか否か)では大差はないので、設計上望ましい方を利用すべきか

小さな配列でたかだか数回しか呼ばれないようなパターンでは気にするまでのことではない。

## はじめに

「定数を返す函数」といふものが欲しいことがある。

`const`や外に露出した変数ではなく函数(メソッド)呼び出しにするのは、実際にメソッド呼び出しされるまで遅延できるからだ。

## 疑問

さて、あるとき疑問が湧いた。`static`で保存しておかなくても別に良いのではないか…?

```php
<?php

class Hoge
{
    private static $array = null;

    /**
     * よくやるやつ
     */
    function func_static()
    {
        if (!self::$array) {
            self::$array = [1, 2, 3];
        }

        return $array;
    }

    /**
     * 別にこれでいいんじゃね…?
     */
    function func_direct()
    {
        return [1, 2, 3];
    }
}

```

まあ、この例なら正直どっちでも良いだろう。しかし、要素数や要素の内容が巨大な配列を一度の実行で何度も参照するような場合ならどうだろうか。

といふわけで気になったので「推測するな、計測せよ」の原則に従って、仕事をさぼってベンチマークのコードを書いてみた。

## ベンチマーク

ジェネレータは https://gist.github.com/zonuexe/e8df881a692f45caf488 に置いた。

比較対象は「函数の`static`でキャッシュしたもの」「函数の`return`に直接リテラルを書いたもの」「クラスの静的メンバー変数にキャッシュしたもの」「メソッドの`return`に直接リテラルを書いたもの」「メソッドの`static`でキャッシュしたもの」だ。

表記は**static** が静的変数または静的プロパティにキャッシュしたもの、 **direct** がキャッシュをせずに `return` にリテラルを直接書いたものだ。

前半の5行はベンチマーカー内で指定回数*100回の函数(メソッド)呼び出しを繰り返す。後半の5行は[system](http://php.net/manual/ja/function.system.php)で`php -r`を起動し、ワンライナー内で指定回数*3回函数(メソッド)呼び出しをする。

前半と後半の方式は測定方法が異なるので、直接比較することはできない。この試行を5回実行し、最終的にその平均値を出力する。

### 10x10

```bash
# 10Byteの文字列10要素の配列を取り出すベンチマーカーを生成
$ ./gen 10 10
$ php ./src/10x10.php 10
Benchmark (10 times run)
10 Bytes x 10 elements
PHP 5.5.19

average : 
        static_func:            0.000269 sec.
        direct_func:            0.001261 sec.
        static_class:           0.000377 sec.
        static_method:          0.000281 sec.
        direct_method:          0.001373 sec.
        static_other_func:      0.182772 sec.
        direct_other_func:      0.182906 sec.
        static_other_class:     0.182361 sec.
        static_other_method:    0.182405 sec.
        direct_other_method:    0.182568 sec.
```

`static`は安定して`direct`よりも早いことは注目すべきだろう。

しかしながら、プロセスを100回起動して3回だけ値を取り出すケースにおいてはそれ以外のぶれの方が大きく、誤差として`direct`の方が勝ってしまったケースがある。

よって、この程度の小さい配列を取り出したい場合においては、書きやすい方で書くのが良いだろう。

### 1000x10000

```bash
# 1KByteの文字列10000要素の配列を取り出すベンチマーカーを生成
$ ./gen 1000 10000
$ php ./src/1000x10000.php
Benchmark (1 times run)
1000 Bytes x 10000 elements
PHP 5.5.19

average : 
        static_func:            0.000390 sec.
        direct_func:            0.470571 sec.
        static_class:           0.000657 sec.
        static_method:          0.000380 sec.
        direct_method:          0.480114 sec.
        static_other_func:      0.508610 sec.
        direct_other_func:      0.519826 sec.
        static_other_class:     0.511884 sec.
        static_other_method:    0.509537 sec.
        direct_other_method:    0.522021 sec.
```

この例において、100回呼び出す際には`direct`は圧倒的に遅いので *絶対利用すべきではない* 。また**静的プロパティ**よりも**静的変数**の方が早いので、同じクラスの別メソッドと共有する必要がない場合は静的変数にしておくと、気持ち早くなることは期待できる。

大差はないが、全体的に`static`の方が若干早い。数回しか呼び出されないとしても**静的変数**にしておくのが無難か。

最後に
------

今回の実験でPHP 5.5.10と5.5.19を利用したが、筆者の環境では**5.5.19の方が有意に早かった**ので、基本的にはマイナーバージョンの差であっても最新の環境を利用するのが望ましい。