All Articles

Spycをもっと頑張ってチューニングしてみた

前回の記事でそれなりにチューニングしたんですが、もうちょっと行けるだろオイ、ってことでもう少しだけ頑張ってみた誰得なメモその2です。

引き続き注意事項

今回も、アンチパターンやバッドノウハウの詰め合わせとなっております。よって、「Yamlをパースする」という単一の目的に対してならまだしも、実際のアプリケーションロジックでやってはいけません。っていうか既にこのコード自体がカオスなことになっているので、誰も触る気は起きないと思いますが…

基本的な手法は前回と同じですが、もっと効率化できる所やあとはいかにソースコードを短くするかを頑張ってみました。おさらいとして、初期状態のSpycのプロファイラはこんな感じ:

前回のチューニング後:

ここから頑張ります。

クラス定数の廃止

前回の記事でも追記してありますが、クラス定数をメソッド内で使いまわすよりも、メソッド内で文字列として生成して使うほうが、メソッド終了後にGCが働くようで、メモリ効率が上がりました。よって、

// Placefolders constant
const PLACEHOLDER                          = '__SPICYYAML__';
const ZEROKEY                                     = '__SPICYZERO__';
const SEQUENCE_PLACEHOLDER = '__SPICYSEQUENCE__';
const MAP_PLACEHOLDER               = '__SPICYMAP__';
const STRING_PLACEHOLDER        = '__SPICYSTRING__';
    
// Double/Single quotes
const DOUBLE_QUOTE       = '"';
const SINGLE_QUOTE         = '\'';

private static $trulyChars    = array('true', 'on', '+', 'yes', 'y');
private static $falsyChars    = array('false', 'off', '-', 'no', 'n');
private static $escapedQuotes = array ('\\"' => '"', '\'\'' => '\'', '\\\'' => '\'');

上記のような宣言は全て廃止し、すべてメソッド内でインラインで生成するようにしました。

もっとfunction calを減らす

特にクラスメソッドの呼び出しを減らすため、簡単なものはインラインへ展開、さらにメソッドコール前に条件判定を行なって、条件を満たしていない場合はそもそもメソッドをコールしないように条件文を追加しています。

さらに、strlen()、trim()などの関数が2回以上呼ばれる所は一度変数に入れて、それを使いまわすようにもしています。

$line    = substr($line, $idt);
// line string is commented or empty section?
if ( trim($line) === ''
     || $line[0] === '#'
     || trim($line, " \r\n\t") === '---' )
{
	continue;
}
self::$path        = $tmpPath;
$lastChar          = substr(trim($line), -1);

これはtrim($line)が二回呼ばれるので、

$line    = substr($line, $idt);
$tmpLine = trim($line);
// line string is commented or empty section?
if ( $tmpLine === ''
     || $line[0] === '#'
     || trim($line, " \r\n\t") === '---' )
{
	continue;
}
self::$path        = $tmpPath;
$lastChar          = substr($tmpLine, -1);

という感じで$tmpLine変数を作って関数コールを地道に減らします。これで1回あたり約1000byte効率化。

如何に短いコードで済ませるか

もうこの辺りからカオスの様相を呈していますが、if分の分岐を減らしたり、条件文の中で変数を受け取って比較したりといった事をそこかしこでやります:

$key = trim(substr($line, 1, -1));
if ( $key )
{
	if ( $key[0] === '\'' )
	{
		$key = trim($key, '\'');
	}
	if ( $key[0] === '"' )
	{
		$key =  trim($key, '"');
	}
}

これもif文の中で$keyを生成してしまいます。

if ( ($key = trim(substr($line, 1, -1))) )
{
	if ( $key[0] === '\'' )
	{
		$key = trim($key, '\'');
	}
	if ( $key[0] === '"' )
	{
		$key =  trim($key, '"');
	}
}

いやー見難くなりますねw。これで約300byte節約。

あと意外に知らなかったのは、foreachで展開する配列は毎回評価されるのかなと思っていたのですが、どこかのメモリ空間に一時的にキャッシュされるようで、メソッドの返り値をそのまま展開してもOKでした。

$array = self::_inlineEscape($inner);
foreach ( $array as $v )
{
	$ret[] = self::_toType($v);
}

展開する配列に対して操作をしない場合ですが、これは以下のように書いてもいいみたいです。

foreach ( self::_inlineEscape($inner) as $v )
{
	$ret[] = self::_toType($v);
}

明らかに上の方が見やすいですが、$array変数を使わない分、これも約300byte節約。

結果

というようなことを細々を続けて、最終的には以下のような結果に。

何とか400KBを切りました\(^o^)/

まぁ、それと引換にコードは汚くなる一方なので、良いのか悪いのか微妙な感じでしたが、とりあえず勉強にはなりました!

一応、githubに上げてあります。カオス具合をご確認したい方はぜひ…

Spicy