2016/10/19


golang のテストツールには標準でベンチマークツールが付属しています。例えば、引数 n を貰ってその数分だけメッセージの入ったスライスを返す関数 makeSlice が以下の実装だったとします。

foo.go

package foo

import "fmt"

func makeSlice(n int) []string {
    var r []string
    for i := 0; i < n; i++ {
        r = append(r, fmt.Sprintf("%03d だよーん", i))
    }
    return r
}

如何にも遅そうなコードですね。まずはこのコードを単品で計測するベンチマークを書きます。

foo_test.go

package foo

import "testing"

func BenchmarkMakeSlice(b *testing.B) {
    b.ResetTimer()
    makeSlice(b.N)
}

このベンチマークを実行する為には以下の様に実行します。

$ go test -test.bench BenchmarkMakeSlice

この時、気を付ける事は makeSlice に与える負荷量を固定値にしない事です。ベンチマークの実行は通常、b.N に対して 100, 10000, 1000000, 5000000 のベンチマークが実行されます。b.N の値の変化により処理がどの様に遅くなるかを検出する為には b.N に依存した処理を書く必要があります。

次に気を付けるべき事は、-count を指定する事です。b.N に対するテストを1回ずつ行ったとしても安定していないならば参考値にしかなり得ないからです。以下の様に -count を指定して実行します。

$ go test -count 10 -test.bench BenchmarkMakeSlice
BenchmarkMakeSlice-4     5000000               355 ns/op
BenchmarkMakeSlice-4     5000000               376 ns/op
BenchmarkMakeSlice-4     5000000               377 ns/op
BenchmarkMakeSlice-4     5000000               390 ns/op
BenchmarkMakeSlice-4     5000000               359 ns/op
BenchmarkMakeSlice-4     5000000               342 ns/op
BenchmarkMakeSlice-4     5000000               400 ns/op
BenchmarkMakeSlice-4     3000000               384 ns/op
BenchmarkMakeSlice-4     3000000               335 ns/op
BenchmarkMakeSlice-4     5000000               377 ns/op
PASS
ok      _/C_/dev/go-sandbox/bench       22.062s

ここまで出来たら改良したソースコードとの比較を始めましょう。改良された後のベンチマークだけを見たとしても本当に速くなったのかどうかは断言できませんよね。改良は上記の makeSlice を以下の様に改良しました。

package foo

import "fmt"

func makeSlice(n int) []string {
    r := make([]string, n)
    for i := 0; i < n; i++ {
        r[i] = fmt.Sprintf("%03d だよーん", i)
    }
    return r
}

make により予めスライスを確保する事で、メモリ確保の回数を減らしています。改良後のベンチマークを取ります。

$ go test -count 10 -test.bench BenchmarkMakeSlice
BenchmarkMakeSlice-4    10000000           162 ns/op
BenchmarkMakeSlice-4    10000000           161 ns/op
BenchmarkMakeSlice-4    10000000           160 ns/op
BenchmarkMakeSlice-4    10000000           161 ns/op
BenchmarkMakeSlice-4    10000000           163 ns/op
BenchmarkMakeSlice-4    10000000           165 ns/op
BenchmarkMakeSlice-4    10000000           166 ns/op
BenchmarkMakeSlice-4    10000000           159 ns/op
BenchmarkMakeSlice-4    10000000           162 ns/op
BenchmarkMakeSlice-4    10000000           157 ns/op
PASS
ok      _/C_/dev/go-sandbox/bench   18.430s

さて、確かに目に見えて速くなってはいるのですが、いったいどの程度速くなっているのでしょうか。-count を指定した事でこの処理には若干ながら揺らぎがある事が見えます。その揺らぎを纏めて、どの程度高速化されたのかを知りたいですよね。そこで便利なのが benchstat です。

benchstat - GoDoc

