RubyInlineがすごい

投稿者 nanki 2007-03-12 14:14:00 GMT

Rubyコード中にCのコードを埋め込めるRubyInlineを使って、 ボトルネックとなっているメソッドを置き換える。

# rubyinline.rb
def benchmark
  s = "a" * 10000

  test = Test.new
  t = Time.now
  1000.times{test.string_xor(s, s)}
  Time.now - t
end
  
class Test
  def string_xor(str1, str2)
    result = str1.clone
    str1.length.times do |i|
      result[i] ^= str2[i]
    end
    result
  end
end
  
b1 = benchmark
  
begin
  require 'inline'
  class Test
    inline do |builder|
      builder.c <<-EOF
        VALUE
        string_xor(VALUE str1, VALUE str2)
        {
          VALUE result = rb_str_new(RSTRING(str1)->ptr, RSTRING(str1)->len);
          int i;
          for (i = 0; i < RSTRING(str1)->len; i++) {
            RSTRING(result)->ptr[i] ^= RSTRING(str2)->ptr[i];
          }
          return result;
        }
      EOF
    end
  end
rescue LoadError
end
  
b2 = benchmark
  
p b1/b2

長さ10000の文字列同士のxorを1000回取る、というプログラムでテスト。

$ ruby -rubygems rubyinline.rb
192.710154532523

192倍!

2007/4/6 追記:

そういえば、GCを切るのを忘れていたと思って、GC無しで実行したら、300倍になった。

参考:

RubyInline


RubyのGeometry系ライブラリ - パース対決

投稿者 nanki 2007-03-06 18:29:00 GMT

必要だったのでベンチマーク対決してみた。 対象は、GEOS, GeoRuby, おまけで自分で実装したWKBパーサ。

GEOSは、JavaのJTSをC++に移植したもので、RubyのバインディングがSWIGで用意されている。

GeoRubyは、PureRuby実装。最近は、地図業界で使われることの多いShapeファイルや、xBaseファイルを扱うクラスも追加されたようだ。

最後のは、僕が初めてRubyで書いたまじめなコード。 大量のデータを処理する目的があったので、エラー処理もすっ飛ばして、可能な限り速く動くようにしてある。 中身はほとんどunpack<<[]

こんなコードで約二万件のMultiLineStringをパース。

# require やらARのモデル定義は省略
def benchmark
  roads = Road.find(:all).map{|road| road.the_geom[4..-1]}
  t = Time.now
  roads.each do |road|
    yield road
  end
  p Time.now - t
end
  
benchmark do |road|
  Geos::geom_from_wkb(road)
end
  
benchmark do |road|
  GeoRuby::SimpleFeatures::Geometry.from_ewkb(road)
end
  
benchmark do |road|
  Geometry::WKBParser.parse(road)
end

結果は、

0.375728  #Geos
5.319374  #GeoRuby
1.353812  #Geometry

やはりC++コードは速い。SWIGのオーバーヘッドはどれくらいなんだろう。

Rubyのコードもそこそこ。頑張れば数倍程度。思ったより遅くない。

さらに、YARVでもやってみた。 ActiveRecord は動かなかったので(大幅に仕様の変ったというsend でこけた時点であきらめた)Marshal.dumpしたデータを利用。 再コンパイルが面倒だったのと、実質のコードはほとんどRuby側じゃないのでGEOSは除外。

3.836262  #GeoRuby
0.699637  #Geometry

YARV速っ。

参考:

:yの悲劇

投稿者 nanki 2006-12-21 18:51:00 GMT

GPS情報を格納するのに、

class Entry < ActiveRecord::Base
  has_one :location
end
  
class Location < ActiveRecord::Base
  # 実際にはGeometry型で格納するので、アクセサを定義
  def x
    point.x
  end
  def y
    point.y
  end
end

こんなモデルを作ると・・・

$ script/console
>> entry = Entry.find(:first)
>> entry.location.x
=> 135.0
>> entry.location.y
=> 35.0
>> entry.location.send :x
=> 135.0
>> entry.location.send :y
ArgumentError: wrong number of arguments (0 for 1)
        from (irb):35:in `y'
        from (irb):35:in `send'
        from (irb):35

xは大丈夫なのにyはだめ?

yの正体は、pのようにyamlを吐くprivate method、Kernel#y in yaml.rb。 何故だ?

