Ruby 2.4 で CGI.unescape が高速化されていた
久しぶりに ruby をいじる機会があったので、備忘的に。
URL encode された文字列を decode したいのだけど、Ruby 2.1 の URI.decode
だと遅すぎる!
というのをなんとか解決できないかと調べていました。
ベンチマークコード
似たようなことをやってくれるメソッドが複数あったので、とりあえずベンチとってみる。
require 'benchmark' require 'uri' require 'cgi' encoded = '%7B%22hoge%22%3A%7B%22foo%22%3A%22bar%22%2C%22hoo%22%3A%22baz%22%7D%7D' # => {"hoge":{"foo":"bar","hoo":"baz"}} def manytimes 100000.times { yield } if block_given? end Benchmark.bmbm do |bm| bm.report("URI.decode") { manytimes { URI.decode encoded } } bm.report("URI.decode_www_form_component") { manytimes { URI.decode_www_form_component encoded } } bm.report("CGI.unescape") { manytimes { CGI.unescape encoded } } end
Ruby 2.1
$ ruby --version ruby 2.1.10p492 (2016-04-01 revision 54464) [x86_64-darwin15.0] $ ruby decode_bench.rb Rehearsal ----------------------------------------------------------------- URI.decode 2.360000 0.020000 2.380000 ( 2.404751) URI.decode_www_form_component 1.270000 0.010000 1.280000 ( 1.467851) CGI.unescape 1.480000 0.020000 1.500000 ( 1.578028) -------------------------------------------------------- total: 5.160000sec user system total real URI.decode 2.570000 0.030000 2.600000 ( 2.729236) URI.decode_www_form_component 1.170000 0.010000 1.180000 ( 1.189314) CGI.unescape 1.510000 0.010000 1.520000 ( 1.577981)
遅いし obsolete だし、URI.decode はやめておいた方がよさそうだ。 singleton method URI.decode (Ruby 2.4.0)
URI.decode_www_form_component
を使うのがいいのかな?
Ruby 2.4
試しに Ruby 2.4 でベンチとってみたら、思わぬ結果になった。
$ ruby --version ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin15] $ ruby decode_bench.rb Rehearsal ----------------------------------------------------------------- URI.decode 2.290000 0.010000 2.300000 ( 2.334818) URI.decode_www_form_component 1.280000 0.020000 1.300000 ( 1.330829) CGI.unescape 0.120000 0.000000 0.120000 ( 0.133207) -------------------------------------------------------- total: 3.720000sec user system total real URI.decode 2.560000 0.030000 2.590000 ( 2.859363) URI.decode_www_form_component 1.270000 0.010000 1.280000 ( 1.329829) CGI.unescape 0.120000 0.000000 0.120000 ( 0.127875)
CGI.unescape
が10倍以上速くなっている。
Ruby 2.4 から CGI モジュールのいくつかのメソッドが C拡張として実装されたらしい。
escape/unescape, escapeHTML/unescapeHTML 辺りが対象かな?
ruby/escape.c at trunk · ruby/ruby · GitHub
というわけで
URL decode したい時は Ruby 2.4 にして CGI.unescape
を使うと速いよ!ということでした。
Project Euler - Problem 10
もろもろの合間にちょっとした体操。
http://projecteuler.net/problem=10
200万までの素数の合計を求める。
単に素数リストを求めて合計すればいいだけ。
組み込みクラス使ってもいいけど、自前で実装する。
#!/usr/bin/env ruby class Prime def list(max) # Eratosthenes list = (3..max).step(2).to_a result = [2] while(list.last > result.last ** 2) do result << list.shift list.reject! {|n| n % result.last == 0} end result + list end end # sum of primes max = ARGV[0].to_i # 2000000 p Prime.new.list(max).reduce(&:+)
$ ruby -v
ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux]$ time ruby p10.rb 2000000
ruby p10.rb 2000000 469.85s user 1.62s system 99% cpu 7:53.67 total
reject の部分で素数の倍数を落としているが、件数が多すぎて時間がかかってしまう。
リストにして一気に落とした方が早いか。
#!/usr/bin/env ruby class Prime def list(max) # Eratosthenes list = (3..max).step(2).to_a result = [2] while(list.last > result.last ** 2) do result << list.shift list -= (result.last..list.last).step(result.last).to_a end result + list end end # sum of primes max = ARGV[0].to_i # 2000000 p Prime.new.list(max).reduce(&:+)
$ time ruby p10.rb 2000000
ruby p10.rb 2000000 12.65s user 0.23s system 99% cpu 12.914 total
arrayが演算できる言語だからできる手抜き。
Cでやると面倒そうだなあ。
メソッドの呼び出し方
PHPでは、static宣言なしでもメソッドのstatic呼び出しができてしまう。
で、どっちが速いの?という話。
メモリの使用量も比べてみたかったけど、速さのみ。
<?php require_once 'Benchmark/Timer.php'; class MyTest { public function myMethod() { return; } public static function staticMethod() { return; } } $max = 100000; $timer = new Benchmark_Timer(); $t = new MyTest(); $timer->start(); for($i = 0; $i < $max; $i++) { MyTest::staticMethod(); } $timer->setMarker('static'); for($i = 0; $i < $max; $i++) { MyTest::myMethod(); } $timer->setMarker('static2'); for($i = 0; $i < $max; $i++) { $t->myMethod(); } $timer->setMarker('instance'); $timer->stop(); $timer->display(); /* ------------------------------------------------------ marker time index ex time perct ------------------------------------------------------ Start 1293934691.81032700 - 0.00% ------------------------------------------------------ static 1293934691.85720200 0.046875 21.74% ------------------------------------------------------ static2 1293934691.99593900 0.138737 64.35% ------------------------------------------------------ instance 1293934692.02592700 0.029988 13.91% ------------------------------------------------------ Stop 1293934692.02593700 0.000010 0.00% ------------------------------------------------------ total - 0.215610 100.00% ------------------------------------------------------ */
$ php --version PHP 5.3.3 (cli) (built: Aug 22 2010 19:41:55)
static宣言なしのメソッドをstaticに呼ぶのは遅いってことかしら。
ちなみにrubyだとこうなった。
static methodは存在しないので、class method。このへんはうろ覚えなので、ちょっと自信ない・・・
require 'benchmark' class MyTest def myMethod 1 + 2 end def self.classMethod 1 + 2 end end module MyModule def moduleMethod 1 + 2 end end module MyModule2 def moduleClassMethod 1 + 2 end end max = 100000; Benchmark.bmbm(10) do |x| t = MyTest.new x.report("instance: ") { max.times do t.myMethod end } x.report("class : ") { max.times do MyTest::classMethod end } MyTest.class_eval { include MyModule } t = MyTest.new x.report("module : ") { max.times do t.moduleMethod end } MyTest.extend MyModule2 x.report("module class : ") { max.times do MyTest::moduleClassMethod end } end
Rehearsal --------------------------------------------------- instance: 0.040000 0.000000 0.040000 ( 0.043314) class : 0.050000 0.000000 0.050000 ( 0.049375) module : 0.040000 0.000000 0.040000 ( 0.042887) module class : 0.050000 0.000000 0.050000 ( 0.045320) ------------------------------------------ total: 0.180000sec user system total real instance: 0.040000 0.000000 0.040000 ( 0.042020) class : 0.040000 0.000000 0.040000 ( 0.046069) module : 0.040000 0.000000 0.040000 ( 0.041350) module class : 0.040000 0.000000 0.040000 ( 0.045474)
$ ruby --version ruby 1.8.7 (2010-08-16 patchlevel 302) [i686-darwin10]
複数のRubyを切り替えられるようにする
Ruby1.9をメインで使いつつ、1.8系も準備しておきたかったので併存させられる環境を作ってみた。
OS: Ubuntu 9.04 Server Edition
Rubyのソースを用意する
Subversionで落としてくる。当然trunk。安定とか知りません。
$ svn co http://svn.ruby-lang.org/repos/ruby/trunk ruby
$ svn co http://svn.ruby-lang.org/repos/ruby/branches/ruby_1_8 ruby1.8
必要なライブラリを入れる
OpenSSLとconfigure作成用のautoconf
bisonがどうとかいうエラーが出るので、それも入れておく
$ sudo apt-get install libssl-dev $ sudo apt-get install autoconf $ sudo apt-get install bison
makeする
/local/ruby の下にバージョン番号のディレクトリを作成して、そこに入れる。
プログラムにはバージョン番号を付けておく
$ cd ruby $ autoconf $ ./configure --prefix=/local/ruby/<VERSION> --program-suffix=<VERSION> --with-readline-dir=/local/ruby/<VERSION> $ make $ make test $ sudo make install
シンボリックリンクを張る
aliasを付けてもいいけど、gemとか他のプログラムが /usr/bin/ruby とかを見ている場合があるので、シンボリックリンクを作っておく。
投げやりなシェルスクリプトにしてみる。
$ less switch_ruby.sh #!/bin/sh ORG_PATH=/local/ruby/$2/bin PREFIX=/usr/local/bin for NAME in ruby gem irb rdoc ri do ln -s $ORG_PATH/$NAME$1 $PREFIX/$NAME echo "/usr/bin/$NAME linked @ $ORG_PATH/$NAME$1" done $ ./switch_ruby.sh 1.9 1.9.1
Mitaka.rb に行ってきた
4/15 に吉祥寺で開かれた、Mitaka.rb設立総会に行ってきました。
一次会
会場がヴィレッジバンガードダイナーだったこともあり、外国ビールが飲み放題!!
Poken共同購入の受け渡しをしてもらったのもあり、Poken充してました。はいふぉーはいふぉー。
その後は、散々に飲み食いしながらおしゃべりを楽しみました。
結構「仕事でRuby/Rails使ってます」という人が多くてびっくり!うらやましい・・・
二次会
カレー屋に移動してさらにしゃべくる。今度販売予定のiPhoneアプリについて話したり遊んでたり、
逆側でid:t-wada さんがBDDやテストツールについて話してたりしてカオス。
iPhoneアプリ開発やってる人が3〜4人いて、またまたびっくり。
お疲れ様でした
幹事のid:onering さん、Pokenの共同購入を主催してくださったid:conceal-rs さん、お話してくださった皆様ありがとうございました!
またよろしくお願いします。
番外編
id:akasata さんに、著書の「実践バグ管理」を頂きました。ありがとうございます!
早速読んで、初書評とかやってみようと思います*1。
まだぱらぱらと見た限りですが、バグ管理にとどまらず、システム開発やレポートの書き方などの様々なノウハウが詰まっている感じです。
読むのが楽しみー。
*1:自分でハードルを上げてみる
ifもcaseも使わずに条件分岐
再び、λ楽しいよラムダの時間がやってまいりました。
true=λx.λy.x
false=λx.λy.yそうすると、変数bがtrueかfalseをとるとして
b(0)(1)
とすると、bがtrueのとき0、falseのとき1を返します。
λで条件分岐ができた!
というわけで、λで条件分岐ができると聞いて、早速やってみた。
できれば、別関数を呼ばずにすませたい!ということで・・・
true/falseの代わりに関数を返すようにする
数字を比較した結果に応じて、値を返す。
#!/opt/local/bin/ruby1.9 class Fixnum @@tr = ->(x){ ->(y){ x }} @@fl = ->(x){ ->(y){ y }} def compare(n, arr) arr[self <=> n] end def ==(n) compare n, [@@tr, @@fl, @@fl] end def >(n) compare n, [@@fl, @@tr, @@fl] end def >=(n) compare n, [@@tr, @@tr, @@fl] end def <(n) compare n, [@@fl, @@fl, @@tr] end def <=(n) compare n, [@@tr, @@fl, @@tr] end end puts (3 == 5).('foo').('bar') #=> bar puts (3 == 3).('foo').('bar') #=> foo puts (3 < 8).('foo').('bar') #=> foo puts (3 > 8).('foo').('bar') #=> bar
できた。けど・・・
FloatやStringとか色々なところでこれを書くのは面倒。
true/falseを書き換えてしまおう。
true/falseが関数を返すようにする
といっても、true/falseはTrueClass, FalseClassのインスタンス。
Classごと書き換えるのはちょっとなーってことで、ちょっと逃げてみた。
class Proc def [](n=nil) self.call n end end class TrueClass def [](n) ->(x){ ->(y){ x }}[n] end end class FalseClass def [](n) ->(x){ ->(y){ y }}[n] end end tr = ->{puts 'this is true.'} fl = ->{puts 'this is false.'} puts (('hoge' == 'hogo')[tr][fl]).() #=> this is false.
比較の結果返されたtrue, falseの[]メソッドを呼ぶと、それぞれを表す関数が返される。
呼び出しが()じゃなくなるのが汚いけど、()は定義できないみたい。
結論
ifもcaseも使わずに、条件分岐ができた!
もっと()だけで済ませられる方法とかないかなー。*1