日記帳

日記です。

Rubyと遅延初期化

そういえばベンチマークは公開してなかったと思うので置いておきます.

ソースコード

# 手軽にソースコードを置いておける場所が欲しいなぁ.

# No.0
class NormalHello
	def initialize
		@message = Time::now.strftime("Hello @ %Y-%m-%d")
	end

	attr_reader :message
end

# No.1
class LaterInitializationHello
	def message
		unless @message then
			@message = Time::now.strftime("Hello @ %Y-%m-%d")
		end
		@message
	end
end

# No.2
class LaterInitializationHello2
	def message
		@message ||=Time::now.strftime("Hello @ %Y-%m-%d")
		@message
	end
end

# No.3
class LaterInitializationSelfOverrideHello
	def message
		unless @message then
			@message = Time::now.strftime("Hello @ %Y-%m-%d")
		end
		def self.message
			@message
		end
		@message;
	end
end

# No.4
class LaterInitializationSelfOverrideUsingAttrReaderHello
	attr_reader :message
	alias :message_by_attr_reader :message
	def message
		unless @message then
			@message = Time::now.strftime("Hello @ %Y-%m-%d")
		end
		class << self
			alias :message :message_by_attr_reader 
		end
		@message;
	end
end

class Module
	alias original_attr_reader attr_reader

	def attr_reader(*names)
		unless block_given?
			return original_attr_reader(*names)
		end

		names.each do |name|
			ivar = "@#{name}"
			define_method(name) do
				value = instance_variable_get(ivar)
				unless value
					value = yield self
					instance_variable_set(ivar, value)
				end
				class << self; self; end.class_eval do
					original_attr_reader name
				end
				value
			end
		end
	end
end

# No.5
class LaterInitializationSelfOverrideUsingAttrReaderDefinedByMethodHello
	attr_reader(:message) {
		Time::now.strftime("Hello @ %Y/%m/%d")
	}
end

if $0 == __FILE__ then
	require 'benchmark'

	LOOP_COUNT= 4000000

	targets = []
	targets << NormalHello::new
	targets << LaterInitializationHello::new
	targets << LaterInitializationHello2::new
	targets << LaterInitializationSelfOverrideHello::new
	targets << LaterInitializationSelfOverrideUsingAttrReaderHello::new
	targets << LaterInitializationSelfOverrideUsingAttrReaderDefinedByMethodHello::new

	Benchmark.bm() { |x|
		targets.each_with_index(){ |obj, index|
			x.report(index.to_s){
				LOOP_COUNT.times{
					obj.message
				}
			}	
		}
	}
end

実行結果

      user     system      total        real
0  4.090000   0.930000   5.020000 (  5.012927)
1  7.640000   1.870000   9.510000 (  9.515122)
2  8.050000   2.050000  10.100000 ( 10.107144)
3  7.030000   1.840000   8.870000 (  8.874788)
4  3.950000   1.170000   5.120000 (  5.118873)
5  3.960000   1.080000   5.040000 (  5.040693)

当然遅延初期化なしのNo.0が一番速いですが最終案のNo.5との差は僅かです.

No.2はRubyで遅延初期化を書く場合の一般的なイディオムですが一番遅いです.一般的なイディオムを利用したコードは可読性が上ってよいのですが…