はじめまして! アイフラッグのシステム開発や保守のほうの担当のKIです(あとボケも)
私も店長から無茶振りされて、少し涙をためながら書いています。
文字が少し滲んでいるかも知れませんがよろしくお願いします。
私と参照渡し
他の言語に慣れている人だと無用な変数のコピーを回避するために参照渡しを良く使うと思います。
しかしPHPだと参照渡しを使うことによって無用なコピーが行われることがあるのです!!!
コピーオンライトとは
まずPHPにはコピーオンライトと言う最適化戦略が実装されています。
Wikipediaによるとコピーオンライトというのは(
コピーオンライト - Wikipedia)
コンピュータ内部で、ある程度大きなデータを複製する必要が生じたとき、愚直な設計では、直ちに新たな空き領域を探して割り当て、コピーを実行する。 ところが、もし複製したデータに対する書き換えがなければその複製は無駄だったことになる。
そこで、複製を要求されても、コピーをした振りをして、とりあえず原本をそのまま参照させるが、ただし、そのままで本当に書き換えてはまずい。原本またはコピーのどちらかを書き換えようとしたときに、それを検出し、その時点ではじめて新たな空き領域を探して割り当て、コピーを実行する。これが「書き換え時にコピーする」、すなわちコピーオンライト (Copy-On-Write) の基本的な形態である。
と書いてあります。
つまり開発者は低レベル(マシンに近いこと)実装を気にすることなく、ロジックに集中してプログラミングして大丈夫ですよと言っているわけです。
そこで、なぜ参照渡しで無用なコピーが行われるのでしょう?
それには「zval」を理解しなければいけません。
zval登場
PHPの変数は内部でzvalと呼ばれて、以下の4つの値を持っています。
- type(変数の型)
- value(実際の値)
- is_ref(参照かどうかを示すフラグ)
- refcount(共有しているラベルの数)
変数名はzvalに付けられたラベルのようなもので、zvalを探すために使用されるだけです。
変数名がキーになっている連想配列だと思ってください。
変数を代入してコピーしたときのzval
変数$aに111を代入。
$a = 111;
zval001
lavel |
type |
value |
is_ref |
refcount |
'a' |
IS_LONG |
111 |
0 |
1 |
$bにコピー(この時点ではrefcountが増えるだけ)
$b = $a;
zval001
lavel |
type |
value |
is_ref |
refcount |
'a', 'b' |
IS_LONG |
111 |
0 |
2 |
ここで$bの値を変更するとコピーが行われて新しいzvalが誕生します。
参照渡し乱入
しかしここで$aか$bを参照渡しすると……
$c = &$a;
すでにzvalはrefcountが2でコピーオンライト中なので、is_refを1とすることはできません。
そのため、参照渡しの変数のため新たにzvalを作ります。
zval001
lavel |
type |
value |
is_ref |
refcount |
'b' |
IS_LONG |
111 |
0 |
1 |
zval002 (新たなzval)
lavel |
type |
value |
is_ref |
refcount |
'a', 'c' |
IS_LONG |
111 |
1 |
2 |
どうでしょう?
値渡しの関数の引数に使ったあと参照渡しの関数の引数に使えば簡単に再現できますね。
とは言っても、使わなければいけないところもあるので気をつけなければいけません。
最後に
対話式シェルを使って簡単なテスト
(php -a したあと、「」 まで入力して [Ctrl]+Z)
C:\php>php -a
Interactive mode enabled
^Z
a: (refcount=2, is_ref=0)=111
b: (refcount=2, is_ref=0)=111
C:\php>php -a
Interactive mode enabled
^Z
a: (refcount=2, is_ref=1)=111
b: (refcount=1, is_ref=0)=111
c: (refcount=2, is_ref=1)=111
次回はコピーオンライトとオブジェクトの関係について書きたいと思います(予定)。