Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Bounds Check Eliminationについて調べてみた / 1218-lt

kaznishi
December 18, 2018

Bounds Check Eliminationについて調べてみた / 1218-lt

kaznishi

December 18, 2018
Tweet

More Decks by kaznishi

Other Decks in Programming

Transcript

  1. Bounds Check Eliminationに
    ついて調べてみた
    2018-12-18 golang.tokyo #20 LT
    by kaznishi

    View Slide

  2. 自己紹介
    Twitter: @kaznishi1246
    主な守備範囲: サーバーサイド,インフラ
    主な使用言語: Go, PHP, Scala

    View Slide

  3. はじめに
    配列やスライスに対して添字アクセス
    通常は範囲チェックが必要
    範囲チェック...Bounds Check

    View Slide

  4. はじめに
    しかし、コンパイラが安全だと判断する状況下で
    はチェックの省略が可能
    範囲チェックの省略のことを Bounds Check
    Elimination(BCE)という
    Go 1.7+ ではSSAが導入されてBCEが動くようにな
    った

    View Slide

  5. func f1(nums []int) int {
    sum := nums[0]
    sum += nums[1]
    sum += nums[2]
    sum += nums[3]
    sum += nums[4]
    return sum
    }
    func f2(nums []int) int {
    sum := nums[4]
    sum += nums[3]
    sum += nums[2]
    sum += nums[1]
    sum += nums[0]
    return sum
    }
    どちらもスライスの要素を足し合わせる処理

    View Slide

  6. build実行時に
    -gcflags="-d=ssa/check_bce/debug=1"
    というオプションを付けてみよう

    View Slide

  7. $ go build -gcflags="-d=ssa/check_bce/debug=1" main.go
    # command-line-arguments
    ./main.go:11:13: Found IsInBounds
    ./main.go:12:13: Found IsInBounds
    ./main.go:13:13: Found IsInBounds
    ./main.go:14:13: Found IsInBounds
    ./main.go:15:13: Found IsInBounds
    ./main.go:20:13: Found IsInBounds

    View Slide

  8. func f1(nums []int) int {
    sum := nums[0] # Found IsInBounds
    sum += nums[1] # Found IsInBounds
    sum += nums[2] # Found IsInBounds
    sum += nums[3] # Found IsInBounds
    sum += nums[4] # Found IsInBounds
    return sum
    }
    func f2(nums []int) int {
    sum := nums[4] # Found IsInBounds
    sum += nums[3]
    sum += nums[2]
    sum += nums[1]
    sum += nums[0]
    return sum
    }

    View Slide

  9. func f1(nums []int) int {
    sum := nums[0] # Found IsInBounds
    sum += nums[1] # Found IsInBounds
    sum += nums[2] # Found IsInBounds
    sum += nums[3] # Found IsInBounds
    sum += nums[4] # Found IsInBounds
    return sum
    }
    func f2(nums []int) int {
    sum := nums[4] # Found IsInBounds
    sum += nums[3]
    sum += nums[2]
    sum += nums[1]
    sum += nums[0]
    return sum
    }
    IsInBounds (IsSliceInBounds) ... Bounds Checkが必要

    View Slide

  10. func f2(nums []int) int {
    sum := nums[4] # 先に[4]にアクセスすることで
    sum += nums[3] # チェック不要
    sum += nums[2] # チェック不要
    sum += nums[1] # チェック不要
    sum += nums[0] # チェック不要
    return sum
    }

    View Slide

  11. ベンチ

    View Slide

  12. func BenchmarkF1(b *testing.B) {
    nums := []int{}
    for i := 0; i < 5; i++ {
    nums = append(nums, i)
    }
    for n := 0; n < b.N; n++ {
    _ = f1(nums)
    }
    }
    func BenchmarkF2(b *testing.B) {
    nums := []int{}
    for i := 0; i < 5; i++ {
    nums = append(nums, i)
    }
    for n := 0; n < b.N; n++ {
    _ = f2(nums)
    }
    }

    View Slide

  13. BenchmarkF1-4 2000000000 0.99 ns/op
    BenchmarkF2-4 2000000000 0.33 ns/op

    View Slide

  14. BCEが適用される他のケース

    View Slide

  15. if, lenで確認してから
    func f3(s []int, index int) {
    if index >= 0 && index < len(s) {
    _ = s[index] // Bounds Check Elimination!!
    _ = s[index:len(s)] // Bounds Check Elimination!!
    }
    }
    func f4(s []int) {
    if len(s) > 2 {
    _, _, _ = s[0], s[1], s[2]//Bounds Check Elimination!!
    }
    }

    View Slide

  16. forループ中でのアクセス
    func f5(s []int) {
    for i := range s {
    _ = s[i] // Bounds Check Elimination!!
    _ = s[i:len(s)] // Bounds Check Elimination!!
    _ = s[:i+1] // Bounds Check Elimination!!
    }
    }
    func f6(s []int) {
    for i := 0; i < len(s); i++ {
    _ = s[i] // Bounds Check Elimination!!
    _ = s[i:len(s)] // Bounds Check Elimination!!
    _ = s[:i+1] // Bounds Check Elimination!!
    }
    }

    View Slide

  17. ただし、別の配列やスライスへのアクセスはBCEは適
    用されない
    func f7a(s []int) []int {
    var hoge int
    s2 := make([]int, len(s))
    for i := range s {
    hoge = -s[i]
    s2[i] = hoge // Bounds Checkが行われる
    }
    return s2
    }
    make([]int, len(s)) なので人の頭で考えれば安全なの
    は分かるが…

    View Slide

  18. ちょっとしたテクニック
    func f7b(s []int) []int {
    var hoge int
    s2 := make([]int, len(s))
    s2 = s2[:len(s)] // これがキモ
    for i := range s {
    hoge = -s[i]
    s2[i] = hoge // Bounds Check Elimination!!
    }
    return s2
    }

    View Slide

  19. ただしベンチの改善はなかった…
    func BenchmarkF7A(b *testing.B) {
    nums := []int{}
    for i := 0; i < 50000; i++ {
    nums = append(nums, i)
    }
    for n := 0; n < b.N; n++ {
    _ = f7a(nums)
    }
    }
    func BenchmarkF7B(b *testing.B) {
    nums := []int{}
    for i := 0; i < 50000; i++ {
    nums = append(nums, i)
    }
    for n := 0; n < b.N; n++ {
    _ = f7b(nums)
    }
    }

    View Slide

  20. ただしベンチの改善はなかった…
    BenchmarkF7A-4 20000 79592 ns/op
    BenchmarkF7B-4 20000 85497 ns/op

    View Slide

  21. BCEを使って性能改善した例
    https://github.com/disintegration/imaging/pull/81
    Great Work!!

    View Slide

  22. 所感
    for,if,lenで配列やスライスの安全なコードを書くこ
    とでBCE適用
    特にBCEを意識しなくても知らぬ間に適用され
    ている可能性が大きい
    意図的にBCEを適用させるテクニックはベンチを
    取ったうえで使いましょう
    チューニングの手段のひとつとして頭に置いてお
    くのはよいと思う

    View Slide

  23. 参考記事
    Bounds Check Elimination
    https://go101.org/article/bounds-check-
    elimination.html

    View Slide

  24. おわり

    View Slide