Index: [Article Count Order] [Thread]

Date: Thu, 11 Nov 2004 19:54:18 +0900
From: Satoru Takabayashi <satoru@namazu.org>
Subject: [webrickja:130] WEBrick::HTMLUtils.escape
To: webrickja@notwork.org
Message-Id: <87sm7g64h1.wl@namazu.org>
X-Mail-Count: 00130

WEBrick::HTMLUtils.escape のコードを見て、

      str.gsub!(/&/n, '&amp;')
      str.gsub!(/\"/n, '&quot;')
      str.gsub!(/>/n, '&gt;')
      str.gsub!(/</n, '&lt;')

のように 4回の gsub! を呼ぶより

    EscapeTable = {
      "&" => "&amp;",
      '"' => "&quot;",
      '<' => "&lt;",
      '>' => "&gt;",
    }

のようなテーブルを作っておいて

  str.gsub(/[&"<>]/n) {|x| EscapeTable[x] }

のように変換した方が多少速いかも? と思い、試してみました。

以下は &"<> が含まれる割合を 0, 0.1, 0.2, 0.5, 1, 10, 100パー
セントで変化させて計測した結果です。

&"<> の出現頻度が 0〜0.5%と少ないときはテーブル式の方が速く、
出現頻度が 1%を越えるときは gsub! 連発式の方が速いようです。

  % ruby -v
  ruby 1.8.2 (2004-11-03) [i386-linux]

  % ruby escape-html.rb
        user     system      total        real
  percentage = 0.00
    0.250000   0.010000   0.260000 (  0.261090)
    0.060000   0.000000   0.060000 (  0.059014)

  percentage = 0.10
    0.320000   0.000000   0.320000 (  0.319485)
    0.100000   0.000000   0.100000 (  0.102455)

  percentage = 0.20
    0.300000   0.020000   0.320000 (  0.326251)
    0.130000   0.010000   0.140000 (  0.139616)

  percentage = 0.50
    0.370000   0.000000   0.370000 (  0.368818)
    0.240000   0.030000   0.270000 (  0.273462)

  percentage = 1.00
    0.430000   0.000000   0.430000 (  0.430216)
    0.470000   0.020000   0.490000 (  0.486549)

  percentage = 10.00
    1.510000   0.020000   1.530000 (  1.525060)
    4.030000   0.450000   4.480000 (  4.486088)

  percentage = 100.00
   12.340000   0.000000  12.340000 ( 12.340990)
   39.520000   3.840000  43.360000 ( 43.360325)


&"<> がそれほど多く出現しないテキストでは、テーブル式の方が
速く、自分の用途ではその方が適していたので、
WEBrick::HTMLUtils.escape の代わりに、テーブル式を用意して使っ
ています。

WEBrick::HTMLUtils.escape をテーブル式に変更した方がいいかと
いうと、微妙ですね。かといって、2種類の方式を用意するのも、
やりすぎな気がします。

結論らしいものはありませんが、FYI ということで。



require 'benchmark'
def escape_by_gsubs (string)
  str = string ? string.dup : ""
  str.gsub!(/&/n, '&amp;')
  str.gsub!(/\"/n, '&quot;')
  str.gsub!(/>/n, '&gt;')
  str.gsub!(/</n, '&lt;')
  str
end

EscapeTable = {
  "&" => "&amp;",
  '"' => "&quot;",
  '<' => "&lt;",
  '>' => "&gt;",
}

def escape_by_hash (string)
  str = string ? string : ""
  str.gsub(/[&"<>]/n) {|x| EscapeTable[x] }
end

def make_test_string (percentage)
  ratio = percentage.to_f / 100
  len = 10000
  string = "o" * (len * (1 - ratio))
  string << (0...(len - string.length)).collect { '<&">'[rand(4)].chr }.join
  string.split(//).sort_by { rand }.join
end


Benchmark.bm {|x|
  [0, 0.1, 0.2, 0.5, 1, 10, 100].each {|percentage|
    str = make_test_string(percentage)
    n = 1000
    printf("percentage = %.2f\n", percentage)
    x.report { n.times { escape_by_gsubs(str) } }
    x.report { n.times { escape_by_hash(str) } }
    puts
  }
}