entry.location の中身は、実はAssociationProxyで、ActiveRecordのソースを見るとこんな風になっている。

# lib/active_record/associations/association_proxy.rb
module ActiveRecord
  module Associations
    class AssociationProxy #:nodoc:
      attr_reader :reflection
      alias_method :proxy_respond_to?, :respond_to?
      alias_method :proxy_extend, :extend
      instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^proxy_extend|^send)/ }

entry.location.send :x すると、AssocationProxy#xが無いのでtargetにsendされるが、entry.location.send :y だと、Kernel#yが見つかってしまうのか・・・

当然entry.location.target.send :y だとうまくいく。

Ruby1.9 だとsendの仕様が違ってうまくいきそう。


Hash.new

投稿者 nanki 2006-06-01 06:06:00 GMT

Hash.new{|hash, key| ...}を悪用してフィボナッチ数列を返す変なハッシュ。

fib = Hash.new{|h, k| h[k] = h[k-1] + h[k-2]}.update(0 => 0, 1 => 1)

p fib[30] # 832040

「Rubyプログラミング入門」には

def (Fib = [0, 1]).[](n)
  super || Fib[n] = Fib[n-1] + Fib[n-2]
end

p Fib[30] # 832040

というのが載っていて、こっちの方がかっこいい。

あと、Hash.new{[]} という形が面白いのでよく使います。グループ化する時とか。

Ruby覚え立ての頃、初々しいコード。

a = {}
zip_address.each do |zip, address|
  if a[zip] then
    a[zip] << address
  else
    a[zip] = [address]
  end
end

unlessの後置構文とか覚え出して。

a = {}
zip_address.each do |zip, address|
  a[zip] = [] unless a[zip]
  a[zip] << address
end

条件分岐使わなくなる。

a = {}
zip_address.each do |zip, address|
  a[zip] ||= []
  a[zip] << address
end

お気に入り。

a = Hash.new{[]}
zip_address.each{|zip, address| a[zip] <<= address}

Ruby勉強会@関西 10

投稿者 nanki 2006-05-16 04:28:00 GMT

今回は半分弱が初参加、60人の定員が満員という勢い。 バス一本乗り過ごして、10分ほど遅れて到着。

丸投げシリーズ(3) C

西本さんの発表。

CのライブラリをRubyから使うというもの。 * #include "ruby.h" * Init_foo(foorequire 'foo'される)の中に、クラス情報、あるいはメソッド情報をつらつらと書きつくる。 * mkmfライブラリを使って、Makefileを作る。 * makeする。 * 30倍から100倍程度の高速化ができることも。

実はなかなか踏み出せないでいた領域だけに(いつもRubyレベルでのアルゴリズムの最適化とかで逃げてる。逃げてるのか?)非常に役立つ内容だった。
さんざん最適化した上でNativeにしたら、相当速くなりそうだ。 非CygwinのWindowsコンパイラを探さないと。
大量のマクロとrb_*が次なる壁。

ゲーム用フレームワーク 「Miyako」

サイロス誠さんの発表。スライドの一枚目がCryossになってる気がしたけど。

本人曰く、女性参加者が予想外に多くて、ネタに対する反応が心配だったそうで、確かにちょっとヒヤヒヤ。
(概ね、高飛車な女性がMiyakoのなんたるかを博士&助手に説明するという内容)
僕は前のほうだったのであまり気にならなかったけど、文字のサイズが小さくて後ろのほうの人は見えなかったそうで、途中から、普段勉強会の会計を担当してくださっている、あゆさんが声優をやって、別の意味で盛り上がっていた。

肝心の内容のほうは、発表の形式がシナリオ形式だったので、記憶に残ってるのは主にデモ中心。
紙芝居的なゲーム(呼び方知らない)を作るなら一考の価値あり、だと思います。 Miyako自体のコードはあまりRubyっぽくないので、Rubyっぽい実装になって欲しい。 あと、プレゼンツールとしての使い道に期待する人もいるようで、今後のバージョンアップはそっちの方向に向かうとか。

それなら、フルスクリーン対応は必須になるね。

ライトニングトーク Ruby on Google SketchUP

兼重さんの発表。Googleが発表したブツを早速。 このスピード感がライトニング。 デモは、SketchUpでぐいぐい立体を作って(ここはRubyと関係ない)、PureImage(西本さん作)で作ったテクスチャーを貼り付けるというもの。

