奇特なブログ

「殊勝に値する行いや心掛け」を意味する、奇特な人になる為のブログです

値渡しと参照渡しの速度比較

今回の内容は、先月書いた値渡しで値を変えると処理速度が激遅になる時があるの続きなんですけど、
それについて、先日とある勉強会で話をして来ました。
で今回は、話をした内容をテキストにしたもの(自己紹介とかは除外してるけど)を書きたいと思います。
赤文字になっている箇所は、この記事の後半で補足しています。
また、横線の区切りはPPTのスライドのページ区切りと同一です。

-------------------------------------------------------------------------------------------------------
値渡しと参照渡しの比較
by kitoku
-------------------------------------------------------------------------------------------------------
[本題]
軽くブログ(前回の記事)の内容をおさらい
・値渡し後に引数の値を変えると、呼び出し元の値を維持する為に値のコピーが行われ時間がかかる
・よって、引数の値を変える場合には、参照渡しかオブジェクト指向カプセル化(セッター)を使うと良い
-------------------------------------------------------------------------------------------------------
[引数のデータ型は関係あるか?]
・boolean、float、int、NULL、objectは、参照渡しの方が速いが気になるレベルの差でもないと思う*1
・resourceは差が出なかった。値の変更は、fopenで違うファイルを読むとしたのが原因か?
・stringと配列(数値か連想問わず)は、length(文字数や要素数)が多いほど、参照渡しの方が有利になる
-------------------------------------------------------------------------------------------------------
[文字列と配列が顕著なのは何故?]
・値を格納するメモリ領域のサイズが可変だからっぽい
・例えばintだと、値が0でもint_maxでも確保するメモリ領域のサイズは変わらないが、
文字列(文字の配列(PHPC言語と同じっぽい))と配列(数値か連想問わず)だけは、文字数や要素数によって、サイズが変わる
・intなどでも時間差は発生するが、メモリ領域のサイズが変わらないので、微々たる差に留まる
←よって、文字列と配列の時に特に注意
-------------------------------------------------------------------------------------------------------
[処理の何処で時間がかかるのか?]
・引数の値を変える「瞬間」(コピーオンライトという仕組み)
・値渡しだと、関数の呼び出し元の値を保持する為、値のコピーが行われるが、コピーが行われるタイミングは、引数を渡した瞬間では「ない」
・だから値渡しでも、その引数の値を操作しなければ、時間はかからない
・コピーにかかる時間は、メモリ領域のサイズの大きさに比例する(だから文字列と配列が注意)
-------------------------------------------------------------------------------------------------------
[サンプルプログラムその1]
*2
function 関数名($引数A, $値A) {
  $引数A = $値A;←このタイミングで値のコピーが行われるので、この行の処理に時間がかかる
  return $引数A;
}

function 関数名($引数A, $値A) {
  $変数A = $引数A;←この場合は、値を変更していないので、値のコピーは行われず、時間もかからない
  return $変数A;
}

function 関数名($引数A, $値A) {
  $変数A = $引数A;
  $変数A = $値A;←これでも引数Aの値のコピーが行われる。変数Aと引数Aで同じメモリ領域を見てる?
  return $変数A;
}
-------------------------------------------------------------------------------------------------------
[サンプルプログラムその2]
*3
class クラス名 {
  public function メソッド名($引数A, $値A) {
    $引数A = $値A;←メソッドにしても所詮は値渡しされた引数の中身を変えているので結果は変わらない
    return $引数A;
  }
}

$変数A = 値B;
$変数B = new クラス名();
$変数A = 変数B->メソッド名($変数A, 値C);
-------------------------------------------------------------------------------------------------------
[サンプルプログラムその3]
class クラス名 {
  public function コンストラクタ() { $this->init(); }
  public function init() { $this->set_変数A(null); }
  public function set_変数A($変数B) { $this->変数A = $変数B; }
  public function get_変数A() { return $this->変数A; }
  public function メソッド名($値A) { $this->変数A = $値A; }
  private $変数A;
}

$変数C = new クラス名();
$変数C->set_変数A(値B);
$変数C->メソッド名(値C);
// 値を取得したければ「$変数C->get_変数A()」

値渡ししていても、その引数の中身を操作していないので問題ない
-------------------------------------------------------------------------------------------------------
で、終わりのはずが・・・
-------------------------------------------------------------------------------------------------------
[前回のブログ記事のはてブにこんなのが]
理屈では知っていても、結果を見ると改めて驚かされますね。ただ速度を考慮しないのであれば、参照渡しとか破壊系のメソッドとかは使いたくないしなぁ。
←破壊系のメソッドってなぁに?
-------------------------------------------------------------------------------------------------------
[破壊系のメソッド]
・オブジェクトの内部状態(値)を変えてしまうメソッド
Rubyに多いみたい
・例えば、「$str = 'a';」の時、「$str->小文字を大文字に変えるメソッド();」を実行し、「戻り値がない」のに、$strが'A'になる様なメソッド
・つまり、自分で自分のクラスのメソッドを呼んでるのに、自分の内部状態を変えてしまうメソッド(ミュータブル的とも言う)
-------------------------------------------------------------------------------------------------------
[非破壊系のメソッド]
・オブジェクトの内部状態(値)を変えないメソッド
・さっきと逆で、内部状態を変えたい時には、戻り値が必要
・例えば、「$str = 'a';」の時、「$str = $str->小文字を大文字に変えるメソッド();」を実行すると、$strが'A'になる様なメソッド
・つまり、自分で自分のクラスのメソッドを呼ぶだけでは、自分の内部状態は変わらないメソッド(イミュータブル的とも言う)
-------------------------------------------------------------------------------------------------------
[で・・・]
・破壊的(ミュータブル的)と参照渡しとオブジェクト指向
・非破壊的(イミュータブル的)と値渡しと非オブジェクト指向
・これらって似てないか?
・前者はオブジェクトの状態が「変わる」が、後者は「変わらない」って角度で見たときに
-------------------------------------------------------------------------------------------------------
[イミュータブルなクラスのサンプル]
final class クラス名 {
  public function コンストラクタ($変数A) { $this->変数B = $変数Aをディープコピーする; }
  public function get_変数B() { return $変数Bをディープコピーする; }
  public function メソッド名($値A) { new 自分のクラス名($値A); }
  private final(PHPは変数にfinal書けないけど) $変数B;
}

$変数C = new クラス名(値B);
$変数C->メソッド名(値C);
// 値を取得したければ「$変数C->get_変数A()」

値渡しした引数の中身を変えていない(というかfinalだと変えれない)ので、速度は低下しない
-------------------------------------------------------------------------------------------------------
[ミュータブルとイミュータブルの比較]
・イミュータブル

メリット:
1.変数の値(オブジェクトの状態)の変化に気を使う必要がないので使いやすい

デメリット:
1.(値渡しは)複数の戻り値を返しにくい*4
2.(値渡しは)値のコピーに処理時間がかかる
3.(イミュータブルは)不変なので、値を変えたい時には新たにインスタンスを生成しなければならない(コスト的にどうか)
4.(イミュータブルは)finalなので継承が出来ない。よって拡張性に欠ける
-------------------------------------------------------------------------------------------------------
[ミュータブルとイミュータブルの比較]
・ミュータブル

メリット:
1.(参照渡しは)複数の戻り値を返しやすい*5
2.引数は「(多分)メモリのアドレスを参照しているだけなので実態をコピーする必要がなく」処理時間がかからない
3.値を変えたい時に、インスタンスを生成する必要がない
4.finalじゃないので、継承したりして拡張出来る

デメリット:
1.引数の値(オブジェクトの状態)の変化に気を使う必要があるので使いにくい

とはいえデメリットについて。
値を変えたい変数は引数で渡さずに、必ずセッター経由で値を変える様にすると、
セッター内でデバッグすれば(どのファイルのどの行から呼ばれたかを調べる)きっちり監視できる
-------------------------------------------------------------------------------------------------------
[ミュータブルの方が良い別の理由]
ソフトウェアは、機能追加や改修により「成長」していくもの(アジャイルが典型的)
保守性の高さがウリと思う、オブジェクト指向(つまりミュータブル)は、そういった意味でソフトウェア開発に合っていると考える
だから、ミュータブルの方が良いと思う
といっても、イミュータブルの方が良い時がある可能性も否定はしない(どういう時かは不明)
-------------------------------------------------------------------------------------------------------
[結論]
ミュータブルの方が扱いが難しいかもしれないが、頑張って勉強して使いこなそう!
イミュータブルも、使える局面があった時に使える様に勉強はしておこう!
今回の話が、皆さんのより良いソフトウェア開発ライフの一助になれば幸いです
-------------------------------------------------------------------------------------------------------

ここからは、上記本文中の「*」についての補足です。