読者です 読者をやめる 読者になる 読者になる

UUUM攻殻機動隊

UUUMのエンジニアによる技術ブログです

GoとNode.jsで作った簡易サーバーのパフォーマンスを比較してみた

Go JavaScript

エンジニアのタナカです。
UUUMではiOSアプリを開発をしています。

アプリを開発するにあたって、バックエンドに何を使えばいいか悩んだりしませんか? バックエンドを決めるには、様々な側面から考える必要があると思いますが、今回はパフォーマンスだけに着目して検討してみました。

ということでGoとNode.jsで簡易WEBサーバーを作って比較してみたいと思います。おまけでRubyも比較してみました。
パフォーマンスで考えた場合にRubyを選ぶことはあまりないとは思いますが、参考までに。

検証環境

  • Go go1.6.2 darwin/amd64
  • Node.js v5.10.1
  • Ruby ruby 2.2.4p230

※GOMAXPROCSは設定していません(マルチコアを使用できるGoが有利な環境)

検証コード

UNIX TIMEを返すだけの簡単なサーバーを用意しました。

Go

標準パッケージのみで作成

package main

import (
    "fmt"
    "net/http"
    "time"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "%d", time.Now().Unix())
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

Node.js

Expressは使わず標準パッケージで

var http = require('http');

var server = http.createServer(function (request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end(String(Math.floor( (new Date()).getTime() / 1000 )));
});

server.listen(8080);

Ruby

こちらもwebrick等は使わずに

require 'socket'
require 'time'

server = TCPServer.new 8080
loop do
  client = server.accept
  client.gets
  client.puts "HTTP/1.0 200 OK"
  client.puts "Content-Type: text/plain"
  client.puts
  client.puts Time.now.to_i
  client.close
end

比較1

Apache付属のabコマンドを使用しました。 今回は、同時100接続のベンチマークを取りたいと思います。そのため以下のオプションを指定しました。

ab -n 100 -c 100 http://127.0.0.1:8080/

結果1

ローカルで動かしたので正確な値ではありませんが、以下の結果になりました。

Go

Concurrency Level:      100
Time taken for tests:   0.008 seconds
Complete requests:      100
Failed requests:        0
Total transferred:      12700 bytes
HTML transferred:       1000 bytes
Requests per second:    12584.95 [#/sec] (mean)
Time per request:       7.946 [ms] (mean)
Time per request:       0.079 [ms] (mean, across all concurrent requests)
Transfer rate:          1560.83 [Kbytes/sec] received

Node.js

Concurrency Level:      100
Time taken for tests:   0.046 seconds
Complete requests:      100
Failed requests:        0
Total transferred:      11100 bytes
HTML transferred:       1000 bytes
Requests per second:    2161.69 [#/sec] (mean)
Time per request:       46.260 [ms] (mean)
Time per request:       0.463 [ms] (mean, across all concurrent requests)
Transfer rate:          234.32 [Kbytes/sec] received

Ruby

Concurrency Level:      100
Time taken for tests:   0.007 seconds
Complete requests:      100
Failed requests:        0
Total transferred:      5300 bytes
HTML transferred:       1100 bytes
Requests per second:    14341.03 [#/sec] (mean)
Time per request:       6.973 [ms] (mean)
Time per request:       0.070 [ms] (mean, across all concurrent requests)
Transfer rate:          742.26 [Kbytes/sec] received

「Requests per second」が1秒間に処理されたリクエスト数なので、この数値が大きい方がパフォーマンスがよいことになります。
意外にも一番よいスコアーだったのはRubyでした。 僅差でGo、Node.jsは1桁低いスコアーでした。

今回用意した検証コードがI/OよりCPUリソースが必要だったのか、シングルスレッドのNode.jsにとっては不利なものだったのかもしれません。

同時100接続ではRubyのスコアーがよかったですが、同時2000接続では、Rubyは応答できませんでした。
また、Goのスコアー7329.66に対してNode.jsのスコアーは3914.49と差が小さくなっていました。
Node.jsはアクセスが多い場合に力を発揮するようです。

比較2

比較1で使用したabコマンドでの同時接続数を100づつ増やして上限を確認してみました。

結果2

Go

同時2300までOK、2400でエラー

Node.js

同時2100までOK、2200でエラー

Ruby

同時120までOK、130でエラー

同時接続数はGoが一番よい結果が出ました。
Node.jsもGoに続くよい結果でした。 Rubyが少なすぎる気がしたのでwebrickでも試してみましたが、同じような結果でした。

まとめ

応答速度、同時アクセス数とも、Goのパフォーマンスが優れています。 通信回数の多いゲーム等を開発する場合は、バックエンドにGoを使うとよいかもしれません。

あと、処理内容にもよると思いますが、同時100アクセスくらいならRubyで十分かもしれません。