みなさん楽しいPHPライフを送っていますか?
\たーのしー!/\PHPしゅきぃ/\I love PHP/
たくさんの声が聞こえてきました。
お姉さんとってもうれしいです!
でも貴方が大好きなPHPですが、実は本人が意図していない挙動をすることがあるかもしれませんよ?
あるいはなんでこんな処理なのにメモリバカ食いしてるの?
なんてことに心あたりがあったりしませんか?
もしかしたらこの記事を読んだ後大好きだったPHPが嫌いになりお別れをしたくなるかもしれません。
あるいは逆に大好きになるかもしれません。
それはアナタ次第です。
という書き出しでスタートしますが内容はありません。まあ言ってみれば釣りです。
もう見出しの内容がすべてなので読まなくていいです。
でははじめましょう。
なお本記事を書くにあたり使用するPHPは以下のとおりです。
PHP 7.1.3 (cli) (built: Mar 14 2017 17:39:59) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Tech
はじめに
他の言語の常識はPHPでは非常識となるケースが存在します。
逆に言えばPHPがはじめて触る言語であればハマる要素ではないですが、
他言語習得者がPHPを触ると大きな痛手を追うというケースが実際存在します。
またはじめに声を大きくしていっておきますが、
PHPはスクリプト言語です。
スコープのこと
PHP のスコープは割と飾りです。偉い人にそれはわからんのです。
<?php
function test()
{
{
$value = "hello world\n";
echo $value;
}
echo $value;
}
test();
実行すると
hello world
hello world
となります。
演算まわりのこと
数字は数字なのです。文字列型とか数値型とかではなく数字なのです。
<?php
function test()
{
echo "3" + 9 . "\n";
echo "3" + "9" . "\n";
echo "3" . 9 . "\n";
echo 3 . 9 . "\n";
}
test();
実行すると
12
12
39
39
となります。
null のこと
nullは個人的にあまり使うべきではないと思います。
nullをつかってnullかどうかで判定するよりも、
不要になったオブジェクトはunsetしてオブジェクトが有効かどうかはissetなどの関数で判定すべきです。
<?php
function test_isset()
{
$value = "aaaa";
echo (isset($value)?1:0) ."\n";
$value = null;
echo (isset($value)?1:0) ."\n";
$value = "aaaa";
unset($value);
echo (isset($value)?1:0) ."\n";
}
function test_isnull()
{
$value = "aaaa";
echo (!is_null($value)?1:0) ."\n";
$value = null;
echo (!is_null($value)?1:0) ."\n";
$value = "aaaa";
unset($value);
echo (!is_null($value)?1:0) ."\n";
}
function test_isset2()
{
echo (isset($value)?1:0) ."\n";
$value = null;
echo (isset($value)?1:0) ."\n";
}
function test_isnull2()
{
echo (!is_null($value)?1:0) ."\n";
$value = null;
echo (!is_null($value)?1:0) ."\n";
}
実行結果は以下のとおりです
test_isset ----------------------
1
0
0
test_isnull ----------------------
1
0
PHP Notice: Undefined variable: value in test.php on line 22
0
test_isset2 ----------------------
0
0
test_isnull2----------------------
PHP Notice: Undefined variable: value in test.php on line 35
0
0
参照は悪である
<?php
function test()
{
$values = array(1,2,3,4,5);
foreach ( $values as &$value )
{
echo $value.",";
}
echo "\n";
$value = 999;
foreach ( $values as &$value )
{
echo $value.",";
}
echo "\n";
}
test();
実行すると
1,2,3,4,5,
1,2,3,4,999,
おわかりいただけただろうか……
copy on write を意識した書き方
PHPはcopy on writeなので普通に扱う分には参照で扱い(ノンコピー)
変更が生じた段階でコピーが行われます。
つまり本来、上で述べたような参照はすべて排除可能です。
他の言語とは違いPHPにおいては参照で値を渡すことよりもcopy on writeを意識した作りにすることで
実行速度やメモリ使用量に有利なコードがかけるといっても良いでしょう。
例えば少々めんどくさいですが通常の値の配列でもクラスにして逃して、渡した先でメンバー変数を書き換える。
というような書き方をするだけで参照と似たような挙動が実現できます。
安易に参照を使って回避するのはよくないやりかたであると個人的には思います。
とりあえずcopy on writeを意識するとはどういうことかをサンプルで提示します。
<?php
function test1()
{
$sc = new stdClass();
$sc->ary = array_fill(0, 999999, "aaaa");
echo number_format(memory_get_usage()) . "byte \n";
}
function test2()
{
$array_a = array_fill(0, 999999, "aaaa");
$array_b = $array_a;
$array_b[0] = 1;
echo "array_a[0]:{$array_a[0]} array_b[0]:{$array_b[0]}\n";
echo number_format(memory_get_usage()) . "byte \n";
}
function test3()
{
$sc = new stdClass();
$sc->ary = array_fill(0, 999999, "aaaa");
$sc_ref = $sc;
$sc_ref->ary[0] = 1;
echo "sc->ary[0]:{$sc->ary[0]} sc_ref->ary[0]:{$sc_ref->ary[0]}\n";
echo number_format(memory_get_usage()) . "byte \n";
}
echo "---\n";
test1();
echo "---\n";
test2();
echo "---\n";
test3();
実行結果は以下のとおりです
---
33,912,272byte
---
array_a[0]:aaaa array_b[0]:1
67,470,464byte
---
sc->ary[0]:1 sc_ref->ary[0]:1
33,912,272byte
test2ェ
無駄なコピーに心あたりがある方は見直してみましょう。
Apache等でPHPを動かす際の注意点
Apache等でPHPを実行するとApache自体にPHPが確保したメモリが乗りますが、
この乗ったメモリ量はPHPのスクリプトが開放されても残りっぱなしになり
Apacheの子プロセスが死ぬまでそのメモリは確保されっぱなしになります。
これは詳細を省くが最悪サーバがメモリ不足に陥って死ぬ。
PHPはスクリプト言語である
何度もいうがPHPはスクリプト言語である。いいね?