Command benchstat Benchstat computes and compares statistics about benchmarks. Usage: benchstat [-de...

https://godoc.org/rsc.io/benchstat

benchstat はベンチマーク結果の前後を比較し、揺らぎを計算した上でどの程度の速度差があるかを表示できるツールです。インストールは以下の様に行います。

$ go get rsc.io/benchstat

改良前のベンチマーク結果を bench1.log というファイルに、改良後のベンチマーク結果を bench2.log というファイルに出力させた後、以下の様に実行します。

$ benchstat bench1.log bench2.log
name         old time/op  new time/op  delta
MakeSlice-4   369ns ± 9%   162ns ± 3%  -56.26%  (p=0.000 n=9+10)

揺らぎが纏められ、この改良で処理時間が改良前の 56.26% にまで減っている事が分かりました。

  • 改修の前後で同じ条件である事
  • 改修内容に対して網羅的な入力パターン
  • 揺らぎが発生する程に多い回数

ベンチマークはこれらの条件を満たさないと本当に正しい結果は得られないと思います。golang のベンチマークツールはこういった条件を極力簡単に満たせる為の仕組みやツールが揃っています。ぜひ有効に活用して下さい。

Posted at by



2016/10/04


golang では配列をソートしたい場合に癖があり、Int や Float64、String といった固定の型であれば sort パッケージが提供する関数でソートが可能でしたが、独自の型や Int64 等といった sort パッケージが用意していない型の配列をソートするには Sorter というインタフェースを備えた型で扱うしかありませんでした。

package main

import (
    "fmt"
    "sort"
)

type Food struct {
    Name  string
    Price int
}

type Foods []Food

func (f Foods) Len() int {
    return len(f)
}

func (f Foods) Less(i, j intbool {
    return f[i].Price < f[j].Price
}

func (f Foods) Swap(i, j int) {
    f[i], f[j] = f[j], f[i]
}

func main() {
    foods := make([]Food, 3)
    foods[0= Food{Name: "みかん", Price: 150}
    foods[1= Food{Name: "バナナ", Price: 100}
    foods[2= Food{Name: "りんご", Price: 120}


    sort.Sort(Foods(foods))

    for _, food := range foods {
        fmt.Printf("%+v\n", food)
    }
}

この件に関して、GitHub の issues で多くの議論が行われました。

sort: make sorting easier, add Slice, SliceStable, SliceIsSorted, reflect.Swapper - Issue #16721 - golang/go - GitHub

Summary of problem 1. Vast majority of sort.Interface uses a slice 2. Have to define a new top level...

https://github.com/golang/go/issues/16721

僕も sort を簡単にする sorter というパッケージを公開したりもしていましたが、昨晩 sort.Slice, sort.SliceStable, sort.SliceIsSorted という3つの関数が master ブランチに入りました。

sort: add Slice, SliceStable, and SliceIsSorted - golang/go@22a2bdf - GitHub

Add helpers for sorting slices. Slice sorts slices: sort.Slice(s, func(i, j int) bool { if s[i].Foo ...

https://github.com/golang/go/commit/22a2bdfedb95612984cec3141924953b88a607b7

これにより上記のコードが以下の様にスッキリと書ける様になりました。

package main

import (
    "fmt"
    "sort"
)

type Food struct {
    Name  string
    Price int
}

func main() {
    foods := make([]Food, 3)
    foods[0= Food{Name: "みかん", Price: 150}
    foods[1= Food{Name: "バナナ", Price: 100}
    foods[2= Food{Name: "りんご", Price: 120}

    sort.Slice(foods, func(i, j intbool {
        return foods[i].Price < foods[j].Price
    })

    for _, food := range foods {
        fmt.Printf("%+v\n", food)
    }
}

とても良いですね。SliceStable は安定ソート、SliceIsSorted はそのスライスがソート済みかどうかを返します。

まぁあるべき姿になったという感じ。

みんなのGo言語【現場で使える実践テクニック】 みんなのGo言語【現場で使える実践テクニック】
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧 大輔, 鈴木健太, 稲葉貴洋
技術評論社 大型本 / ¥112 (2016年09月09日)
 
発送可能時間:

Posted at by



2016/09/30


http://qiita.com/shuetsu@github/items/ac21e597265d6bb906dc

orelang を Java で実装してみた

わりとよくある JSON ベースの lisp っぽいインタープリタの実装ですが、コードを見ていてもよくわからなかったので自分で実装しなおしてみました。

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "os"
)

func eval(env map[string]interface{}, v interface{}) interface{} {
    if vl, ok := v.([]interface{}); ok {
        return doRun(env, vl)
    }
    return v
}

func doRun(env map[string]interface{}, v []interface{}) interface{} {
    var r interface{}

    mn := v[0].(string)
    switch mn {
    case "step":
        for _, vi := range v[1:] {
            r = doRun(env, vi.([]interface{}))
        }
    case "until":
        for {
            c := eval(env, v[1])
            if c.(bool== true {
                break
            }
            r = doRun(env, v[2].([]interface{}))
        }
    case "get":
        return env[eval(env, v[1]).(string)]
    case "set":
        env[eval(env, v[1]).(string)] = eval(env, v[2])
        return v[2]
    case "=":
        return eval(env, v[1]).(float64== eval(env, v[2]).(float64)
    case "+":
        return eval(env, v[1]).(float64+ eval(env, v[2]).(float64)
    default:
        panic("Unknown operation: " + fmt.Sprint(v))
    }
    return r
}

func main() {
    source := `
["step",
  ["set", "i", 10],
  ["set", "sum", 0],
  ["until", ["=", ["get", "i"], 0], [
    "step",
    ["set", "sum", ["+", ["get", "sum"], ["get", "i"]]],
    ["set", "i", ["+", ["get", "i"], -1]]
  ]],
  ["get", "sum"]
]`

    var v interface{}
    err := json.Unmarshal([]byte(source), &v)
    if err != nil {
        log.Fatal(err)
    }

    env := make(map[string]interface{})

    defer func() {
        err := recover()
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
        }
    }()
    fmt.Println(doRun(env, v.([]interface{})))
}

自分で書いた割に panic 前提なのが気に入らないし、そもそも orelang って同じ名前の言語持ってる。

GitHub - mattn/orelang: 俺言語

README.md orelang 俺言語 プログラミング言語の作り方 プログラミング言語の作り方(2) プログラミング言語の作り方(3) プログラミング言語の作り方(4) プログラミング言語の作り方...

http://github.com/mattn/orelang
みんなのGo言語【現場で使える実践テクニック】 みんなのGo言語【現場で使える実践テクニック】
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧 大輔, 鈴木健太, 稲葉貴洋
技術評論社 大型本 / ¥112 (2016年09月09日)
 
発送可能時間:

Posted at by