go言語のwebフレームワークechoとpythonのwebフレームワークflaskの速度を比較してみた

# golang-echoとpython-flaskの実行速度を比較してみる

今までずっとpythonを触っていて特に不満もなかったが、最近golangを触っている。
何かをちょこちょこと作るのにpythonは便利だが速度が必要なときにgolangを使ってどの程度早くなるのか感覚的に掴みたいと思い、golangで代表的なwebフレームワークechoとpythonの軽量webframeworkのflaskを比較して、どの程度速度に差があるか調べてみた。

## テスト環境

vmware上に構築したubuntu 16.04 hddは普通のやつでssdではない。
i7,memory 6GB
python2.7
golang 1.6

# シンプルな文字列を返すwebアプリ

まず感触をつかむために簡単なwebアプリを作ってみる

## golang-echoを使ったシンプルなアプリ

まずは、golangを使ったシンプルなwebアプリから

```simple_server.go

package main
import (
        "net/http"
        "github.com/labstack/echo"
        "github.com/labstack/echo/engine/standard"
)
func main() {
        e := echo.New()
        e.GET("/", func(c echo.Context) error {
                return c.String(http.StatusOK, "Hello, World!")
        })
        e.Run(standard.New(":1323"))
}
```

golangでechoライブラリを使えばこれだけ書けば、`Hello World` を返すwebアプリになり手軽だ。

## golang-echoの速度の速度を計測してみる

apach benchを使って速度を見てみる

```
shibacow@ubuntu:~/prog/golang/echo_test$ ab -n 100000 -c 100 http://localhost:1323/
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 10000 requests
...
Completed 100000 requests
Finished 100000 requests

Server Software:
Server Hostname:        localhost
Server Port:            1323

Document Path:          /
Document Length:        13 bytes

Concurrency Level:      100
Time taken for tests:   9.525 seconds
Complete requests:      100000
Failed requests:        0
Total transferred:      13000000 bytes
HTML transferred:       1300000 bytes
Requests per second:    10498.93 [#/sec] (mean)
Time per request:       9.525 [ms] (mean)
Time per request:       0.095 [ms] (mean, across all concurrent requests)
Transfer rate:          1332.87 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    3   2.8      2      19
Processing:     0    6   3.2      7      31
Waiting:        0    5   2.5      4      26
Total:          0    9   4.1      9      33

Percentage of the requests served within a certain time (ms)
  50%      9
  66%     11
  75%     12
  80%     13
  90%     15
  95%     17
  98%     18
  99%     19
100% 33 (longest request)
```

