go ``` cmd/compile: teach prove about slice expressions ```

xqnpmsa8  于 5个月前  发布在  Go
关注(0)|答案(4)|浏览(60)

当我们将代码重写为不进行切片操作时,边界检查就会消失。这在实际代码中经常出现,例如,我在encoding/json解码器的某个热点函数中遇到了这个问题:

我不确定让证明通过意识到切片表达式有多容易。我认为处理简单的x = x[N:]情况(其中N是常数)应该是可行的,并希望在标准库中消除几十个边界检查。
/cc @aclements@rasky@josharian

xxslljrj

xxslljrj1#

在调试gio(https://go.godbolt.org/z/hc44d5)时发现了一个特别糟糕的案例:

package ex

import (
    "math"
    "encoding/binary"
)

type Point struct { X, Y float32 }
type Quad  struct { From, Ctrl, To Point }

func EncodeQuad(d []byte, q Quad) {
    if len(d) < 24 {
        return
    }
    binary.LittleEndian.PutUint32(d[0:], math.Float32bits(q.From.X))
    binary.LittleEndian.PutUint32(d[4:], math.Float32bits(q.From.Y))
    binary.LittleEndian.PutUint32(d[8:], math.Float32bits(q.Ctrl.X))
    binary.LittleEndian.PutUint32(d[12:], math.Float32bits(q.Ctrl.Y))
    binary.LittleEndian.PutUint32(d[16:], math.Float32bits(q.To.X))
    binary.LittleEndian.PutUint32(d[20:], math.Float32bits(q.To.Y))
}

请注意,当使用d = d[:24]时,问题会消失。

y4ekin9u

y4ekin9u2#

指定长度也使问题消失。

binary.LittleEndian.PutUint32(d[0:4], math.Float32bits(q.From.X))
    binary.LittleEndian.PutUint32(d[4:8], math.Float32bits(q.From.Y))
    binary.LittleEndian.PutUint32(d[8:12], math.Float32bits(q.Ctrl.X))
    binary.LittleEndian.PutUint32(d[12:16], math.Float32bits(q.Ctrl.Y))
    binary.LittleEndian.PutUint32(d[16:20], math.Float32bits(q.To.X))
    binary.LittleEndian.PutUint32(d[20:24], math.Float32bits(q.To.Y))

以下代码不需要边界检查:

func EncodeQuad(d []byte, q Quad) {
    if len(d) < 24 {
        return
    }

    _ = d[0:]
    _ = d[4:]
    _ = d[8:]
    _ = d[12:]
    _ = d[16:]
    _ = d[20:]
}
fslejnso

fslejnso3#

更多的观察表明,这是由于代码内联引起的:

package ex

import (
    "math"
    "encoding/binary"
)

type Point struct { X, Y float32 }
type Quad  struct { From, Ctrl, To Point }

func EncodeQuad(d []byte, q Quad) {
    if len(d) < 24 {
        return
    }

    binary.LittleEndian.PutUint32(d[0:], math.Float32bits(q.From.X))
    PutUint32_NoInline(d[4:], math.Float32bits(q.From.Y))
    PutUint32_MayInline(d[8:], math.Float32bits(q.Ctrl.X)) // bounds check
    t.PutUint32_NoInline(d[12:], math.Float32bits(q.Ctrl.Y))
    t.PutUint32_MayInline(d[16:], math.Float32bits(q.To.X)) // bounds check
    //PutUint32(d[20:], math.Float32bits(q.To.Y))
    {
        b, v := d[20:], math.Float32bits(q.To.Y)
        _ = b[3] // bounds check
        b[0] = byte(v >> 24)
        b[1] = byte(v >> 16)
        b[2] = byte(v >> 8)
        b[3] = byte(v)
    }
}

//go:noinline 
func PutUint32_NoInline(b []byte, v uint32) {
	_ = b[3] // bounds check
	b[0] = byte(v >> 24)
	b[1] = byte(v >> 16)
	b[2] = byte(v >> 8)
	b[3] = byte(v)
}

func PutUint32_MayInline(b []byte, v uint32) {
	_ = b[3] // bounds check
	b[0] = byte(v >> 24)
	b[1] = byte(v >> 16)
	b[2] = byte(v >> 8)
	b[3] = byte(v)
}

type T struct{}
var t T

//go:noinline 
func (T) PutUint32_NoInline(b []byte, v uint32) {
	_ = b[3] // bounds check
	b[0] = byte(v >> 24)
	b[1] = byte(v >> 16)
	b[2] = byte(v >> 8)
	b[3] = byte(v)
}

func (T) PutUint32_MayInline(b []byte, v uint32) {
	_ = b[3] // bounds check
	b[0] = byte(v >> 24)
	b[1] = byte(v >> 16)
	b[2] = byte(v >> 8)
	b[3] = byte(v)
}
iecba09b

iecba09b4#

奇怪的是,只有第一行消除了边界检查:

binary.LittleEndian.PutUint32(d[0:], math.Float32bits(q.From.X))
    binary.LittleEndian.PutUint32(d[4:], math.Float32bits(q.From.Y))
    binary.LittleEndian.PutUint32(d[8:], math.Float32bits(q.Ctrl.X))
    binary.LittleEndian.PutUint32(d[12:], math.Float32bits(q.Ctrl.Y))
    binary.LittleEndian.PutUint32(d[16:], math.Float32bits(q.To.X))
    binary.LittleEndian.PutUint32(d[20:], math.Float32bits(q.To.Y))

相关问题