当我们将代码重写为不进行切片操作时,边界检查就会消失。这在实际代码中经常出现,例如,我在encoding/json解码器的某个热点函数中遇到了这个问题:
encoding/json
我不确定让证明通过意识到切片表达式有多容易。我认为处理简单的x = x[N:]情况(其中N是常数)应该是可行的,并希望在标准库中消除几十个边界检查。/cc @aclements@rasky@josharian
x = x[N:]
N
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]时,问题会消失。
d = d[:24]
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:] }
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) }
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))
4条答案
按热度按时间xxslljrj1#
在调试gio(https://go.godbolt.org/z/hc44d5)时发现了一个特别糟糕的案例:
请注意,当使用
d = d[:24]
时,问题会消失。y4ekin9u2#
指定长度也使问题消失。
以下代码不需要边界检查:
fslejnso3#
更多的观察表明,这是由于代码内联引起的:
iecba09b4#
奇怪的是,只有第一行消除了边界检查: