fc2ブログ

奇特なブログ

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

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

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

-------------------------------------------------------------------------------------------------------
値渡しと参照渡しの比較
by kitoku
-------------------------------------------------------------------------------------------------------
[本題]
軽くブログ(前回の記事)の内容をおさらい
・値渡し後に引数の値を変えると、呼び出し元の値を維持する為に値のコピーが行われ時間がかかる
・よって、引数の値を変える場合には、参照渡しかオブジェクト指向のカプセル化(セッター)を使うと良い
-------------------------------------------------------------------------------------------------------
[引数のデータ型は関係あるか?]
・boolean、float、int、NULL、objectは、参照渡しの方が速いが気になるレベルの差でもないと思う*1
・resourceは差が出なかった。値の変更は、fopenで違うファイルを読むとしたのが原因か?
・stringと配列(数値か連想問わず)は、length(文字数や要素数)が多いほど、参照渡しの方が有利になる
-------------------------------------------------------------------------------------------------------
[文字列と配列が顕著なのは何故?]
・値を格納するメモリ領域のサイズが可変だからっぽい
・例えばintだと、値が0でもint_maxでも確保するメモリ領域のサイズは変わらないが、
文字列(文字の配列(PHPもC言語と同じっぽい))と配列(数値か連想問わず)だけは、文字数や要素数によって、サイズが変わる
・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.引数の値(オブジェクトの状態)の変化に気を使う必要があるので使いにくい

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

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

*1→秒数の差は、値渡しか参照渡しをする以外は処理に差異がない関数を1000000回呼び出して0.1秒前後でした
*2→値渡しをしている前提です
*3→*2と同じ
*4→関数内で変更した変数を、1つずつ配列に追加していけば出来るが、要素の何番目にどの変数が入っているかを意識しないといけないので、微妙だと思います
*5→複数の戻り値ではなく、関数内で複数の変数の値を変更して呼び出し元に反映できるという意味です

また、話した時に頂いた質問についても、以下で触れてみます。
ちなみに、他にも質問がありましたが忘れました(苦笑)
終わった後にすぐまとめたほうが良いですねぇ。

Q.ミュータブルにすると何がオイシイのか?

A.アジャイルとの相性が良い辺り。また、イミュータブルだと本文の通りオイシクない時があるので、相対的にミュータブルがオイシくなると思います

Q.objectはクラス構成(メンバーの数などクラスの大きさ)によって速度が変わるのでは?

A.構成を変えて試してはいないので分からない。あくまでも今回は、同じ構成で値渡しと参照渡しをした時の速度比較した時の話です

最後に、ウチ自身の継続課題などを。

ミュータブルだと、マルチスレッドの時に値の書き換えを心配しないといけないのでは

Webだと、リクエスト(スレッド)ごとにインスタンスを生成すれば、
値を共有することはない?

シングルトンは?

シングルトンは、単一のインスタンスをずっと変えない性質なのでイミュータブルと少し似ている?
であれば、少なくとも積極的に使う必要はないと思う。
疑似グローバルという側面もあるので。

PHPの内部的には一体どうなっているのか?

PHPのデータ構造を読む。
といってもこの辺は、C言語の知識の応用で事足りる?

とりあえず今回はこんな所です。

<追記>
えっと、PHPの関数では、参照渡しよりもreturnしたほうが速い!?というページを見つけました。
で、ウチも検証してみましたが、確かに2倍ぐらい差が出ます。
ただ、今回の記事と違って、値渡しを「していない(正確にはする必要がない)」ので、
そもそも引数が不要な場合(引数の値を関数内で使う必要がない場合)には、
参照渡しよりreturnで戻り値を返した方が良いよって事だと思います。
また、多分突っ込みで、
「この関数が終わった後に、他のクラスで使いたいから参照じゃないとダメなんだ」っていうのがあるかもしれませんが、
それなら、関数が終わった後にセッターに代入しておけば良いのではと思います。
といってもまあ、これはワリと本気で、
クラス構成(こっちは上手く組めば無い?)や処理の流れ(特にコッチ)によっては、
参照渡しにせざるを得ないケースもあるかもしれません。
まぁ、なかなか難しい所だと思います。
以上です。

スポンサーサイト



テーマ:日記 - ジャンル:日記

  1. 2012/02/19(日) 21:25:48|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
次のページ