最近の好きなメソッド

今更ながら、injectから、Symbol.to_procが急浮上しております。

あと、最近やっとsort_byなんてメソッドがあることに気付きました。気付いたきっかけというのが、この間の松江ruby会議のUstで行われていたパネラーによるコミット祭りで"sort_by_bangコミットしちゃおうかな"発言が飛び交っていたので気付きました。ちなみに、Rubyのソースでhoge_fuga_bangというのは、大抵破壊的なメソッドの実装のときにつかわれております。というわけで、Rubyのさきっちょにはsort_by!というメソッドができているはずです。

以前から、ログファイルを解析するときに、ログ1行分を表すクラスを作って、それぞれを配列にいれてソートして眺めるときに<=>をオーバーライドするのがめんどくさいなぁと思っていたんですが、1.9なら(1.8.7もですが)こうかけることにきづいた

class Hoge; attr_accessor :i;end
(1..10).{|e| i=Hoge.new; i.i=e}.sort_by(&:i)

で、sort_byの説明をみると、こうかいてあった instance method Enumerable#sort_by

Enumerable#sort と比較して sort_by が優れている点として、
比較条件が複雑な場合の速度が挙げられます。 sort_by を使わない
以下の例では比較を行う度に downcase が実行されます。従って 
downcase の実行速度が遅ければ sort の速度が致命的に低下します。 

というわけで、単純に Fixnum のインスタンス変数を文字列にしてから比較するやりかたと、そのまま比較するやりかたで時間をとってみた

#! /opt/local/bin/ruby1.9
#require 'profiler'

class Hoge
	def initialize(i)
		@i = i
	end
	def i()
		@i.to_s
	end
	def <=>(o)
		i <=> o.i
	end
end

def prof_sort(ary)
	puts "sort"
#Profiler__.start_profile
	s = Time.now
	ary.sort{|a,b| a.i <=> b.i}
	e = Time.now
	p (e - s)
#Profiler__.print_profile(STDOUT)

	puts "sort_by Symbol.to_proc"
#Profiler__.start_profile
	s = Time.now
	ary.sort_by(&:i)
	e = Time.now
	p (e - s)
#Profiler__.print_profile(STDOUT)

	puts "sort_by"
#Profiler__.start_profile
	s = Time.now
	ary.sort_by{|e| e.i}
	e = Time.now
	p (e - s)
#Profiler__.print_profile(STDOUT)

	puts "<=> override"
#Profiler__.start_profile
	s = Time.now
	ary.sort
	e = Time.now
	p (e - s)
#Profiler__.print_profile(STDOUT)
end

ary = (1...1000000).map{|e| Hoge.new e}
puts "method i is to_s"
prof_sort ary
class Hoge
	def i()
		@i
	end
end
puts "method i is fixnum"
prof_sort ary

Rubyのaryはqsortで実装されているので、Rangeクラスから作った配列をソートなんてすると、簡単に最悪ケースにはまってくれる。で、実行結果。

takkanm% ruby1.9 sort_prof.rb
method i is to_s
sort
27.680841
sort_by Symbol.to_proc
3.323916
sort_by
3.3218
<=> override
25.805198
method i is fixnum
sort
0.540341
sort_by Symbol.to_proc
0.457979
sort_by
0.589265
<=> override
0.54745

普通にfixnumで比較すると変化はないみたいですけど、そこで変換を行ったりするとかなり遅くなりますね。

念のため、プロファイラmodule Profiler__もとってみようと思ったけど、全然一つめのメソッド自体が返ってこない。なんだこれ。