LoginSignup
4
0

More than 5 years have passed since last update.

Goで2つのスライスのうち片方のみに存在する要素を400倍くらい速く取り出した

Posted at

これ、流石にほっとけないので400倍くらい速くしてみましたが、眠くてお手上げなので私ができたところまで解説。きっと親切な人がまさかりがてらもっと保守性が高くて速いコードを教えてくれるはず。

問題の内容

Goで2つのSlice(A、B)のうち、Aにだけ存在し、Bには存在しない値を持ったSliceを生成する

大分曖昧ですが、詳細は元記事を。元記事のコードは二重ループを使う典型的な遅くなるやつだったので改造に取りかかる。
基本的には、元記事のコードと同じ要件を満たすようにする。
ただし、元記事には書いてない条件として、配列はソートされた状態で出力するものとする。

結果

BenchmarkQiita-4           1    2127655888 ns/op     2303264 B/op         28 allocs/op
BenchmarkMine0-4         200       6466429 ns/op      524352 B/op          3 allocs/op
BenchmarkMine1-4         100      18598037 ns/op     8514379 B/op       2432 allocs/op

400倍くらい速くなりました。配列長くすればもっと(相対的に)速くなります。ループもメモリ消費も実装0ベースで考えていけば良さそう。

私の実装0

func checkDup(sliceA, sliceB []int) []int {
    s := make([]int, 0, len(sliceA))
    sort.Ints(sliceA)
    sort.Ints(sliceB)
    for i, j := 0, 0; i < len(sliceA) && j < len(sliceB); {
        if sliceA[i] == sliceB[j] {
            i++
        } else if sliceA[i] < sliceB[j] {
            s = append(s, sliceA[i])
            i++
        } else {
            if j == len(sliceB)-1 {
                s = append(s, sliceA[i:len(sliceA)]...)
            }
            j++
        }
    }
    return s
}

汚いコードだな…。保守性低そうですが、私が書いた中ではこれが一番速くてメモリ使いませんでした。ソートして合計一週ずつするだけの非常にシンプルな考え方。
工夫のしどころはまだまだありそうですが、眠いのでこんなもので。

私の実装1(map利用、遅め)

func checkDup1(sliceA, sliceB []int) []int {
    s := make([]int, 0, len(sliceA))
    m := make(map[int]int, len(sliceA))
    for _, n := range sliceA {
        m[n] += 1
    }
    for _, n := range sliceB {
        m[n] -= 1
    }
    for n, count := range m {
        if count > 0 {
            for i := 0; i < count; i++ {
                s = append(s, n)
            }
        }
    }
    sort.Ints(s)
    return s
}

map作ってみたけど流石にソート済みには勝てなかったよ…

テストコード

func makeSlices() [][]int {
    l := 65536
    s := make([][]int, 2, 2)
    s[0] = make([]int, 0, l)
    s[1] = make([]int, 0, l)
    for i := 0; i < l; i++ {
        s[0] = append(s[0], i*3)
        s[1] = append(s[1], i*5)
    }
    return s
}

var cases = makeSlices()

とりあえずこんなので生成してます。
これもなんかもう少し速いやり方あった気がする…

まとめ

二重ループに気をつけよう

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0