Cの拡張ライブラリを作ってみた
Cを使った拡張ライブラリが簡単に書けるという噂があるので、昨日つくった「各桁に1から9が必ず1度だけ現れる」ということを判定する関数の部分だけ、RubyからCに置き換えたら高速化するかどうか試してみた。
元のコードは、以下。
def has9digits? (num) digits = num.to_s.split(//).sort.join digits == "123456789" end 10000.upto(33333) do |i| print i," * ",i," = ",i**2,"\n" if has9digits?(i**2) end
このhas9digits?がいかにも遅い。Ruby用拡張ライブラリをCで作るというページを参考に、以下の2つのファイルを用意した。
#include <ruby.h> VALUE uniq(VALUE self, VALUE n) { VALUE r; int i, num, amari; int flag = 1; int d[10] = {0}; num = NUM2INT(n); for (i = 1; i < 10; i++) { amari = num % 10; d[amari]++; num = num / 10; } for (i = 1; i < 10; i++) flag *= d[i]; r = INT2FIX(flag); return r; } void Init_Digit(void){ VALUE rb_cDigit; rb_cDigit = rb_define_class("Digit", rb_cObject); rb_define_method(rb_cDigit, "uniq", uniq, 1); } (digit.c)
require "mkmf" create_makefile("Digit") (extconf.rb)
それで、ruby extconf.rbとやるとMakefileができて、makeするとDigit.soができた。これを使って、
require 'Digit.so' d = Digit.new 10000.upto(33333) do |i| print i," * ",i," = ",i**2,"\n" if (d.uniq(i**2) == 1) end (c9digits.rb)
を実行すると、ちゃんと動いた。Rubyのみで書いたものと、Cの拡張ライブラリを使って計算したもので実行時間を測ると、
$ time ruby 9digits.rb >/dev/null real 0m0.678s user 0m0.664s sys 0m0.020s $ time ruby c9digits.rb >/dev/null real 0m0.175s user 0m0.152s sys 0m0.024s
おおぉ、速い! 当たり前だけど。
こうやって自分で書いてみると、RHGを読んでいたときには「そんなもんか、ふーん」と思っていたVALUEやらrb_define_methodも、なるほどという感じ。NUM2INT辺りのマクロは型チェックもしてくれているらしいけど、便利にできてるなぁ。