> Requests per second:    **10498.93**  [#/sec] (mean)

と対してチューニングしなくても1万req/secくらい出るようだ

## python-flaskを使ったシンプルなアプリ

次にflaskを調べてみる

```simple_server.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == '__main__':
app.run()
```
flaskもこれで、`Hello World` を返す

## python-flaskのベンチマーク

```
shibacow@ubuntu:~/prog/golang/echo_test$ ab -n 10000 -c 100  http://localhost:5000/
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 1000 requests
...
Completed 10000 requests
Finished 10000 requests


Server Software:        Werkzeug/0.11.10
Server Hostname:        localhost
Server Port:            5000

Document Path:          /
Document Length:        12 bytes

Concurrency Level:      100
Time taken for tests:   8.190 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      1680000 bytes
HTML transferred:       120000 bytes
Requests per second:    1220.97 [#/sec] (mean)
Time per request:       81.902 [ms] (mean)
Time per request:       0.819 [ms] (mean, across all concurrent requests)
Transfer rate:          200.32 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.7      0       8
Processing:     2   81  12.6     81     148
Waiting:        1   81  12.5     81     148
Total:          6   81  12.2     81     148

Percentage of the requests served within a certain time (ms)
  50%     81
  66%     90
  75%     91
  80%     92
  90%     95
  95%     98
  98%    101
  99%    106
 100%    148 (longest request)
```

> Requests per second:    **1220.97** [#/sec] (mean)

特に何もせず、1220req/sec位出る

# Mongoに接続して、検索して結果を返すwebアプリ

シンプルなアプリだと現実の問題とずれてしまうので、mongoに接続し、検索して結果を返すなど、少し現実よりのものを入れる。
こちらの[サイト](http://peroon.hatenablog.com/entry/2016/08/04/181956)を参考にした。

## golang-echoを使ったmongoに接続するwebアプリ

```mgo_server.go

package main

import (
        // サーバ系
        "net/http"
        "github.com/labstack/echo"
        "github.com/labstack/echo/engine/standard"

        // mongo系
        "fmt"
        "gopkg.in/mgo.v2"
        "gopkg.in/mgo.v2/bson"

        // 型
        "strconv"
        //"reflect"
)

// ユーザクラス
type User struct {
        Name  string `bson:"name"`
        Id int `bson:"id"`
}

func main() {
        e := echo.New()
        e.GET("/", func(c echo.Context) error {
                return c.String(http.StatusOK, "Hello, World!")
        })

        // :id, :name に値が入っていないとNot Foundになる
        e.GET("/users/id/:id", func(c echo.Context) error {

                // 数値に変換する必要あり
                var id int
                id, _ = strconv.Atoi(c.Param("id"))

                //idでDBを引く
                session, _ := mgo.Dial("mongodb://localhost")
                defer session.Close()
                db := session.DB("test")

                // id指定でユーザ取得
                var results []User
                fmt.Println(id)
                db.C("user").Find(bson.M{"id": id}).All(&results)

                fmt.Println("Results of one user: ", results)
                fmt.Println(len(results))

                if len(results) == 0 {
                        return c.String(http.StatusOK, "No user")
                }else{
                        name := results[0].Name
                        return c.String(http.StatusOK, "Hello, " + name)
                }
        })

        e.POST("/user", func(c echo.Context) error {
                return c.String(http.StatusOK, "Hello, POST user")
        })

        // ポート
        e.Run(standard.New(":1323"))
}

```

## golang-echo mongo接続のベンチマーク

mongoに接続して、結果を返すもののベンチマーク

```
shibacow@ubuntu:~/prog/golang/echo_test$ ab -n 10000 -c 100  http://localhost:1323/users/id/1
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 1000 requests
.....
Completed 10000 requests
Finished 10000 requests

Server Software:
Server Hostname:        localhost
Server Port:            1323

Document Path:          /users/id/1
Document Length:        11 bytes

Concurrency Level:      100
Time taken for tests:   9.156 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      1280000 bytes
HTML transferred:       110000 bytes
Requests per second:    1092.21 [#/sec] (mean)
Time per request:       91.557 [ms] (mean)
Time per request:       0.916 [ms] (mean, across all concurrent requests)
Transfer rate:          136.53 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   4.4      0      29
Processing:     9   90  24.9     88     213
Waiting:        9   89  25.1     87     213
Total:         25   91  24.3     89     213

Percentage of the requests served within a certain time (ms)
  50%     89
  66%    100
  75%    107
  80%    112
  90%    123
  95%    134
  98%    147
  99%    156
 100%    213 (longest request)

```

> Requests per second:    **1092.21** [#/sec] (mean)


## pytho-flask を使ってmongoを接続し、結果を取得する

mongoで接続するアプリを作る。

```mongo_server.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-

from flask import Flask
from flask_pymongo import PyMongo
app = Flask(__name__)
app.config['MONGO_HOST']='localhost'
app.config['MONGO_DBNAME'] = 'test'
mongo = PyMongo(app)

@app.route("/users/id/<int:id>")
def user_id(id):
    user= mongo.db.user.find_one({"id":id})
    msg="Hello id={} name={}".format(user["id"],user['name'])
    return msg

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == '__main__':
    app.run(host="0.0.0.0",debug=False)
```

## python-flaskを使ったベンチマーク

```
shibacow@ubuntu:~/prog/python/flask_test$ ab -n 10000 -c100 http://localhost:500
0/users/id/1
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 1000 requests
.....
Completed 10000 requests
Finished 10000 requests

Server Software:        Werkzeug/0.11.10
Server Hostname:        localhost
Server Port:            5000

Document Path:          /users/id/1
Document Length:        20 bytes

Concurrency Level:      100
Time taken for tests:   12.639 seconds
Complete requests:      10000
Failed requests:        0
Total transferred:      1760000 bytes
HTML transferred:       200000 bytes
Requests per second:    791.22 [#/sec] (mean)
Time per request:       126.387 [ms] (mean)
Time per request:       1.264 [ms] (mean, across all concurrent requests)
Transfer rate:          135.99 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.7      0       8
Processing:     6  126  11.8    125     164
Waiting:        6  125  11.8    125     163
Total:         11  126  11.5    125     164

Percentage of the requests served within a certain time (ms)
  50%    125
  66%    129
  75%    131
  80%    132
  90%    138
  95%    143
  98%    149
  99%    153
 100%    164 (longest request)

```

> Requests per second:    **791.22** [#/sec] (mean)

# まとめ

golang-echoとpython-flaskの速度を比較した。

| 比較             |    シンプルなアプリ |    複雑なアプリ    |
|:-----------------|:-----------------:|:------------------:|
| golang-echo      | **10498** req/sec |  **1092** req/sec      |
| python-flask     | **1220** req/sec  |    **791** req/sec     |


シンプルなアプリでは、１０倍位性能が違うが、mongoに接続する場合は、そこまで速度差が出ない。リクエストのたびにmongoに接続するのでそちらの方が、負荷が高いのかもしれない。echoもpymongoもコネクションプールを使えばまた結果は変わるのかもしれない。
golangとpythonを比較したのは別にpythonの性能を貶めたいわけではなく、単に大体どこくらい差があるのかを体感したいからだ。

## まとめ(追記)

下記追記にあるが、mongoへの毎回への接続をやめて、一回接続をして使い回し用にしたら、だいぶ高速化した。ただ、セッションが状態を持ち続けるので、例えばアップデートしたりとか、コミットしたりするときにどのようになるかはわからないので、ベンチマーク以外の本番環境で使うには検証が必要だろう。

| 比較             |    シンプルなアプリ |    複雑なアプリ    |
|:-----------------|:-----------------:|:------------------:|
| golang-echo      | **10498** req/sec |  **1092** req/sec      |
| golang-echo(下記改良版) | なし |  **6283.74** req/sec     |
| python-flask     | **1220** req/sec  |    **791** req/sec     |


# 追記 golang-echo mongo接続して結果を取得の高速化

## golang-echo mongo接続改良

上の例では、一リクエストあたり、mongoへの接続と切断を繰り返していた。それをやめてGETの外側で接続と切断をやる。標準出力への表示は遅くなる元なのでそれをやめた。

```mgo_server.go
package main

import (
        // サーバ系
        "net/http"
        "github.com/labstack/echo"
        "github.com/labstack/echo/engine/standard"

        // mongo系
        //"fmt" //標準出力は遅いので、止める
        "gopkg.in/mgo.v2"
        "gopkg.in/mgo.v2/bson"

        // 型
        "strconv"
        //"reflect"
)

// ユーザクラス
type User struct {
        Name  string `bson:"name"`
        Id int `bson:"id"`
}

func main() {
        e := echo.New()
        //idでDBを引くGETの外側に出して、毎回の接続・切断をやめた
        session, _ := mgo.Dial("mongodb://localhost")
        defer session.Close()
        db := session.DB("test")

        e.GET("/", func(c echo.Context) error {
                return c.String(http.StatusOK, "Hello, World!")
        })

        // :id, :name に値が入っていないとNot Foundになる
        e.GET("/users/id/:id", func(c echo.Context) error {

                // 数値に変換する必要あり
                var id int
                id, _ = strconv.Atoi(c.Param("id"))


                // id指定でユーザ取得
                var results []User
                //fmt.Println(id)
                db.C("user").Find(bson.M{"id": id}).All(&results)

                //fmt.Println("Results of one user: ", results)
                //fmt.Println(len(results))

                if len(results) == 0 {
                        return c.String(http.StatusOK, "No user")
                }else{
                        name := results[0].Name
                        return c.String(http.StatusOK, "Hello, " + name)
                }
        })

        e.POST("/user", func(c echo.Context) error {
                return c.String(http.StatusOK, "Hello, POST user")
        })

        // ポート
        e.Run(standard.New(":1323"))
}

```

## 改良版ベンチマーク

上記改良を行ったところ、だいぶ高速化し、**6282** req/secになった。

```
shibacow@ubuntu:~$ ab -n 100000 -c 100 http://localhost:1323/users/id/1
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 10000 requests
....
Completed 100000 requests
Finished 100000 requests


Server Software:
Server Hostname:        localhost
Server Port:            1323

Document Path:          /users/id/1
Document Length:        11 bytes

Concurrency Level:      100
Time taken for tests:   15.914 seconds
Complete requests:      100000
Failed requests:        0
Total transferred:      12800000 bytes
HTML transferred:       1100000 bytes
Requests per second:    6283.74 [#/sec] (mean)
Time per request:       15.914 [ms] (mean)
Time per request:       0.159 [ms] (mean, across all concurrent requests)
Transfer rate:          785.47 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    4   3.2      3      21
Processing:     0   12   5.0     12      44
Waiting:        0   10   4.9     10      44
Total:          0   16   5.2     15      45

Percentage of the requests served within a certain time (ms)
  50%     15
  66%     17
  75%     19
  80%     20
  90%     22
  95%     25
  98%     29
  99%     32
 100%     45 (longest request)
```

> Requests per second:  **6283.74** [#/sec] (mean)

だいぶ高速化した。