gracetory’s blog

東池袋にある合同会社グレストリのエンジニアブログです

PHPで設定ファイルをどうやって書くか考えた話

f:id:grnishi:20170816152911p:plain

はじめに

グレストリでサーバサイドの開発をしていますgrnishiです。

突然ですが皆さん、設定ファイルってどうやって書いていますか?データベースの接続情報とかアプリケーションのログレベルの設定とか、色々項目はあると思うのですが、それをどうやって管理するべきか?というのは中々デファクトスタンダードなやり方がわかっておりません。

言語を問わず色々調べてみると

www.slideshare.net

qiita.com

JSON, YAML, ini, TOML ざっくり比較 · GitHub

いまいちこれといったものが無いように見えます。

そこで今回はパフォーマンスの観点から設定ファイルについて考えていきたいと思います。

環境

  • さくらのVPS 1G (HDD 100GBプラン)
  • PHP 7.0.21

対象フォーマット

  • serialize
    • unserializeを使います
  • ini
    • parse_ini_string を使います
  • xml
    • simplexml_load_stringを使います
  • json
    • json_decodeを使います
  • yaml
    • peclのyamlを使います
  • toml
    • Yosymfony/Tomlを使います

hoconとか試したかったんだけど自分で実装するのが面倒なので使えそうなライブラリが無かったので見送りました。

明らかにtomlは分が悪いのですがご愛嬌ということで。

計測項目

  • ファイルサイズ
  • PHPの配列形式に変換100、1000、10000、100000回の速度
    • データ量を3件と220件で
    • ファイル読み込み速度は含まれません。
  • 使用メモリ量(memory_get_peak_usageで)

テストデータ

本来はデータベースの接続情報やログの出力設定などなのでそんなに大きなファイルにはならないと思いますが、ベンチマークを測るのであればある程度データが無いと差が出ないと思いますので、下記サイトにあるデータを借りました。

キン肉マンのキャラクター(超人)220人を紹介|キン肉マンDB

以下各フォーマットのデータサンプル。ここに書くためにデータ減らしていますが、実際は220人分の超人データが入っています。

一部アレな感じのデータになっていますが、各フォーマットの特性を吸収するためです。

serialize_data.txt

a:3:{s:5:"data1";a:6:{s:4:"name";s:15:"キン肉マン";s:4:"from";s:12:"キン肉星";s:5:"power";s:14:"95万パワー";s:6:"height";s:5:"185cm";s:6:"weight";s:4:"90kg";s:8:"finisher";a:3:{i:0;s:21:"キン肉バスター";i:1;s:24:"キン肉ドライバー";i:2;s:27:"マッスル・スパーク";}}s:5:"data2";a:6:{s:4:"name";s:15:"テリーマン";s:4:"from";s:12:"アメリカ";s:5:"power";s:14:"95万パワー";s:6:"height";s:5:"190cm";s:6:"weight";s:4:"95kg";s:8:"finisher";a:3:{i:0;s:36:"スピニング・トーホールド";i:1;s:45:"テキサス・クローバー・ホールド";i:2;s:33:"カーフ・ブランディング";}}s:5:"data3";a:6:{s:4:"name";s:18:"ラーメンマン";s:4:"from";s:6:"中国";s:5:"power";s:14:"97万パワー";s:6:"height";s:5:"208cm";s:6:"weight";s:5:"130kg";s:8:"finisher";a:3:{i:0;s:27:"レッグ・ラリアート";i:1;s:27:"キャメル・クラッチ";i:2;s:51:"九龍城落地(ガウロンセンドロップ)";}}}
data.ini

[data1]
name = キン肉マン
from = キン肉星
power = 95万パワー
height = 185cm
weight = 90kg
finisher[] = キン肉バスター
finisher[] = キン肉ドライバー
finisher[] = マッスル・スパーク

[data2]
name = テリーマン
from = アメリカ
power = 95万パワー
height = 190cm
weight = 95kg
finisher[] = スピニング・トーホールド
finisher[] = テキサス・クローバー・ホールド
finisher[] = カーフ・ブランディング

[data3]
name = ラーメンマン
from = 中国
power = 97万パワー
height = 208cm
weight = 130kg
finisher[] = レッグ・ラリアート
finisher[] = キャメル・クラッチ
finisher[] = 九龍城落地(ガウロンセンドロップ)
data.xml

<root>
    <data1>
        <name>キン肉マン</name>
        <from>キン肉星</from>
        <power>95万パワー</power>
        <height>185cm</height>
        <weight>90kg</weight>
            <finisher>
            <finisher1>キン肉バスター</finisher1>
            <finisher2>キン肉ドライバー</finisher2>
            <finisher3>マッスル・スパーク</finisher3>
        </finisher>
    </data1>
    <data2>
        <name>テリーマン</name>
        <from>アメリカ</from>
        <power>95万パワー</power>
        <height>190cm</height>
        <weight>95kg</weight>
        <finisher>
            <finisher1>スピニング・トーホールド</finisher1>
            <finisher2>テキサス・クローバー・ホールド</finisher2>
            <finisher3>カーフ・ブランディング</finisher3>
        </finisher>
    </data2>
    <data3>
        <name>ラーメンマン</name>
        <from>中国</from>
        <power>97万パワー</power>
        <height>208cm</height>
        <weight>130kg</weight>
        <finisher>
            <finisher1>レッグ・ラリアート</finisher1>
            <finisher2>キャメル・クラッチ</finisher2>
            <finisher3>九龍城落地(ガウロンセンドロップ)</finisher3>
        </finisher>
    </data3>
