2015/11/20


ミドルウェアとして Use するだけでパフォーマンスが向上するなんて夢の様な話はないと思っていたけど、HTTP Coala はそれをやってのけている様です。

goware/httpcoala · GitHub

Go http middleware handler for request coalescing

https://github.com/goware/httpcoala

ちょっと信じがたかったので、まずはベンチマークを取ってみた。

Coala 未使用

package main

import (
    "net/http"

    "github.com/zenazn/goji"
    "github.com/zenazn/goji/web"
)

func main() {
    goji.Get("/"func(c web.C, w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello World"))
    })
    goji.Serve()
}
$ ab -n 10 -n 10000 http://localhost:8000/ Server Software:
Server Hostname:        localhost
Server Port:            8000

Document Path:          /
Document Length:        11 bytes

Concurrency Level:      10
Time taken for tests:   4.185837 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      1280000 bytes
HTML transferred:       110000 bytes
Requests per second:    2389.01 [#/sec] (mean)
Time per request:       4.186 [ms] (mean)
Time per request:       0.419 [ms] (mean, across all concurrent requests)
Transfer rate:          298.63 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       7
Processing:     0    3   1.5      4      31
Waiting:        0    2   1.5      2      30
Total:          0    3   1.5      4      31

Percentage of the requests served within a certain time (ms)
  50%      4
  66%      4
  75%      4
  80%      4
  90%      5
  95%      6
  98%      7
  99%      8
 100%     31 (longest request)

Coala 使用

package main

import (
    "net/http"

    "github.com/goware/httpcoala"
    "github.com/zenazn/goji"
    "github.com/zenazn/goji/web"
)

func main() {
    goji.Get("/"func(c web.C, w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello World"))
    })
    goji.Use(httpcoala.Route("GET")) // ココ
    goji.Serve()
}
Server Software:
Server Hostname:        localhost
Server Port:            8000

Document Path:          /
Document Length:        11 bytes

Concurrency Level:      10
Time taken for tests:   3.860772 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      1280000 bytes
HTML transferred:       110000 bytes
Requests per second:    2590.16 [#/sec] (mean)
Time per request:       3.861 [ms] (mean)
Time per request:       0.386 [ms] (mean, across all concurrent requests)
Transfer rate:          323.77 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       1
Processing:     0    3   2.2      3      74
Waiting:        0    2   2.3      2      71
Total:          0    3   2.2      3      74

Percentage of the requests served within a certain time (ms)
  50%      3
  66%      3
  75%      4
  80%      4
  90%      4
  95%      5
  98%      5
  99%      6
 100%     74 (longest request)

確かに若干ですがパフォーマンスが良くなってる。README.md によるとスレッド数を上げると

Without httpcoala middleware, Requests/sec:   7081.09
   With httpcoala middleware, Requests/sec:  18373.87

2倍以上の差が出る場合もある様です。何がそうさせるのだろうとコードを読んでみた。

httpcoala/httpcoala.go at master · goware/httpcoala · GitHub
https://github.com/goware/httpcoala/blob/master/httpcoala.go

複数のリクエストを channel を使って結合し、同じレスポンスを返すという仕組みで毎回 ResponseWriter の生成や切断時処理を行うのを回避してる。ハンドラ内が遅くなればなるほど効果が出ます。

httpcoala/httpcoala.go at master · goware/httpcoala · GitHub
https://github.com/goware/httpcoala/blob/master/httpcoala.go#L71-L78

今回の様に同じ URL に対して同じメソッドのリクエストが頻繁に起こる場合には非常に有効な手段だと分かる。キャッシュではない為、リアルタイム性も担保される。なかなかいい所に目を付けてるなーと思った。

効果には個人差がある様です。

Go Programming Language, The (Addison-Wesley Professional Computing Series) (English Edition) Go Programming Language, The (Addison-Wesley Professional Computing Series) (English Edition)
Donovan, Alan A. A., Kernighan, Brian W.
Addison-Wesley Professional Kindle版 / ¥2,648 (2015年11月16日)
 
発送可能時間:

Posted at by



2015/10/30


golang - Goで外部コマンドをパイプして実行する - Qiita

もっとうまいやり方誰か教えてください( ꒪⌓꒪)

http://qiita.com/yuroyoro/items/9358cd25b5f7fe9dd37f

本当はプロセスの生死と共にパイプが閉じられないといけないので io.Pipe ではなく Cmd.StdoutPipe を使った方がよい。ただしコード量はもう少し多くなる。確かに毎回書くのはダルいのでパッケージを作った。

mattn/go-pipeline - GitHub
https://github.com/mattn/go-pipeline

これを使うと簡単にコマンドパイプラインが扱える。

package pipeline

import (
    "fmt"
    "log"
)

func ExampleCommandPipeLine() {
    out, err := Output(
        []string{"git""log""--oneline"},
        []string{"grep""first import"},
        []string{"wc""-l"},
    )
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(out))
    // Output:
    // 1
}

よろしければどうぞお使い下さい。

Posted at by



2015/09/28


Go - ISUCON5予選でスコア34000を出す方法 - Qiita

Goで下記のようなsliceの先頭にひたすらオブジェクトを追加する処理を書く際は、不必要に長いsliceにならないよう注意しましょう。負荷が非常に高い処理になります。

hoge := []int{}
for _, comment := range comments {
    hoge = append([]int{comment.ID}, hoge...)
}
http://qiita.com/y_matsuwitter/items/771020ebb68c07053548

append は第一引数のスライスに第二引数以降の可変個アイテムを追加する関数です。なるべく呼ばれない実装が良いです。どうしても多いアイテムを扱う場合は出来れば以下の様に書くのが良いです。

参考: https://blog.golang.org/slices

hoge := make([]int0len(comments))
for i := 0; i < len(comments); i++ {
    hoge = append(hoge, comment.ID)
}

注意)これは逆順です

