MessagePack-RPCでバイナリデータを扱うときにUTF-8のままsliceすると死ぬほど遅いのでforce_encodingしよう(Ruby 1.9)
Ruby 1.9から、文字列にそのエンコーディングが含まれるようになった。また、MessagePack-RPCでは、Encoding::ASCII_8BITの文字列を送っても、受け手側ではEncoding::UTF_8になってしまうという仕様がある。
そのため、受け取ったデータをそのままsliceしたりすると非常に遅い。あるオフセットからのデータを取り出したいときなどに顕著となる。
以下実験。
(UTF-8の"あ")*1024 というデータ(3072バイト)を2バイトずつに切り出していく処理。
これをRuby 1.8.7で実行すると:
version: 1.8.7 0.505368 sec ←
となるが、1.9.2で実行すると:
version: 1.9.2 by UTF-8: data_utf8.encoding: UTF-8 data_utf8.length: 1024 1.894018 sec ← converting by force-encoding 0.000003 sec data_ascii.encoding: ASCII-8BIT data_ascii.length: 3072 by ASCII: 0.207381 sec
と、3〜4倍遅くなる。コレを防ぐには、str.force-encoding(Encoding::ASCII_8BIT)してやればよいようだ。
実験コードは以下の通り:
#!/bin/env ruby # -*- encoding: UTF-8 def bench_time before = Time.now yield printf(" %8.6f sec\n", Time.now - before) end def do_bench(data) bench_time do (1024*1024).times do |i| data.slice(i*2, 2) end end end # base data data_utf8 = "あ" * 1024 puts "version: #{RUBY_VERSION}" if RUBY_VERSION < "1.9.0" do_bench(data_utf8) exit 0 end # ruby >= 1.9 puts "by UTF-8:" puts "data_utf8.encoding: #{data_utf8.encoding}" puts "data_utf8.length: #{data_utf8.length}" do_bench(data_utf8) puts " converting by force-encoding" data_ascii = nil bench_time do data_ascii = data_utf8.force_encoding(Encoding::ASCII_8BIT) end puts "data_ascii.encoding: #{data_ascii.encoding}" puts "data_ascii.length: #{data_ascii.length}" puts "by ASCII:" do_bench(data_ascii)