</root>
data.json

{
    "data1": {
        "finisher": {
            "finisher1": "キン肉バスター", 
            "finisher2": "キン肉ドライバー", 
            "finisher3": "マッスル・スパーク"
        }, 
        "from": "キン肉星", 
        "height": "185cm", 
        "name": "キン肉マン", 
        "power": "95万パワー", 
        "weight": "90kg"
    }, 
    "data2": {
        "finisher": {
            "finisher1": "スピニング・トーホールド", 
            "finisher2": "テキサス・クローバー・ホールド", 
            "finisher3": "カーフ・ブランディング"
        }, 
        "from": "アメリカ", 
        "height": "190cm", 
        "name": "テリーマン", 
        "power": "95万パワー", 
        "weight": "95kg"
    }, 
    "data3": {
        "finisher": {
            "finisher1": "レッグ・ラリアート", 
            "finisher2": "キャメル・クラッチ", 
            "finisher3": "九龍城落地(ガウロンセンドロップ)"
        }, 
        "from": "中国", 
        "height": "208cm", 
        "name": "ラーメンマン", 
        "power": "97万パワー", 
        "weight": "130kg"
    }
}
data.yaml

---
data1:
  name: キン肉マン
  from: キン肉星
  power: 95万パワー
  height: 185cm
  weight: 90kg
  finisher:
    finisher1: キン肉バスター
    finisher2: キン肉ドライバー
    finisher3: マッスル・スパーク
data2:
  name: テリーマン
  from: アメリカ
  power: 95万パワー
  height: 190cm
  weight: 95kg
  finisher:
    finisher1: スピニング・トーホールド
    finisher2: テキサス・クローバー・ホールド
    finisher3: カーフ・ブランディング
data3:
  name: ラーメンマン
  from: 中国
  power: 97万パワー
  height: 208cm
  weight: 130kg
  finisher:
    finisher1: レッグ・ラリアート
    finisher2: キャメル・クラッチ
    finisher3: 九龍城落地(ガウロンセンドロップ)
...
data.toml

#Toml file

[data1]
name = "キン肉マン"
from = "キン肉星"
power = "95万パワー"
height = "185cm"
weight = "90kg"
finisher = ["キン肉バスター", "キン肉ドライバー", "マッスル・スパーク"]

[data2]
name = "テリーマン"
from = "アメリカ"
power = "95万パワー"
height = "190cm"
weight = "95kg"
finisher = ["スピニング・トーホールド", "テキサス・クローバー・ホールド", "カーフ・ブランディング"]

[data3]
name = "ラーメンマン"
from = "中国"
power = "97万パワー"
height = "208cm"
weight = "130kg"
finisher = ["レッグ・ラリアート", "キャメル・クラッチ", "九龍城落地(ガウロンセンドロップ)"]

計測結果

ファイルサイズ

単位:byte

serialize ini xml json yaml toml
54,746 37,502 53,708 43,152 41,586 38,663

どれも似たようなものですが、iniやtomlがやや優位でしょうか。

実行速度(データ量3件)

単位:sec

format 100回 1000回 10000回 100000回
serialize 0.00065 0.00480 0.0482 0.5020
ini 0.00171 0.01500 0.1487 1.4844
xml 0.00295 0.02722 0.2731 2.7566
json 0.00103 0.01155 0.1063 1.0793
yaml 0.00711 0.07225 0.7400 7.3343
toml 0.16975 1.70521 16.9795 172.9021

実行速度(データ量220件)

単位:sec

format 100回 1000回 10000回 100000回
serialize 0.03427 0.34198 3.5682 35.4121
ini 0.08679 0.89662 9.1200 90.6430
xml 0.14188 1.37301 13.8983 140.0371
json 0.07225 0.71918 6.8512 68.5011
yaml 0.45630 4.47941 43.9969 -
toml 10.02795 99.03344 - -

※時間がかかるので途中で止めました。

メモリ使用量

単位:MB

serialize ini xml json yaml toml
0.7278 0.6883 0.4748 0.6672 0.6677 0.7500

どれも大差ありません。

考察

serializeはまぁ反則ですよね。速いには速いですが、別言語で使いまわせませんしね。あくまで参考値として。 ただ、どうしてもパフォーマンスが求められる場面ではほんの僅かに高速化の余地として知っておいた方が良いかもです。

yamlの結果がこんなに悪いのは予想していませんでした。jsonと比較すると約7倍時間がかかるみたいです。

予想通りtomlは爆死してます。仕方が無い。PECLなりがあれば良かったんですが。

というわけで、設定ファイルの見やすさとパフォーマンスの観点からjsonでいいんじゃないですかね?

ちなみにPHP特化で良ければ配列に書いて別ファイルで持っておけばrequireするだけだから一番速いです。