Born Too Late

Yuya's old tech blog.

代入の返り値は代入値

2010-02-09 23:27:42

例えば memoize しながらフィボナッチ数列を求める場合。

class Fibonacci
  def initialize
    @memo = [0, 1]
  end

  def fibonacci(n)
    @memo[n] || @memo[n] = fibonacci(n - 2) + fibonacci(n - 1)
    @memo[n] # この行は省略しても結果は同じ
  end
end

fib = Fibonacci.new
10000.times { |n| fib.fibonacci(n) }

これで何が嬉しいのかというと、プロファイリングしてみるとこんな結果が現れます。

fibonacci メソッドの最後の行を省略しなかった場合

$ time ruby -rprofile fibonacci.rb
  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 67.92     3.24      3.24    29996     0.11     0.21  Fibonacci#fibonacci
 16.98     4.05      0.81    59992     0.01     0.01  Array#[]
  6.29     4.35      0.30        1   300.00  4770.00  Integer#times
  3.56     4.52      0.17    19996     0.01     0.01  Fixnum#-
  2.73     4.65      0.13     9954     0.01     0.01  Bignum#+
  2.52     4.77      0.12     9998     0.01     0.01  Array#[]=
  0.00     4.77      0.00        2     0.00     0.00  Module#method_added
  0.00     4.77      0.00        1     0.00     0.00  Fibonacci#initialize
  0.00     4.77      0.00       45     0.00     0.00  Fixnum#+
  0.00     4.77      0.00        1     0.00     0.00  Bignum#coerce
  0.00     4.77      0.00        1     0.00     0.00  Class#inherited
  0.00     4.77      0.00        1     0.00     0.00  Class#new
  0.00     4.77      0.00        1     0.00  4770.00  #toplevel

real    0m6.005s
user    0m4.776s
sys     0m1.184s

fibonacci メソッドの最後の行を省略した場合

$ time ruby -rprofile fibonacci_fast.rb
  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 66.39     2.39      2.39    29996     0.08     0.14  Fibonacci#fibonacci
  8.06     2.68      0.29    29996     0.01     0.01  Array#[]
  8.06     2.97      0.29    19996     0.01     0.01  Fixnum#-
  6.94     3.22      0.25        1   250.00  3600.00  Integer#times
  5.56     3.42      0.20     9998     0.02     0.02  Array#[]=
  5.00     3.60      0.18     9954     0.02     0.02  Bignum#+
  0.00     3.60      0.00        1     0.00     0.00  Fibonacci#initialize
  0.00     3.60      0.00        1     0.00     0.00  Class#inherited
  0.00     3.60      0.00        2     0.00     0.00  Module#method_added
  0.00     3.60      0.00        1     0.00     0.00  Class#new
  0.00     3.60      0.00       45     0.00     0.00  Fixnum#+
  0.00     3.60      0.00        1     0.00     0.00  Bignum#coerce
  0.00     3.60      0.00        1     0.00  3600.00  #toplevel

real    0m4.516s
user    0m3.600s
sys     0m0.904s

実行時間が約 25% 短縮されました。

注目するポイントは 2 行目の Array#[] メソッド、つまり配列から値を引き出す処理の回数が半分になっているというところ。どうしてこうなるかは、コードの意味を考えながら読めばすぐにわかると思います。ただ、こういう最適化によって、有意な効果を得られるケースがあるのかは疑問ですが・・・。

でもせっかくなので、代入の返り値はどうなってるのか、言語ごとに調べてみました。

Ruby

p (foo = "bar")
# => "bar"

JavaScript (Rhino)

var foo;
print(foo = "bar");
// => bar

Perl

my $foo;
print $foo = "bar";
# => bar

PHP

var_dump($foo = "bar");
// => string(3) "bar"

MySQL (これはちょっと番外編?)

mysql> SELECT @today := CURDATE();
+---------------------+
| @today := CURDATE() |
+---------------------+
| 2010-02-09          |
+---------------------+
1 row in set (0.00 sec)

と、ここまでは、どれも代入値が返ってくるようですが・・・

Python

print(foo = "bar")
# => SyntaxError: invalid syntax

Python では Syntax Error となります。

これを知って何の役に立つのかイマイチわかりませんが、こういうことになっていますよ、ということで。