segmentio / encoding

Go package containing implementations of efficient encoding, decoding, and validation APIs.
MIT License
987 stars 52 forks source link

json: extendSlice optimization is still valid for go1.19 #127

Closed chriso closed 1 year ago

chriso commented 1 year ago

This PR enables the extendSlice optimization for go1.18 and go1.19, where it's still valid. The optimization will be disabled again in go1.20 in case internals change.

I get the following improvement for go test ./json -run=^$ -bench=CodeUnmarshal$ -benchtime=2000x -count 10 on arm64:

name              old time/op    new time/op    delta
CodeUnmarshal-10     706µs ± 2%     695µs ± 2%  -1.50%  (p=0.002 n=10+10)

name              old speed      new speed      delta
CodeUnmarshal-10  2.75GB/s ± 2%  2.79GB/s ± 2%  +1.52%  (p=0.002 n=10+10)

The optimization was previously enabled for go1.17 in https://github.com/segmentio/encoding/pull/96.

achille-roussel commented 1 year ago

It's been 5 versions of Go since we introduced this optimization and it's never been invalidated by the runtime. Maybe we would spend less engineering time if we were to remove the build tag and address if the build ever breaks in a future Go version?

chriso commented 1 year ago

I think it's still better to opt-in to the unsafe code.

If the internals do change, and we don't remember to check whether it still works each time a new version is released, do we risk exposing users to segfaults?

//go:linkname unsafe_NewArray reflect.unsafe_NewArray
func unsafe_NewArray(rtype unsafe.Pointer, length int) unsafe.Pointer

//go:linkname typedslicecopy reflect.typedslicecopy
//go:noescape
func typedslicecopy(elemType unsafe.Pointer, dst, src slice) int

func extendSlice(t reflect.Type, s *slice, n int) slice {
    elemTypeRef := t.Elem()
    elemTypePtr := ((*iface)(unsafe.Pointer(&elemTypeRef))).ptr

    d := slice{
        data: unsafe_NewArray(elemTypePtr, n),
        len:  s.len,
        cap:  n,
    }

    typedslicecopy(elemTypePtr, d, *s)
    return d
}

On the other hand, if we forget to (re)enable it when a new version is released, our code only gets ~1.5% slower.