もしくは

hoge := make([]intlen(comments), len(comments))
for i := 0; i < len(comments); i++ {
    hoge[i] = comment.ID
}

注意)これは逆順です

golang の append はキャパシティ内であれば hoge は増幅せずに戻り値として返されます。つまり先に make で確保しておけば append でのアローケートは1回となります。次に以下のコード見て下さい。リンク先のコードと以下のコードは少しの違いしかないですが、実は物凄い違いがあります。一見、単純な間違いに見えますが実は大きな落とし穴があります。

hoge := []int{}
for _, comment := range comments {
    hoge = append(hoge, comment.ID)
}

上記リンクのコードと引数の順番が違うだけに見え、処理性能も変わらない様に見えます。しかし上記のリンクにあるコードの場合だと、ループ毎に新しいスライスが作られ、それに対して増えつつある hoge を追加し、さらに hoge を壊して代入する形になります。つまり O(N^2) 回処理されます。

先頭に追加するのであれば以下の様に書くべきです。

hoge := make([]intlen(comments), len(comments))
l := len(comments)
for i, j := l - 10; i >= 0; i-- {
    hoge[i] = comments[j].ID
    j++
}

ベンチマークを取ってみると

package foo

import (
    "testing"
)

type comment struct {
    ID int
}

var (
    comments []comment
)

func ready() {
    comments = make([]comment, 6000060000)
    for i := 0; i < len(comments); i++ {
        comments[i].ID = i
    }
}

func BenchmarkTest1(b *testing.B) {
    ready()

    b.ResetTimer()
    hoge := []int{}
    for _, comment := range comments {
        hoge = append([]int{comment.ID}, hoge...)
    }
}

func BenchmarkTest2(b *testing.B) {
    ready()

    b.ResetTimer()
    hoge := make([]intlen(comments), len(comments))
    l := len(comments)
    for i, j := l-10; i >= 0; i-- {
        hoge[i] = comments[j].ID
        j++
    }
}
testing: warning: no tests to run
PASS
BenchmarkTest1-4               1        7712771200 ns/op
BenchmarkTest2-4        2000000000               0.00 ns/op
ok      _/c_/dev/arrr   7.949s

60000個の処理を行うと、2000000000倍(2億倍)の差が付くようです。

Posted at by