Railsのurl_forを30%高速化するgemを作った

まあ、タイトルは若干釣りで、特定のユースケースにおいて3割程度の高速化が見込める、というだけです。

[joker1007/curl_escape: This gem provides fast URL escape by libcurl.](https://github.com/joker1007/curl_escape "joker1007/curl_escape: This gem provides fast URL escape by libcurl.")

以下、実装経緯。

Railsの`url_for`を辿っていくと、最終的にクエリパラメーターとして渡したハッシュやらArrayやらに対して、`Object#to_query`を発行します。
この`Object#to_query`の実装は、ほぼ`CGI.escape`の実行です。
やたらと`link_to`の数が多くて、クエリパラメーターをそれなりに渡している、というページを表示しようとすると、割とこの処理時間が馬鹿にならない感じになってきます。

ということで、`CGI.escape`を早くすれば、ちょっとはマシになるんじゃないかと思いました。
しかし、`CGI.escape`の実装をRubyのままで今以上早くするのはちょっと難しそうでした。
なので、いっそCで書いてしまえという[SamSaffron/fast_blank](https://github.com/SamSaffron/fast_blank "SamSaffron/fast_blank")と同じ発想でいくことにしました。
文字コードとか考慮してURLをエスケープするのはかなり辛そうだったので、実装はlibcurlに丸投げして、`CGI.escape`の仕様に合わせて文字列を適当に調整して終わり、というお手軽gemなので、一晩で適当にでっち上げました。
C拡張のパワーで`CGI.escape`と同等の処理が5倍強の速度で実行できるようになったので、後はモンキーパッチで`CGI.escape`の実装をC拡張側に差し替えればオーケーです。

ベンチマーク結果は以下の様になってます。

```ruby
require 'benchmark/ips'
require 'cgi'

$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
require_relative './lib/curl_escape'

Benchmark.ips do |x|
  x.config(time: 15, warmup: 5)
  x.report("cgi_escape") { CGI.escape("'Stop!' said Fred") }
  x.report("curl_escape") { CurlEscape.escape("'Stop!' said Fred") }
  x.compare!
end
```

```
Calculating -------------------------------------
          cgi_escape    13.791k i/100ms
         curl_escape    52.251k i/100ms
-------------------------------------------------
          cgi_escape    130.471k (±17.8%) i/s -      1.889M
         curl_escape    832.501k (± 5.6%) i/s -     12.488M

Comparison:
         curl_escape:   832501.4 i/s
          cgi_escape:   130470.6 i/s - 6.38x slower
```

### `url_for` benchmark
```
irb(main):005:0> app.dashboard_app_path("hogehoge", {foo: "bar", hoge: "aa", aaa: "aaa", bbb: "bbb", hogehoge: "%&[12]"})
=> "/apps/hogehoge/dashboard?aaa=aaa&bbb=bbb&foo=bar&hoge=aa&hogehoge=%25%26%5B12%5D"
irb(main):006:0> Benchmark.ips do |x|
irb(main):007:1*   x.config(time: 15, warmup: 5)
irb(main):008:1>   x.report("url_for") { app.dashboard_app_path("hogehoge", {foo: "bar", hoge: "aa", aaa: "aaa", bbb: "bbb", hogehoge: "%&[12]"}) }
irb(main):009:1> end
Calculating -------------------------------------
             url_for   922.000  i/100ms
-------------------------------------------------
             url_for      9.038k (±19.5%) i/s -    129.080k
=> #<Benchmark::IPS::Report:0x007fe017c73a68 @entries=[#<Benchmark::IPS::Report::Entry:0x007fe01b53b9b8 @label="url_for", @microseconds=15008895.1587677, @iterations=129080, @ips=9037.936272156061, @ips_sd=1764, @measurement_cycle=922, @show_total_time=false>], @data=nil>
irb(main):010:0> require 'curl_escape/core_ext/cgi'
=> true
irb(main):011:0> app.dashboard_app_path("hogehoge", {foo: "bar", hoge: "aa", aaa: "aaa", bbb: "bbb", hogehoge: "%&[12]"})
=> "/apps/hogehoge/dashboard?aaa=aaa&bbb=bbb&foo=bar&hoge=aa&hogehoge=%25%26%5B12%5D"
irb(main):012:0>
irb(main):013:0* Benchmark.ips do |x|
irb(main):014:1*   x.config(time: 15, warmup: 5)
irb(main):015:1>   x.report("url_for") { app.dashboard_app_path("hogehoge", {foo: "bar", hoge: "aa", aaa: "aaa", bbb: "bbb", hogehoge: "%&[12]"}) }
irb(main):016:1> end
Calculating -------------------------------------
             url_for     1.192k i/100ms
-------------------------------------------------
             url_for     12.170k (±14.2%) i/s -    177.608k
=> #<Benchmark::IPS::Report:0x007fe01b4c8da0 @entries=[#<Benchmark::IPS::Report::Entry:0x007fe015863560 @label="url_for", @microseconds=15003959.655761719, @iterations=177608, @ips=12170.236970422775, @ips_sd=1723, @measurement_cycle=1192, @show_total_time=false>], @data=nil>
```

`url_for`はクエリパラメーターがそこそこ渡されているなら30%ぐらい早くなります。
また、エスケープ対象の文字列が多い程、相対的に早くなります。
なので、クエリパラメーターの多い検索ファセットの`link_to`などには多少効果があると思います。
マトリックステーブルの各マスにクエリパラメーターがそこそこ乗った`link_to`が存在するようなページだと、大体50ms〜70msぐらいレンダリングが早くなりました。
gem突っ込むだけで高速化できる手段としては悪くないような気がします。