Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce modeler.Read helpers allocations #90

Merged
merged 5 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 86 additions & 59 deletions binary/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,144 +9,155 @@ 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
}

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 {
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:
Expand All @@ -162,7 +173,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 {
Expand All @@ -181,7 +192,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
}
Expand Down Expand Up @@ -284,3 +295,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
}
84 changes: 84 additions & 0 deletions modeler/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,94 @@ 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 {
b.Fatal(err)
}
}
}

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)
}
}
}
12 changes: 6 additions & 6 deletions modeler/read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,10 @@ func TestReadAccessorAllocs(t *testing.T) {
}

testFunc := func(t *testing.T, buf []byte, want float32) {
allocs := testing.AllocsPerRun(10, func() {
allocs := testing.AllocsPerRun(50, func() {
modeler.ReadAccessor(doc, acr, buf)
})
if allocs != float64(want) {
if allocs > float64(want) {
t.Errorf("ReadAccessor expected %v allocs got %v", want, allocs)
}

Expand All @@ -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, 3)
testFunc(t, buf, 3)
testFunc(t, buf, 3)
testFunc(t, buf, 3)
})
t.Run("4", func(t *testing.T) {
buf := make([]byte, 48)
Expand Down
Loading