From 2344d282558c42493f90637dd383ff5cdec78c55 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Fri, 30 Aug 2024 11:19:12 +0200 Subject: [PATCH] reduce modeler.Read helpers allocations --- binary/slice.go | 146 +++++++++++++++++++++++++----------------- modeler/bench_test.go | 84 ++++++++++++++++++++++++ modeler/read_test.go | 8 +-- 3 files changed, 175 insertions(+), 63 deletions(-) diff --git a/binary/slice.go b/binary/slice.go index ecb42d9..398dc7a 100644 --- a/binary/slice.go +++ b/binary/slice.go @@ -9,144 +9,156 @@ import ( "github.com/qmuntal/gltf" ) -func castSlice(c gltf.ComponentType, t gltf.AccessorType, v []byte) any { +func reslice[T any](v []byte, length, rem int) []T { var ptr unsafe.Pointer if len(v) != 0 { ptr = unsafe.Pointer(&v[0]) } + s := unsafe.Slice((*T)(unsafe.Pointer(ptr)), length) + if rem > 0 { + return append(s, make([]T, rem)...) + } + return s[:length] +} + +func castSlice(c gltf.ComponentType, t gltf.AccessorType, count int, v []byte) any { + l := len(v) / (c.ByteSize() * t.Components()) + var rem int + if count < l { + l = count + } else if count > l { + l = count + rem = count - l + } + switch c { case gltf.ComponentUbyte: switch t { case gltf.AccessorScalar: - return v + return reslice[uint8](v, l, rem) case gltf.AccessorVec2: - return unsafe.Slice((*[2]uint8)(ptr), len(v)/2) + return reslice[[2]uint8](v, l, rem) case gltf.AccessorVec3: - return unsafe.Slice((*[3]uint8)(ptr), len(v)/3) + return reslice[[3]uint8](v, l, rem) case gltf.AccessorVec4: - return unsafe.Slice((*[4]uint8)(ptr), len(v)/4) + return reslice[[4]uint8](v, l, rem) case gltf.AccessorMat2: - return unsafe.Slice((*[2][2]uint8)(ptr), len(v)/4) + return reslice[[2][2]uint8](v, l, rem) case gltf.AccessorMat3: - return unsafe.Slice((*[3][3]uint8)(ptr), len(v)/9) + return reslice[[3][3]uint8](v, l, rem) case gltf.AccessorMat4: - return unsafe.Slice((*[4][4]uint8)(ptr), len(v)/16) + return reslice[[4][4]uint8](v, l, rem) } case gltf.ComponentByte: switch t { case gltf.AccessorScalar: - return unsafe.Slice((*int8)(ptr), len(v)) + return reslice[int8](v, l, rem) case gltf.AccessorVec2: - return unsafe.Slice((*[2]int8)(ptr), len(v)/2) + return reslice[[2]int8](v, l, rem) case gltf.AccessorVec3: - return unsafe.Slice((*[3]int8)(ptr), len(v)/3) + return reslice[[3]int8](v, l, rem) case gltf.AccessorVec4: - return unsafe.Slice((*[4]int8)(ptr), len(v)/4) + return reslice[[4]int8](v, l, rem) case gltf.AccessorMat2: - return unsafe.Slice((*[2][2]int8)(ptr), len(v)/4) + return reslice[[2][2]int8](v, l, rem) case gltf.AccessorMat3: - return unsafe.Slice((*[3][3]int8)(ptr), len(v)/9) + return reslice[[3][3]int8](v, l, rem) case gltf.AccessorMat4: - return unsafe.Slice((*[4][4]int8)(ptr), len(v)/16) + return reslice[[4][4]int8](v, l, rem) } case gltf.ComponentUshort: switch t { case gltf.AccessorScalar: - return unsafe.Slice((*uint16)(ptr), len(v)/2) + return reslice[uint16](v, l, rem) case gltf.AccessorVec2: - return unsafe.Slice((*[2]uint16)(ptr), len(v)/4) + return reslice[[2]uint16](v, l, rem) case gltf.AccessorVec3: - return unsafe.Slice((*[3]uint16)(ptr), len(v)/6) + return reslice[[3]uint16](v, l, rem) case gltf.AccessorVec4: - return unsafe.Slice((*[4]uint16)(ptr), len(v)/8) + return reslice[[4]uint16](v, l, rem) case gltf.AccessorMat2: - return unsafe.Slice((*[2][2]uint16)(ptr), len(v)/8) + return reslice[[2][2]uint16](v, l, rem) case gltf.AccessorMat3: - return unsafe.Slice((*[3][3]uint16)(ptr), len(v)/18) + return reslice[[3][3]uint16](v, l, rem) case gltf.AccessorMat4: - return unsafe.Slice((*[4][4]uint16)(ptr), len(v)/32) + return reslice[[4][4]uint16](v, l, rem) } case gltf.ComponentShort: switch t { case gltf.AccessorScalar: - return unsafe.Slice((*int16)(ptr), len(v)/2) + return reslice[int16](v, l, rem) case gltf.AccessorVec2: - return unsafe.Slice((*[2]int16)(ptr), len(v)/4) + return reslice[[2]int16](v, l, rem) case gltf.AccessorVec3: - return unsafe.Slice((*[3]int16)(ptr), len(v)/6) + return reslice[[3]int16](v, l, rem) case gltf.AccessorVec4: - return unsafe.Slice((*[4]int16)(ptr), len(v)/8) + return reslice[[4]int16](v, l, rem) case gltf.AccessorMat2: - return unsafe.Slice((*[2][2]int16)(ptr), len(v)/8) + return reslice[[2][2]int16](v, l, rem) case gltf.AccessorMat3: - return unsafe.Slice((*[3][3]int16)(ptr), len(v)/18) + return reslice[[3][3]int16](v, l, rem) case gltf.AccessorMat4: - return unsafe.Slice((*[4][4]int16)(ptr), len(v)/32) + return reslice[[4][4]int16](v, l, rem) } case gltf.ComponentUint: switch t { case gltf.AccessorScalar: - return unsafe.Slice((*uint32)(ptr), len(v)/4) + return reslice[uint32](v, l, rem) case gltf.AccessorVec2: - return unsafe.Slice((*[2]uint32)(ptr), len(v)/8) + return reslice[[2]uint32](v, l, rem) case gltf.AccessorVec3: - return unsafe.Slice((*[3]uint32)(ptr), len(v)/12) + return reslice[[3]uint32](v, l, rem) case gltf.AccessorVec4: - return unsafe.Slice((*[4]uint32)(ptr), len(v)/16) + return reslice[[4]uint32](v, l, rem) case gltf.AccessorMat2: - return unsafe.Slice((*[2][2]uint32)(ptr), len(v)/16) + return reslice[[2][2]uint32](v, l, rem) case gltf.AccessorMat3: - return unsafe.Slice((*[3][3]uint32)(ptr), len(v)/36) + case gltf.AccessorMat4: - return unsafe.Slice((*[4][4]uint32)(ptr), len(v)/64) + return reslice[[4][4]uint32](v, l, rem) } case gltf.ComponentFloat: switch t { case gltf.AccessorScalar: - return unsafe.Slice((*float32)(ptr), len(v)/4) + return reslice[float32](v, l, rem) case gltf.AccessorVec2: - return unsafe.Slice((*[2]float32)(ptr), len(v)/8) + return reslice[[2]float32](v, l, rem) case gltf.AccessorVec3: - return unsafe.Slice((*[3]float32)(ptr), len(v)/12) + return reslice[[3]float32](v, l, rem) case gltf.AccessorVec4: - return unsafe.Slice((*[4]float32)(ptr), len(v)/16) + return reslice[[4]float32](v, l, rem) case gltf.AccessorMat2: - return unsafe.Slice((*[2][2]float32)(ptr), len(v)/16) + return reslice[[2][2]float32](v, l, rem) case gltf.AccessorMat3: - return unsafe.Slice((*[3][3]float32)(ptr), len(v)/36) + return reslice[[3][3]float32](v, l, rem) case gltf.AccessorMat4: - return unsafe.Slice((*[4][4]float32)(ptr), len(v)/64) + return reslice[[4][4]float32](v, l, rem) } } - return nil + panic(fmt.Errorf("gltf: unsupported component type %d or accessor type %d", c, t)) } // MakeSliceBuffer returns the slice type associated with c and t and with the given element count. // If the buffer is an slice which type matches with the expected by the acr then it will // be used as backing slice. func MakeSliceBuffer(c gltf.ComponentType, t gltf.AccessorType, count int, buffer []byte) (any, error) { - if len(buffer) == 0 { - return MakeSlice(c, t, count) + if err := checkAccessorType(c, t); err != nil { + return nil, err } - v := castSlice(c, t, buffer) - if v == nil { + buffer = buffer[:cap(buffer)] // Extend the slice to its capacity. + if len(buffer) == 0 { return MakeSlice(c, t, count) } - count1 := reflect.ValueOf(v).Len() - if count1 < count { - tmpSlice, _ := MakeSlice(c, t, count-count1) - return reflect.AppendSlice(reflect.ValueOf(v), reflect.ValueOf(tmpSlice)).Interface(), nil - } - if count1 > count { - return reflect.ValueOf(v).Slice(0, int(count)).Interface(), nil - } - return v, nil + return castSlice(c, t, count, buffer), nil } // MakeSlice returns the slice type associated with c and t and with the given element count. // For example, if c is gltf.ComponentFloat and t is gltf.AccessorVec3 // then MakeSlice(c, t, 5) is equivalent to make([][3]float32, 5). func MakeSlice(c gltf.ComponentType, t gltf.AccessorType, count int) (any, error) { + if err := checkAccessorType(c, t); err != nil { + return nil, err + } var tp reflect.Type switch c { case gltf.ComponentUbyte: @@ -162,7 +174,7 @@ func MakeSlice(c gltf.ComponentType, t gltf.AccessorType, count int) (any, error case gltf.ComponentFloat: tp = reflect.TypeOf((*float32)(nil)) default: - return nil, fmt.Errorf("gltf: unsupported component type %d", c) + panic(fmt.Errorf("gltf: unsupported component type %d", c)) } tp = tp.Elem() switch t { @@ -181,7 +193,7 @@ func MakeSlice(c gltf.ComponentType, t gltf.AccessorType, count int) (any, error case gltf.AccessorMat4: tp = reflect.ArrayOf(4, reflect.ArrayOf(4, tp)) default: - return nil, fmt.Errorf("gltf: unsupported accessor type %d", t) + panic(fmt.Errorf("gltf: unsupported accessor type %d", t)) } return reflect.MakeSlice(reflect.SliceOf(tp), count, count).Interface(), nil } @@ -284,3 +296,19 @@ func Type(data any) (c gltf.ComponentType, t gltf.AccessorType, count int) { } return } + +func checkAccessorType(c gltf.ComponentType, t gltf.AccessorType) error { + switch c { + case gltf.ComponentUbyte, gltf.ComponentByte, gltf.ComponentUshort, + gltf.ComponentShort, gltf.ComponentUint, gltf.ComponentFloat: + default: + return fmt.Errorf("gltf: unsupported component type %d", c) + } + switch t { + case gltf.AccessorScalar, gltf.AccessorVec2, gltf.AccessorVec3, + gltf.AccessorVec4, gltf.AccessorMat2, gltf.AccessorMat3, gltf.AccessorMat4: + default: + return fmt.Errorf("gltf: unsupported accessor type %d", t) + } + return nil +} diff --git a/modeler/bench_test.go b/modeler/bench_test.go index a5fe5c5..bec95ce 100644 --- a/modeler/bench_test.go +++ b/modeler/bench_test.go @@ -36,6 +36,8 @@ func BenchmarkReadAccessorSparse(b *testing.B) { Values: gltf.SparseValues{BufferView: 2}, }, } + b.ResetTimer() + b.ReportAllocs() for i := 0; i < b.N; i++ { _, err := modeler.ReadAccessor(doc, acr, nil) if err != nil { @@ -43,3 +45,85 @@ func BenchmarkReadAccessorSparse(b *testing.B) { } } } + +func BenchmarkReadAccessorBuffer(b *testing.B) { + doc := &gltf.Document{ + Buffers: []*gltf.Buffer{{ByteLength: 52, Data: []byte{ + 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, + 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, + 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, + 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, + }}}, BufferViews: []*gltf.BufferView{{Buffer: 0, ByteLength: 48}}, + } + acr := &gltf.Accessor{ + BufferView: gltf.Index(0), ComponentType: gltf.ComponentFloat, Type: gltf.AccessorVec3, Count: 4, + } + buf := make([]byte, 48) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, err := modeler.ReadAccessor(doc, acr, buf) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkReadAccessor(b *testing.B) { + doc := &gltf.Document{ + Buffers: []*gltf.Buffer{{ByteLength: 52, Data: []byte{ + 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, + 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, + 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, + 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, + }}}, BufferViews: []*gltf.BufferView{{Buffer: 0, ByteLength: 48}}, + } + acr := &gltf.Accessor{ + BufferView: gltf.Index(0), ComponentType: gltf.ComponentFloat, Type: gltf.AccessorVec3, Count: 4, + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, err := modeler.ReadAccessor(doc, acr, nil) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkReadIndices(b *testing.B) { + doc := &gltf.Document{ + Buffers: []*gltf.Buffer{{ByteLength: 8, Data: []byte{1, 0, 0, 0, 2, 0, 0, 0}}}, + BufferViews: []*gltf.BufferView{{Buffer: 0, ByteLength: 8}}, + } + acr := &gltf.Accessor{ + BufferView: gltf.Index(0), ComponentType: gltf.ComponentUint, Type: gltf.AccessorScalar, Count: 2, + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, err := modeler.ReadIndices(doc, acr, nil) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkReadIndicesBuffer(b *testing.B) { + doc := &gltf.Document{ + Buffers: []*gltf.Buffer{{ByteLength: 8, Data: []byte{1, 0, 0, 0, 2, 0, 0, 0}}}, + BufferViews: []*gltf.BufferView{{Buffer: 0, ByteLength: 8}}, + } + acr := &gltf.Accessor{ + BufferView: gltf.Index(0), ComponentType: gltf.ComponentUint, Type: gltf.AccessorScalar, Count: 2, + } + buf := make([]uint32, 6) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, err := modeler.ReadIndices(doc, acr, buf) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/modeler/read_test.go b/modeler/read_test.go index 585252f..79f7de7 100644 --- a/modeler/read_test.go +++ b/modeler/read_test.go @@ -216,10 +216,10 @@ func TestReadAccessorAllocs(t *testing.T) { }) t.Run("2", func(t *testing.T) { buf := make([]byte, 24) - testFunc(t, buf, 6) - testFunc(t, buf, 6) - testFunc(t, buf, 6) - testFunc(t, buf, 6) + testFunc(t, buf, 1) + testFunc(t, buf, 1) + testFunc(t, buf, 1) + testFunc(t, buf, 1) }) t.Run("4", func(t *testing.T) { buf := make([]byte, 48)