Ruby の HTTP リクエストを送る方法の性能比較

HTTP リクエストの性能比較をさまざまな Gem などで行ってみた。

### net-http
一番標準的な Net::HTTP を使ってみる。

```rb:net-http.rb
require "net/http"

100.times do
  res = Net::HTTP.start("www.yahoo.co.jp") do |http|
    http.get "/"
  end
  res.body
end
```

### open-uri
みんな大好き open-uri も試してみる。

```rb:open-uri.rb
require "open-uri"

100.times do
   body = open("http://yahoo.co.jp", &:read)
end
```

### persistent_http
[persistent_http](https://github.com/bpardee/persistent_http) を使ってみた。

```rb:persistent-http.rb
require "persistent_http"

http = PersistentHTTP.new(
  :name => "MyHTTPClient",
  :pool_size => 3,
  :pool_timeout => 5,
  :warn_timeout => 0.25,
  :force_retry => true,
  :url => "http://www.yahoo.co.jp/"
)

100.times do
  response = http.request
  response.body
end
```

### Net::HTTP （オブジェクト再利用）
標準ライブラリの Net::HTTP でオブジェクトを再利用したバージョン。

```rb:persistent-net-http.rb
require "net/http"

Net::HTTP.start("www.yahoo.co.jp") do |http|
  100.times do
    res = http.get "/"
    res.body
  end
end
```

### wget
意表を突いて、 Open3 + wget も試してみるテスト。

```rb:wget.rb
require "open3"

100.times do
  stdout, stderr, exitcode = Open3.capture3("wget -d -O - http://www.yahoo.co.jp")
end
```

### excon
[excon](https://github.com/excon/excon) を使ってみた。
リポジトリ中に Benchmark の実装もあるが、気にせず、独自に検証。

```rb:excon.rb

require "excon"

connection = Excon.new("http://www.yahoo.co.jp", persistent: true)

100.times do
  get_response = connection.get(path: "/")
  get_response.body
end
```
### curb

libcurl を使った実装の１つ [curb](https://github.com/taf2/curb) を使ってみた。
今回の実装では凝ったことをしてみた。

```rb:curb.rb
require "curb"

curl = Curl::Easy.new

urls = ["http://www.yahoo.co.jp/"] * 100

multi = Curl::Multi.new

responses = Hash.new do |hash, k|
  hash[k] = ""
end

urls.each do |url|
  curl = Curl::Easy.new url do |easy|
    easy.on_body do |data|
      responses[url] << data; data.size
    end
  end
  multi.add curl
end

multi.perform do
end

urls.each do |url|
  responses[url]
end
```
### em-synchrony
EventMachine を使った実装の１つ [em-synchrony](https://github.com/igrigorik/em-synchrony) を使ってみた。

```rb:em-synchrony.rb
require "em-synchrony"
require "em-synchrony/em-http"
require "em-synchrony/fiber_iterator"

urls = [ "http://www.yahoo.co.jp/" ] * 100

responses = Enumerator.new do |y|
  EM.synchrony do
    concurrency = 1

    EM::Synchrony::FiberIterator.new( urls, concurrency ).each do |url|
      y << [ url, EventMachine::HttpRequest.new(url).get ]
    end
    EventMachine.stop
  end
end

responses.each do |url, response|
end
```

### typhoeus
libcurl を使った実装の１つ[typhoeus](https://github.com/typhoeus/typhoeus) を使ってみた。

```rb:typhoeus
require "typhoeus"

100.times do
  Typhoeus.get("www.yahoo.co.jp/")
end
```
### httparty
なんとなく楽しそうな印象の HTTParty も使ってみた。

```rb:httparty.rb
require "httparty"

100.times do
  response = HTTParty.get('http://www.yahoo.co.jp/')
end
```


## 計測結果
実行時間は time コマンドで測定。

| ファイル名 | 実行時間 |
|:---|---:|
| net-htp.rb | 11.712s |
| open-uri.rb | 22.079s |
| persistent-http.rb | 17.293s |
| persistent-net-htp.rb | 16.401s |
| wget.rb | 14.450s |
| excon.rb | 25.299s |
| curb.rb | 20.234s |
| em-synchrony.rb | 8.906s |
| typhoeus.rb | 8.909s |
| httparty.rb | 12.472s |

Faraday 系の Gem は内部の Gem を直接使う場合に
比べて遅そうなので、試していません。

## 考察
たとえ concurrency が 1 でも、EventMachine を使う em-synchrony が最速という結果は少し意外でした。

実行タイミングによる差も非常に大きく、時間帯によっては上記の 2倍くらいの時間となることもあるので、上記の結果の信頼性はかなり低いです。
また www.yahoo.co.jp ではなく、もっと違う WEB サーバで試すべきかもしれません。
ただし、やっていることはほとんどアタックなので、自重しましょう。

私の中では

* 安定した実行性能（実行時間の標準偏差が小さいこと）
* 手馴れていること
* 異常時のデバッグログ取得などもやりやすいこと
* コード量も短くなること

などから wget を Open3 で実行した結果を使うことにしました。

ちゃんちゃん。

## 追記
コメントをいただいたので、追加で typhoeus を試してみました。さらについでに HTTParty も試してみました。

typhoeus は速いですね。
機能も豊富そうな上に API も簡単で試してみる価値ありそうです。