西本さん、丸投げばかりじゃなかったのね。
PureImageの回は参加できなかったのですっかり忘れてた。

Rubyと関係ないFollow Meツールがうまくいかないのでちょっとヒヤッとしてた。

Ruby 初級者向けレッスン 7 -ブロックとイテレータ-

最後はかずひこさん・コウザイさんによる発表。 もう七回目になるのか。僕が最初に参加したときは一回目だったかなぁ。 内容は、イテレータ・ブロックを使ってみる、というもの。 今回は、時間がよっぽど足りなかったらしく、途中のキリのいいところ?で終わってしまった。 * injectの引数省略知らなかった * 僕、Ruby歴2年半くらいなんですが・・・ * おじさん向け課題:パスカルの三角形

(1..9).inject([1]){|r,| p r;(r+[0]).zip([0]+r).map{|a| a[0]+a[1]}}
* いまいさんの解答、enum_consとか勉強になります。 * to_procのやつ(.map(&:+))、引数一個以上のメソッドを渡しても大丈夫なのか。むむ。 * &:+が梅干たべたパーマの外国人みたい。 * 偶然か必然か、途中までまったく一緒・・・。1..9 とか、10にすると文字が一文字増えるからだよなぁ。

小波ゼミでは最初からRubyを教え込まれるので、卒業するころにはRuby暦3年とか4年とかざらにいるみたいです。恐るべし。

発表の形式として、壇上で先生と生徒の対話みたいな形がとられていた?(→意図したものだったようで)のは聞いてる方としては入りやすいんじゃないかと思う。
Ruby/SDL上での先生と生徒の対話はちょっと食傷気味だけど。
後半の応用編(ログ解析)は次回に持ち越し?

プリンセスラインに対する京都女子大学の絶大なる影響力(ピポパで臨時便)を目の当たりにしつつ、懇親会へ。

懇親会の内容は日記になるので略。

参考:

第10回 Ruby勉強会@関西
Miyako メインサイト


Thinkpad の加速度センサをRubyから使う(2)

投稿者 nanki 2006-05-08 05:46:00 GMT

前回のバージョンはモノクロでスクリーンショットが地味だったり、放っておくと収束してしまって長い時間楽しめない、という問題があったのでバージョンアップ。

新機能は * 速度にあわせて、色相や輝度が変化。 * くっつきモード。 * すごい勢いで壁にぶつけると分裂。

など。 もうちょっと有機的にくっついたりしてくれると楽しいんだけど。

screenshot

使い方は前回同様

$ ruby leap-frog.rb
  • Tab キーで粒々生成。
  • Enter キーでくっつきモードに。

追記: 2007/03/01

この間のLiveCodingでyharaさんからライセンスについて質問されたので、 修正BSD相当とビールウェア(optional)ということにしておきます。

参考:

Thinkpad の加速度センサをRubyから使う


Rails Plugin

投稿者 nanki 2006-04-25 23:35:00 GMT

全部終わったら公開しようと思っていたけど、全く終わる気配がないので公開します。

今回は量が多いので分割しました。

全部見たい人は、 タグ:rails_pluginでどうぞ。


Thinkpad の加速度センサをRubyから使う

投稿者 nanki 2006-03-13 01:36:00 GMT

今回の第9回 Ruby勉強会@関西での初級者レッスンは、「わかりやすいRubyのコードを書こう」という題で、小波ゼミの学生さんのコードを元に、わかりやすくしていく、という内容。

元々のソースコードがLeap-Frog法という数値計算の手法で運動方程式を解く、というものだったので、前日に調べておいたThinkpadの加速度を読む方法を早速組み込んでみた。 (前日に調べたのは読み出す方法までで、結果がどう値に対応してるのか調べるために、こっそりPCを振るのに苦労した)

Thinkpadを傾けると、そっちの方向に重力が働くというもの。

Rubyでの情報の可視化や入力インターフェースのものめずらしさもあってか、懇親会での受けが結構よかったので公開します。 * leap-frog.zip

加速度が変わりまくるので、コード中の方法ではあんまり意味がないような。

会場からは、最適化に関する質問や、誤差に関する指摘が多かっただけにちょっと心配だけど、本質はそっちじゃないので目を瞑ってください。

  • ドライバと直接通信、しかも仕様がよくわかってないから適当、ということをやっているので、加速度センサ付きThinkpadでも動かない可能性があります。
  • 昔 mimio と通信してた時は、変なデータを送ると画面が真っ青になったりしました。
  • sensor.dllのAPIを呼び出すのが正当なやり方。

  • テスト環境は、T43,WindowsXP,ActiveScriptRuby1.8.4,Ruby/SDL1.0.0Windows用binary。

