激しく流行に乗り遅れた感がありますが、LTSVを調べてみました。
- 文字列分割
- 正規表現マッチ
- StringScanner
のそれぞれでLTSVをパースした時のベンチマークを取ってみました。
というか、なんで文字列分割してるの、StringScannerが速いんじゃないの、という思いがあったのでした。
比較が大事だろうということでマシン性能とかは省略。
Ruby 1.9.3です。
require 'benchmark'
logline = 'time:10/Feb/2013:00:22:43 +0900 host:127.0.0.1 request:GET / HTTP/1.1 status:200 size:133 referer:- ua:Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:18.0) Gecko/20100101 Firefox/18.0 reqtime:0.000 upsttime:-'
logfile = 'log.ltsv'
open(logfile, 'w') do |file|
100000.times do
file.puts logline
end
end
TAB = "\t"
COLON = ':'
NL = "\n"
def parse_line_by_split(line)
pairs = line.split(TAB).map {|pair| pair.split(COLON, 2)}
end
def parse_line_by_regexp(line)
matched = (/((?<key>[^#{COLON}]*):(?<value>[#{TAB}]*)\t*)*/o).match(line)
end
def parse_line_by_strscan(line)
require 'strscan'
scanner = StringScanner.new(line)
while key = scanner.scan(/[^#{COLON}]*/o)
scanner.scan(/#{COLON}/o)
value = scanner.scan(/[^#{TAB}]*/o)
scanner.scan(/#{TAB}/o)
return if scanner.eos?
end
end
Benchmark.bmbm do |x|
[:parse_line_by_split, :parse_line_by_regexp, :parse_line_by_strscan].each do |method|
x.report method do
open logfile do |file|
file.each_line do |line|
send method, line
end
end
end
end
end
$ ruby bench.rb
Rehearsal ---------------------------------------------------------
parse_line_by_split 0.750000 0.010000 0.760000 ( 0.751666)
parse_line_by_regexp 0.800000 0.010000 0.810000 ( 0.811203)
parse_line_by_strscan 5.990000 2.300000 8.290000 ( 8.633933)
------------------------------------------------ total: 9.860000sec
user system total real
parse_line_by_split 0.760000 0.010000 0.770000 ( 0.768746)
parse_line_by_regexp 0.820000 0.010000 0.830000 ( 0.832008)
parse_line_by_strscan 6.170000 2.100000 8.270000 ( 8.602963)
ありゃ。
まあでも、上のスクリプトは一行一行をメソッドに送り込んで、その度にStringScanner
オブジェクトを作っているからオーバーヘッドでかいよね。
リアルタイムでログを解析する分にはこういう風になるんだと思うけど、既存のログを解析する時にはStringScanner
活躍するんじゃない?
def parse_by_split(filename)
result = File.read(filename).split(NL).map {|line|
line.split(TAB).map {|pair| pair.split(COLON, 2)}
}
end
def parse_by_regexp(filename)
matched = (/(^((?<key>[^#{COLON}]*):(?<value>[^#{TAB}]*)#{TAB}*)*$)*/mo).match(File.read(filename))
end
def parse_by_strscan(filename)
require 'strscan'
scanner = StringScanner.new(File.read(filename))
while key = scanner.scan(/[^#{COLON}]*/o)
# p key
scanner.scan(/#{COLON}/o)
value = scanner.scan(/[^#{TAB}]*/o)
# p value
scanner.scan(/[#{TAB}$]/)
return if scanner.eos?
end
end
Benchmark.bmbm do |x|
[:parse_by_split, :parse_by_regexp, :parse_by_strscan].each do |method|
x.report method do
send method, logfile
end
end
end
Rehearsal ----------------------------------------------------
parse_by_split 0.820000 0.060000 0.880000 ( 0.874224)
parse_by_regexp 0.780000 0.260000 1.040000 ( 1.059403)
parse_by_strscan 4.960000 0.030000 4.990000 ( 5.001664)
------------------------------------------- total: 6.910000sec
user system total real
parse_by_split 0.750000 0.000000 0.750000 ( 0.755708)
parse_by_regexp 0.620000 0.250000 0.870000 ( 0.890210)
parse_by_strscan 5.220000 0.030000 5.250000 ( 5.245181)
ええっ、そうなの……。