simanのブログ

ゆるふわプログラマー。競技プログラミングやってます。Ruby好き

RubyのIntegerにおけるsuccとnext

自分はRubyで数値をインクリメントするときはいつも「+=1」を使用しているのですが、succメソッドとnextメソッドもインクリメントに使用できるので、どれが一番速いのか調べてみました。

require 'benchmark'
 
Benchmark.bm do |x|
  cnt = 100000000
  fixnum = 1
  bignum = 1 << 64
 
 
  x.report("+=1 Fixnum") do
    a = fixnum
    cnt.times{ a += 1 }
  end
 
  # Fixnum#succ
  x.report("Fixnum succ") do 
    a = fixnum
    cnt.times{ a = a.succ }
  end
 
  # Fixnum#next
  x.report("Fixnum next") do 
    a = fixnum
    cnt.times{ a = a.next }
  end
 
  x.report("+=1 Bignum") do
    a = bignum
    cnt.times{ a += 1 }
  end
 
  # Bignum#succ
  x.report("Bignum succ") do 
    a = bignum
    cnt.times{ a = a.succ }
  end
 
  # Bignum#next
  x.report("Bignum_next") do 
    a = bignum
    cnt.times{ a = a.next }
  end
end

実行結果

       user     system      total        real
+=1 Fixnum  5.550000   0.010000   5.560000 (  5.607988)
Fixnum succ  5.100000   0.000000   5.100000 (  5.104199)
Fixnum next  7.200000   0.000000   7.200000 (  7.241638)
+=1 Bignum 11.610000   0.010000  11.620000 ( 11.641881)
Bignum succ 11.960000   0.030000  11.990000 ( 12.039480)
Bignum_next 11.330000   0.010000  11.340000 ( 11.352631)

結果だけ見てみると、Bignumの時はどれもほとんど差がありませんが、Fixnumだとnextだけ遅い結果が出ました。

そこでnumeric.cの中のsuccの実装を見てみると

  VALUE
rb_int_succ(VALUE num)
{
  if (FIXNUM_P(num)) {
    long i = FIX2LONG(num) + 1;
    return LONG2NUM(i);
  }
  if (RB_TYPE_P(num, T_BIGNUM)) {
    return rb_big_plus(num, INT2FIX(1));
  }
  return rb_funcall(num, '+', 1, INT2FIX(1));
}

こんな感じになっていたのですが、別でfix_succなるものがありました。

  static VALUE
fix_succ(VALUE num)
{
  long i = FIX2LONG(num) + 1;
  return LONG2NUM(i);
}

そしてメソッド定義の部分を見ると

  rb_define_method(rb_cInteger, "succ", int_succ, 0);
  rb_define_method(rb_cInteger, "next", int_succ, 0);
  rb_define_method(rb_cFixnum, "succ", fix_succ, 0);

こんな感じでsuccは2つ定義されていました。

つまりは、succメソッドはFixnumかBignumの違いで呼ばれていた関数が違っていたのではないでしょうか。rb_int_succはfix_succに比べ判定式があるので、その分Fixnumにおいてnextが遅くなったのかもしれません。

まだしっかりと調査しきれていませんが、結論としては「+=1」でいいやって感じです。


追記(2014/04/30)

記事のタイトルをNumericからIntegerに修正しました。

Rubyで日本語関数名&変数名

前からRubyは日本語の関数が書けるのは知っていたのですが、変数も日本語にできるんですね。

def 挨拶(名前)
  puts "こんにちは" + 名前 + "さん"
end

挨拶("山田")
こんにちは山田さん

これで何か面白いことできないかなーと思ったのですが、なかなか良いのが思いつかないので、思いついたらまた何か書きます。

Rubyでユークリッドの互除法

アルゴリズムの勉強中なのですが、せっかくなのでRubyで書いたコードを残していこうかなと思っています。今回はユークリッドの互除法です。最大公約数を求める時に使います。Rubyには元からgcdメソッドがあるので、わざわざ再開発する必要はないのですが。。。

def gcd(a, b)
  a, b = b, a % b
  return a if b.zero?
  gcd(a,b)
end

puts gcd(128,72)
puts 128.gcd(72)
8
8

これから少しずつ書いていきたいと思います。