Thinkpad以外のノートPCにも加速度センサを搭載しているものがあるらしいので、頑張れば動くかも。

次回の勉強会、みんなでThinkpadをフリフリしましょう。

参考

Ruby/SDL

ActiveScriptRuby

Thinkpad加速度センサを使ったソフトウェア・リンク集


vim/rubyのための部分最適化

投稿者 nanki 2006-01-21 19:06:00 GMT

この辺 を読んで思い出した。

数年前、Javaコードのメトリクス(というか、文字別タイプ量)を調べた時、もっとも多かったのは;と()だった。

考えてみれば、このセミコロン、ほとんどすべての行末に現れて、しかも、ほぼ100% ;+Enter の形で出現、右小指を二回連続消耗するというあり得ないほど無駄にタイピングの苦痛を増やしている奴なのだ。いくらStrong Typingと言ったって・・・

当時はvimでJavaのコードを書いていたので、早速;を;+Enter に置き換え、なんとか快適な環境を作ることに成功したのだった。

その時の経験からか、あまりタイピングが苦痛にならないruby でも、次のような部分的な最適化をしている。

  • ; で改行
  • 文字列中の;では反応しない
  • end も自動で改行
  • begin,else,then,ensureなどの予約語で改行
  • rails 向けに、Ctrl+=,Ctrl+eでerbのタグ
" ~/.vimrc
au BufNewFile,BufRead *.rhtml setf eruby

autocmd FileType html imap <buffer> <C-_> <%=  %><Esc>2hi
autocmd FileType html imap <buffer> <C-E> <%  %><Esc>2hi
autocmd FileType ruby imap <buffer> <C-L> RAILS_DEFAULT_LOGGER.debug()<Esc>i

function SmartSemicolon()
  let s = synIDattr(synID(line("."),col("."),0),"name")
  if s == "rubyString"
    execute "normal a \<Esc>vr;"
  else
    execute "normal a\<Enter> \<BS>"
  endif
endfunction

function SmartEnd()
  let s = synIDattr(synID(line("."),col("."),0),"name")
  if s == "" || s == "rubyNoDoBlock"
    execute "normal ae\<Esc>and\<Enter> \<BS>"
  else
    execute "normal ae\<Esc>and"
  endif
endfunction

autocmd FileType ruby imap <buffer> begin begin<Enter>
autocmd FileType ruby imap <buffer> end  <Esc>:call SmartEnd()<CR>a
autocmd FileType ruby imap <buffer> then then<Enter>
autocmd FileType ruby imap <buffer> ensure ensure<Enter>
autocmd FileType ruby imap <buffer> else else<Enter>
autocmd FileType ruby inoremap <buffer> ; <Esc>:call SmartSemicolon()<CR>a

コード補完も含めコーディング時の最適化というのは、タイプ量当たりの情報量をいかに増やすかというところにある。

コード中に頻出するパターンを見つけ出して、一つずつつぶしていくのが、部分最適化が好きな人間にはたまらない。

次にやるとしたら「閉じるキー」。 begin-end,(),{},||,”“,” などを区別せず、閉じるキーを押すと一番近くの括弧を閉じてくれる、というやつ。 スペース近くの使わないキーに割り当ててやりたい。

誰か作りませんか?or既にある奴をご存知ないですか?


DRUM

投稿者 nanki 2006-01-19 05:58:00 GMT

ちょっと前から注目のプロジェクト、DRUM が徐々にその姿を現し始めた。

DRUMは、現在、Ruby/Java/C#での開発が進められている、多言語対応の分散オブジェクトブローカー。 ネットワークの向こうのオブジェクトに透過的にアクセスできる仕組み。

個人的には、3Dに凝ってた時代があるので、LookingGlassのAPIをたたくデモが興味深い。

全く本質的じゃないところに興味を持ってしまって後ろめたいのだが。

irbで「光あれ」とかやると光源ができたりするんだろうな。いいなぁそれ。

今度のRuby勉強会でデモが見られそう!

なんだかうれしくなって、気がついたらドラムができてた・・・。

drum

参考:

航海日誌