きりかノート 3冊め

おあそびプログラミング

NSNumberが即値っぽい動きをしてる

小ネタ。

勉強会の@YES, @NOの件のついでに、たとえばNSNumberが0, 1, 2とかの値のときってどうなんだろ?と思ったので試してみた。

RubyCocoaではObject#to_nsというメソッドがあって、変換可能ならてきとうにCocoaのオブジェクトを返すという機能があるので、これを使ってみよう。FixnumまたはBignumだと-[NSNumber numberWithLongLong:]を使ってNSNumberを返すはず。

   % irb -rosx/cocoa --simple-prompt
   >> 0.to_ns.retainCount
   => 9223372036854775807
   >> 1.to_ns.retainCount
   => 9223372036854775807
   >> 2.to_ns.retainCount
   => 9223372036854775807
   >> (2**55).to_ns.retainCount
   => 1
   >> (2**55 - 1).to_ns.retainCount
   => 9223372036854775807
   >> (-(2**55)).to_ns.retainCount
   => 1
   >> (-(2**55 - 1)).to_ns.retainCount
   => 9223372036854775807
   >>

9223372036854775807はNSIntegerMaxで、releaseが効かない開放されないオブジェクトはだいたいこの値を返すようになってる。推測だけど、-(2**55 -1) から (2**55 -1)の範囲ではRubyのFixnumのように即値的なオブジェクトが提供されるってことなのかな。NSValue系はあんましパフォーマンスよくないイメージだったから、こういった工夫してると思わなかったよ。がんばってるもんだねえ。

RubyCocoaだとオブジェクトのキャッシュ機構があるので、Objective-Cでアドレスがどうなるか確認してみましょう。

   #import <Foundation/Foundation.h>
   int main(int argc, const char *argv[])
   {
       long long ll;
       NSNumber *num;
       @autoreleasepool {
           ll = 36028797018963967; // 2 ** 55 - 1;
           for (int i = 0; i < 10; i++)  {
               num = [NSNumber numberWithLongLong:ll];
               NSLog(@"%d %@ (%p)", i, num, num);
           }
       }
   }

実行結果はこうなります。

   0 36028797018963967 (0x7fffffffffffffc7)
   1 36028797018963967 (0x7fffffffffffffc7)
   2 36028797018963967 (0x7fffffffffffffc7)
   3 36028797018963967 (0x7fffffffffffffc7)
   4 36028797018963967 (0x7fffffffffffffc7)
   5 36028797018963967 (0x7fffffffffffffc7)
   6 36028797018963967 (0x7fffffffffffffc7)
   7 36028797018963967 (0x7fffffffffffffc7)
   8 36028797018963967 (0x7fffffffffffffc7)
   9 36028797018963967 (0x7fffffffffffffc7)

いかにもな感じのアドレスですねえ。

   % ruby -rosx/cocoa -e '10.times {|i| p "%#x" % i.to_ns.__ocid__}'
   "0xc7"
   "0x1c7"
   "0x2c7"
   "0x3c7"
   "0x4c7"
   "0x5c7"
   "0x6c7"
   "0x7c7"
   "0x8c7"
   "0x9c7"

うんうん。

では、値を2**55にすると、

   0 36028797018963968 (0x7f83b0408070)
   1 36028797018963968 (0x7f83b040a490)
   2 36028797018963968 (0x7f83b1300000)
   3 36028797018963968 (0x7f83b1400000)
   4 36028797018963968 (0x7f83b1500000)
   5 36028797018963968 (0x7f83b1300020)
   6 36028797018963968 (0x7f83b06019e0)
   7 36028797018963968 (0x7f83b06038c0)
   8 36028797018963968 (0x7f83b0408e30)
   9 36028797018963968 (0x7f83b0406d40)

ということでアドレスが毎回変わりましたね。予測通り、2**55以上になると都度オブジェクトを生成するようです。

(3/26 追記)ちなみにブクマでコメントのあった32bitでは、上述のObjective-Cコードで確認してみたところ、-1 から +12までとえらい狭い範囲について

  • プロセス中では同じアドレスが返ってくる
  • retainCountはふつうに管理されている(1からどんどん増える)

となりました。実行の都度、アドレスは変わるので即値ではないようですね。単によく使う値についてインスタンスを共有してるって感じでしょうか。

単に値の範囲が半分くらいになるかとは予想したのですが、64bitと動作がぜんぜんちがうものなんですね。これもまた今回の発見かも。