Shoesでログの出力

ShoesでGUIプログラミングしていると、デバッグの際に中の値を見たくなる時があるので、それを出力する方法です。出力にはinfoメソッドを使用します

Shoes.app {
  radius = 40
  oval(
    left: 10,
    top: 10,
    radius: radius)
  info("radius size = #{radius}")
}

実行するとこんな感じで円が出力されます。
f:id:simanman:20140305174722p:plain

この画面ではログを見ることができませんが、ここでMacだと「alt + /」を押すことでShoes consoleを呼び出すことができます。
f:id:simanman:20140305174904p:plain

この画面にログが出力されますので、このログを見ながらデバッグを行うと良いと思います。

沖縄Ruby会議01に参加してきた!

「沖縄Ruby会議01」に参加&LT発表してきました!
http://regional.rubykaigi.org/okrk01/

発表資料
「琉大図書館にRuby本を!」
http://www.slideshare.net/_siman/ruby-31905851
※ 発表資料に関しては少し修正が入っています。

まさかのトラブル

今回、琉球大学で開催されるということでスタッフとして参加していました。
「何もトラブル起こらないでくれー」と思っていたのですが、最初のまつもとさんの発表でまさかのネットワーク障害が。。。結講大きな障害だったのですが、ネットワーク復旧に対応してくれたシス管の方々には感謝です。

LT発表&プログラム修正

自分は今回の会議でコミュニティ紹介とLTの2つの発表を行ったのですが、2つ目の発表の後に「Amazonスクレイピング禁止」という事実が発覚し、その後発表を聞きながら実は裏でAmazon APIを使用するようにスクリプトの修正を行っていました。現在はちゃんとAPIを使用して情報を取得しているので何も問題はありません!

懇談会

懇談会ではLT発表者の方々から細かい話を聞いたり、ゲストの人達との写真撮影など、とても楽しい雰囲気でした。琉大にはRubyの話を出来る機会があまりなかったので、本当に楽しかったです。

ぜひ第2回も!

初めてのRuby会議だったのですが、これだけRubyの話を聞くことができたのは初めてです。個人的にRyudai.rbはOkinawa.rbに参加したりしているのですが、いつも集まるメンバーが同じだったりするので、このようなイベントで県内外の様々な人達の話を聞けるのは貴重だと思います。これはもう第2回をやるしかないですね!

Rubyの変数宣言で少しハマった話

よくRubyで多重代入とか使うんですが、この仕様は知らなかった...

a = 1,2,3
[1, 2, 3]

こうなって欲しい時はaの前に*をつけるので、ちょっと予想外でした

*a = 1,2,3
[1, 2, 3]

というのも

x = 3, y = 4

このように宣言して

x = [3, 4]
y = 4

となったのが事の発端でした。次から気をつけます

RubyのFixnumの最大値と最小値

気になったので調べてみました。
Rubyではある値を越えた時にFixnumからBignumへと変わります。

p (2**0).class #=> 1
p (2**10).class #=> 1024
p (2**100).class #=> 1267650600228229401496703205376
p (2**1000).class #=> 略
Fixnum
Fixnum
Bignum
Bignum

そこで二分探索を行ってFixnumとBignumの境界値を調べてみました。

class Fixnum
  class << self
    def const_missing(name)
      if [:MAX,:MIN].include?(name)
        generate_max_min
        self.const_get(name)
      else
        super
      end
    end

    def generate_max_min
      a = 0
      b = 2**256

      while b-a > 1
        c = (a+b)/2

        if c.instance_of?(Fixnum)
          a = c
        else
          b = c
        end
      end

      self.const_set(:MAX, a)
      self.const_set(:MIN, -(a+1))
    end
  end
end

p Fixnum::MAX
p Fixnum::MIN
4611686018427387903
-4611686018427387904

ちゃんと境界値が取得できているかチェックします

p 4611686018427387903.class
p 4611686018427387904.class
p -4611686018427387904.class
p -4611686018427387905.class
Fixnum
Bignum
Fixnum
Bignum

ちゃんと取得できていました。試していないですが、実行する環境によって値が変わったりするかも。

追記

実装レベルで説明があってとても参考になりました
http://patshaughnessy.net/2014/1/9/how-big-is-a-bignum