Ruby 1.9 だと String#sub 等にハッシュを渡せる

String#sub などの置換メソッドを何度も渡すと効率が問題になってくる。かといってブロックを渡すと ブロック呼び出しコストが高い(特に 1.8 )ため遅くなることがある。そこで、置換パターンをハッシュで渡せるようになった。これなら効率も上がる。

TABLE = { 'a' => 'A', 'b' => 'B' }
# 複数回の置換
"hagbx".gsub (/a/, 'A').gsub (/b/, 'B')  # => "hAgBx"
# ブロック置換
"hagbx".gsub (/[ab]/){|c| TABLE[c] }    # => "hAgBx"
# ハッシュ置換( Ruby 1.9 )
"hagbx".gsub (/[ab]/, TABLE)            # => "hAgBx"

で、ベンチマーク
Ruby 1.9 の String#inspect で JSON 形式への変換を高速化 - WebOS Goodies から拝借。

# -*- coding: euc-jp -*-
 
def profile (label)
  time = Time.now
  yield
  puts "#{label} : #{Time.now - time}\n"
end
 
ESCAPE_CONVERSION = { '\x' => '\u00', '\a' => '\u0007', '\v' => '\u000B', '\e' => '\u001B' }
fixture = <<EOS
\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f
\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f
\x22\x5c
EOS
 
profile ('inspect を使わない') do
  100000.times do
    str = fixture.to_s.encode ('UTF-8').gsub (/[^\x20-\x21\x23-\x5b\x5d-\xff]/n) do |chr|
      c = chr[0].ord
      if c != 0 && (index = "\"\\/\b\f\n\r\t".index (chr[0]))
        "\\" + '"\\/bfnrt'[index, 1]
      else
        sprintf ("\\u%04X", c)
      end
    end
  end
end
 
profile ('ブロックで置換') do
  100000.times do
    str = fixture.to_s.encode ('UTF-8').inspect
    str.gsub! (/\\[xave]/u){|s| ESCAPE_CONVERSION[s] }
  end
end
 
profile ('複数の gsub で置換') do
  100000.times do
    str = fixture.to_s.encode ('UTF-8').inspect
    str.gsub (/\\x/u, '\u00').gsub (/\\a/u, '\u0007').gsub (/\\v/u, '\u000B').gsub (/\\e/u, '\u001B')
  end
end
 
profile ('複数の gsub! で置換') do
  100000.times do
    str = fixture.to_s.encode ('UTF-8').inspect
    str.gsub! (/\\x/u, '\u00')
    str.gsub! (/\\a/u, '\u0007')
    str.gsub! (/\\v/u, '\u000B')
    str.gsub! (/\\e/u, '\u001B')
  end
end

# 追加!
profile ('ハッシュ引数で置換') do
  100000.times do
    str = fixture.to_s.encode ('UTF-8').inspect
    str.gsub! (/\\[xave]/u, ESCAPE_CONVERSION)
  end
end
# >> inspect を使わない : 21.437445578
# >> ブロックで置換 : 7.768467606
# >> 複数の gsub で置換 : 10.30494176
# >> 複数の gsub! で置換 : 10.731268803
# >> ハッシュ引数で置換 : 7.144320215

「ブロックで置換」よりも「ハッシュ引数で置換」のほうが少しだけ速い。置換パターンが増えてくるとかなり差がつくのかな…

俺のマシンは Pentium4 2.66GHz なのだが、 Core 2 Duo 2GHz よりちょっとばっかし遅いくらいだった。意外…

ていうか、 inspect をこういう目的で使うのはどうかと思うんだよなぁ。 inspect ってデバッグ目的の出力だから Ruby の仕様にフォーマットが定められているわけじゃないし、いつ変更されるか分からない。ちょっとリスキーかも…