zero memory allocation slice filtering

次のように、あるスライスをフィルタリングする関数を書くことがあると思います。 1 2 3 4 5 6 7 8 9 func FilterFoo(arr []string) []string { b := []string{} for _, e := range arr { if IsFoo(e) { b = append(b, e) } } return b } 簡単なベンチマークを書くとわかるように、この関数は返値となるスライスの長さ+1回のメモリアロケーションを行います。一般に、メモリアロケーションの回数は少ない方がパフォーマンスがよく、可能ならばアロケーション回数0を目指したいものです。 今回の場合、次のように書くとメモリアロケーション回数0回の関数を書くことができます。 追記 b := arr[:0]とすると、基底配列に影響が出るので一概に比較できない、とご指摘を受けました。実際に使用する際は副作用に注意しましょう。 このやりかたって引数に副作用あるので、わかってないで使うと危ないような…https://t.co/iKXrXHUD3N https://t.co/CMrAYGJrdA — Yoichiro Shimizu (@budougumi0617) February 4, 2019 append は引数を弄ってしまうので動作が異なりますね。 / “zero memory allocation slice filtering” https://t.co/JFFDJlfIQA — mattn (@mattn_jp) February 5, 2019 追記終わり 1 2 3 4 5 6 7 8 9 func FilterFoo(arr []string) []string { b := arr[:0] for _, e := range arr { if IsFoo(e) { b = append(b, e) } } return b } 違うのは一行目だけですが、ベンチマークを取ってみると、速度面では大きな違いがあります。次のようなベンチマークを実行してみます。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package benchmark_test import ( "strings" "testing" ) var a = []string{"Hello", "foo.go", "hoge", "bar.go", "baz.go", "fuga"} func IsGoFilename(filename string) bool { return strings.HasSuffix(filename, ".go") } func naive(arr []string) []string { var b []string for _, x := range arr { if IsGoFilename(x) { b = append(b, x) } } return b } func BenchmarkNaive(b *testing.B) { for i := 0; i < b.N; i++ { naive(a) } } func woAlloc(arr []string) []string { b := arr[:0] for _, x := range arr { if IsGoFilename(x) { b = append(b, x) } } return b } func BenchmarkWithoutAlloc(b *testing.B) { for i := 0; i < b.N; i++ { woAlloc(a) } } 結果は次のようになります。 ...

2019-02-04 · nasa9084

array/sliceに対する存在確認関数のベンチマーク

Pythonでいうところの、次の様な条件式を実現する関数を書きたかった。 1 2 3 4 5 ls = ["foo", "bar", "baz"] s = "baz" if s in ls: print("FOOBAR!") 対象がリストの時、普段なら普通にfor文を回すのですが、今回やりたかったのは定数値の一覧にあるかどうか、だったのと、定数の数も少なかったので、とりあえずで以下の様に実装していました。 1 2 3 4 5 func something(s string) error { if s != "foo" && s != "bar" && s != "baz" { return errors.New("value invalid") } } 流石に雑すぎるので、リファクタリングしよう、と思ったのですが、「はて、for文挟んだら遅くなったりしないだろうか」などと考えてしまったのでベンチマークを取りました。 TL; DR 素直にfor文を回しても大して問題はなさそう result 今回取ったベンチマークは6種類です。 for-range文を回す for文を回す map[string]struct{}を集合として取り扱ってみる &&, ||でつなぐ switch文を使う sort.SearchStrings()を使う 6番目のsort.SearchStrings()を使う方法はstackoverflow に書いてあった方法で、二分探索をしてくれるというのでやってみました。 結果は次の通り。 BenchmarkInByForRange-4 200000000 9.34 ns/op 0 B/op 0 allocs/op BenchmarkInByFor-4 100000000 10.1 ns/op 0 B/op 0 allocs/op BenchmarkInByMap-4 200000000 7.79 ns/op 0 B/op 0 allocs/op BenchmarkInByAnd-4 1000000000 2.85 ns/op 0 B/op 0 allocs/op BenchmarkInBySwitch-4 2000000000 1.39 ns/op 0 B/op 0 allocs/op BenchmarkInBySortSearchStrings-4 10000000 179 ns/op 32 B/op 1 allocs/op まぁ予想通りではあるものの、sort.SearchStrings()を使う方法は遅いですね。これはこの関数の「事前にリストがソート済みであること」という条件のために関数内でソートをしてるからだと思われます。(実際、ソート済みのリストを使って、関数内でソートをしないようにすると1/4くらいにはなる) ...

2018-06-26 · nasa9084

Golang: 配列からスライスに変換する

TL;DR: slice := array[:]で変換できる Go言語にはリストの様なものが二つあります。配列(固定長)とスライス(可変長)です。 一般に、Go言語で配列を扱うことは多くないでしょう。 実際、多くのパッケージ(標準パッケージを含む)が要求するのはスライスです。 とは言っても一部のパッケージでは配列を取り扱っているものがあります。 例えば、crypto/sha512を見てみる と、以下の様な関数が存在します。 1 func Sum512(data []byte) [Size]byte ここで、Sizeは同パッケージ内で宣言されている定数で、値は64です。 つまり、この関数は64バイトの長さを持った配列を返します。 この関数は与えられたデータからSHA512チェックサムを計算するものです。 勿論、返ってきた値をそのまま使用することもあるとは思いますが、そのままの値は人間可読な値では無いため、hexdigestを得たいと思うでしょう。 Go言語にはもちろんのことながら、encoding/hexパッケージが存在し、簡単に16進文字列を得ることができます。 16進表記の文字列を得るためには、次の関数を使用します。 1 func EncodeToString(src []byte) string 引数に注目します。 要求されているのはbyteのスライスです。 Goでは、配列とスライスは基本的に別物ですから、以下の様に書くことはできません。 1 2 3 4 h := sha512.Sum512("foobar") // 型エラーが発生する EncodeToString(h) そうは言っても、配列とスライスは非常に似ています。 次のように書きたくなりますね。 1 2 h := sha512.Sum512("foobar") EncodeToString([]byte(h)) // 配列をスライスに変換したい しかし、次のようなエラーを生じます。 cannot convert sha512.Sum512("foobar") (type [64]byte) to type []byte 型変換はできないようです。 どうしたら良いのでしょうか。 Go言語では、配列の範囲インデックスを使った場合、返される値はスライスとなります。 1 2 a := [3]string{"foo", "bar", "baz"} s := a[0:2] // sはスライス また、インデックスを省略することもできます。 開始値を省略すれば、0を与えたものと見なされますし、終了値を省略すれば、配列の最後までを切り取ります。 1 2 3 a := [3]string{"foo", "bar", "baz"} s1 := a[:2] // a[0:2]と等しい s2 := a[0:] // a[0:3]と等しい では、両方省略するとどうなるでしょうか。 両方省略すると、もとの配列と同じ内容のスライスが返されます。 ...

2018-03-16 · nasa9084