From db74fec9433391009131b599c686baf638f55844 Mon Sep 17 00:00:00 2001 From: Edward McFarlane Date: Thu, 5 Sep 2024 20:11:39 -0400 Subject: [PATCH 1/9] Fix descriptor.Table buffer growth Fixes InsertAt size calculation to avoid growing slices more than needed. On insert the mask should calculate the index in the slice not the number of elements in the table. Avoids a factor of 8 increase in table size. Small optimisation to use the capcity of the slice to reduce the average length whilst keeping allocations small. Clarified the grow method. Signed-off-by: Edward McFarlane --- internal/descriptor/table.go | 39 +++++++++++++++--------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/internal/descriptor/table.go b/internal/descriptor/table.go index 03761e6ec4..d13048b04f 100644 --- a/internal/descriptor/table.go +++ b/internal/descriptor/table.go @@ -37,23 +37,19 @@ func (t *Table[Key, Item]) Len() (n int) { return n } -// grow ensures that t has enough room for n items, potentially reallocating the -// internal buffers if their capacity was too small to hold this many items. +// grow grows the table by n * 64 items. func (t *Table[Key, Item]) grow(n int) { - // Round up to a multiple of 64 since this is the smallest increment due to - // using 64 bits masks. - n = (n*64 + 63) / 64 - - if n > len(t.masks) { - masks := make([]uint64, n) - copy(masks, t.masks) - - items := make([]Item, n*64) - copy(items, t.items) + total := len(t.masks) + n + if total > cap(t.masks) { + t.masks = append(t.masks, make([]uint64, n)...) + } + t.masks = t.masks[:total] - t.masks = masks - t.items = items + total = len(t.items) + n*64 + if total > cap(t.items) { + t.items = append(t.items, make([]Item, n*64)...) } + t.items = t.items[:total] } // Insert inserts the given item to the table, returning the key that it is @@ -78,13 +74,9 @@ insert: } } + // No free slot found, grow the table and retry. offset = len(t.masks) - n := 2 * len(t.masks) - if n == 0 { - n = 1 - } - - t.grow(n) + t.grow(1) goto insert } @@ -109,10 +101,10 @@ func (t *Table[Key, Item]) InsertAt(item Item, key Key) bool { if key < 0 { return false } - if diff := int(key) - t.Len(); diff > 0 { + index := uint(key) / 64 + if diff := int(index) - len(t.masks) + 1; diff > 0 { t.grow(diff) } - index := uint(key) / 64 shift := uint(key) % 64 t.masks[index] |= 1 << shift t.items[key] = item @@ -124,7 +116,8 @@ func (t *Table[Key, Item]) Delete(key Key) { if key < 0 { // invalid key return } - if index, shift := key/64, key%64; int(index) < len(t.masks) { + if index := uint(key) / 64; int(index) < len(t.masks) { + shift := uint(key) % 64 mask := t.masks[index] if (mask & (1 << shift)) != 0 { var zero Item From 965f793f76310ff17226580b88a77aa2a5998113 Mon Sep 17 00:00:00 2001 From: Edward McFarlane Date: Thu, 5 Sep 2024 23:51:18 -0400 Subject: [PATCH 2/9] Rename sys pkg test Signed-off-by: Edward McFarlane --- internal/descriptor/{table_test.go => table_sys_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename internal/descriptor/{table_test.go => table_sys_test.go} (100%) diff --git a/internal/descriptor/table_test.go b/internal/descriptor/table_sys_test.go similarity index 100% rename from internal/descriptor/table_test.go rename to internal/descriptor/table_sys_test.go From 4d84665212daf0bc99b96f6bbe37aa105db0d5b3 Mon Sep 17 00:00:00 2001 From: Edward McFarlane Date: Thu, 5 Sep 2024 23:52:44 -0400 Subject: [PATCH 3/9] Add unit test for table behaviour Signed-off-by: Edward McFarlane --- internal/descriptor/table_test.go | 86 +++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 internal/descriptor/table_test.go diff --git a/internal/descriptor/table_test.go b/internal/descriptor/table_test.go new file mode 100644 index 0000000000..6f347f8959 --- /dev/null +++ b/internal/descriptor/table_test.go @@ -0,0 +1,86 @@ +package descriptor + +import ( + "testing" + + "github.com/tetratelabs/wazero/internal/testing/require" +) + +func Test_sizeOfTable(t *testing.T) { + tests := []struct { + name string + operation func(*Table[int32, string]) + expectedSize int + }{ + { + name: "empty table", + operation: func(table *Table[int32, string]) {}, + expectedSize: 0, + }, + { + name: "1 insert", + operation: func(table *Table[int32, string]) { + table.Insert("a") + }, + expectedSize: 1, + }, + { + name: "32 inserts", + operation: func(table *Table[int32, string]) { + for i := 0; i < 32; i++ { + table.Insert("a") + } + }, + expectedSize: 1, + }, + { + name: "257 inserts", + operation: func(table *Table[int32, string]) { + for i := 0; i < 257; i++ { + table.Insert("a") + } + }, + expectedSize: 5, + }, + { + name: "1 insert at 63", + operation: func(table *Table[int32, string]) { + table.InsertAt("a", 63) + }, + expectedSize: 1, + }, + { + name: "1 insert at 64", + operation: func(table *Table[int32, string]) { + table.InsertAt("a", 64) + }, + expectedSize: 2, + }, + { + name: "1 insert at 257", + operation: func(table *Table[int32, string]) { + table.InsertAt("a", 257) + }, + expectedSize: 5, + }, + { + name: "insert at until 320", + operation: func(table *Table[int32, string]) { + for i := int32(0); i < 320; i++ { + table.InsertAt("a", i) + } + }, + expectedSize: 5, + }, + } + for _, tt := range tests { + tc := tt + + t.Run(tc.name, func(t *testing.T) { + table := new(Table[int32, string]) + tc.operation(table) + require.Equal(t, tc.expectedSize, len(table.masks)) + require.Equal(t, tc.expectedSize*64, len(table.items)) + }) + } +} From c1cb20a04abd365623c3b5c9d3247f1bb1d72d3f Mon Sep 17 00:00:00 2001 From: Edward McFarlane Date: Fri, 6 Sep 2024 00:13:53 -0400 Subject: [PATCH 4/9] Remove bench, cleanup test checks Signed-off-by: Edward McFarlane --- internal/descriptor/table_sys_test.go | 72 +++++---------------------- 1 file changed, 12 insertions(+), 60 deletions(-) diff --git a/internal/descriptor/table_sys_test.go b/internal/descriptor/table_sys_test.go index 9713a27ad2..7f33a4cb31 100644 --- a/internal/descriptor/table_sys_test.go +++ b/internal/descriptor/table_sys_test.go @@ -10,9 +10,8 @@ import ( func TestFileTable(t *testing.T) { table := new(sys.FileTable) - if n := table.Len(); n != 0 { - t.Errorf("new table is not empty: length=%d", n) - } + n := table.Len() + require.Equal(t, 0, n, "new table is not empty: length=%d", n) // The id field is used as a sentinel value. v0 := &sys.FileEntry{Name: "1"} @@ -38,16 +37,12 @@ func TestFileTable(t *testing.T) { {key: k1, val: v1}, {key: k2, val: v2}, } { - if v, ok := table.Lookup(lookup.key); !ok { - t.Errorf("value not found for key '%v'", lookup.key) - } else if v.Name != lookup.val.Name { - t.Errorf("wrong value returned for key '%v': want=%v got=%v", lookup.key, lookup.val.Name, v.Name) - } + v, ok := table.Lookup(lookup.key) + require.True(t, ok, "value not found for key '%v'", lookup.key) + require.Equal(t, lookup.val.Name, v.Name, "wrong value returned for key '%v'", lookup.key) } - if n := table.Len(); n != 3 { - t.Errorf("wrong table length: want=3 got=%d", n) - } + require.Equal(t, 3, table.Len(), "wrong table length: want=3 got=%d", table.Len()) k0Found := false k1Found := false @@ -62,9 +57,7 @@ func TestFileTable(t *testing.T) { case k2: k2Found, want = true, v2 } - if v.Name != want.Name { - t.Errorf("wrong value found ranging over '%v': want=%v got=%v", k, want.Name, v.Name) - } + require.Equal(t, want.Name, v.Name, "wrong value found ranging over table") return true }) @@ -76,9 +69,7 @@ func TestFileTable(t *testing.T) { {key: k1, ok: k1Found}, {key: k2, ok: k2Found}, } { - if !found.ok { - t.Errorf("key not found while ranging over table: %v", found.key) - } + require.True(t, found.ok, "key not found while ranging over table: %v", found.key) } for i, deletion := range []struct { @@ -89,48 +80,9 @@ func TestFileTable(t *testing.T) { {key: k2}, } { table.Delete(deletion.key) - if _, ok := table.Lookup(deletion.key); ok { - t.Errorf("item found after deletion of '%v'", deletion.key) - } - if n, want := table.Len(), 3-(i+1); n != want { - t.Errorf("wrong table length after deletion: want=%d got=%d", want, n) - } - } -} - -func BenchmarkFileTableInsert(b *testing.B) { - table := new(sys.FileTable) - entry := new(sys.FileEntry) - - for i := 0; i < b.N; i++ { - table.Insert(entry) - - if (i % 65536) == 0 { - table.Reset() // to avoid running out of memory - } - } -} - -func BenchmarkFileTableLookup(b *testing.B) { - const sentinel = "42" - const numFiles = 65536 - table := new(sys.FileTable) - files := make([]int32, numFiles) - entry := &sys.FileEntry{Name: sentinel} - - var ok bool - for i := range files { - files[i], ok = table.Insert(entry) - if !ok { - b.Fatal("unexpected failure to insert") - } - } - - var f *sys.FileEntry - for i := 0; i < b.N; i++ { - f, _ = table.Lookup(files[i%numFiles]) - } - if f.Name != sentinel { - b.Error("wrong file returned by lookup") + _, ok := table.Lookup(deletion.key) + require.False(t, ok, "item found after deletion of '%v'", deletion.key) + n, want := table.Len(), 3-(i+1) + require.Equal(t, want, n, "wrong table length after deletion: want=%d got=%d", want, n) } } From 976d0e95f2c622f8703431790cc23814aa463f33 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Tue, 17 Sep 2024 10:02:06 +0900 Subject: [PATCH 5/9] use export_test pattern Signed-off-by: Takeshi Yoneda --- internal/descriptor/export_test.go | 11 +++ internal/descriptor/table_sys_test.go | 88 --------------------- internal/descriptor/table_test.go | 108 ++++++++++++++++++++++---- 3 files changed, 106 insertions(+), 101 deletions(-) create mode 100644 internal/descriptor/export_test.go delete mode 100644 internal/descriptor/table_sys_test.go diff --git a/internal/descriptor/export_test.go b/internal/descriptor/export_test.go new file mode 100644 index 0000000000..069eb99138 --- /dev/null +++ b/internal/descriptor/export_test.go @@ -0,0 +1,11 @@ +package descriptor + +// Masks returns the masks of the table for testing purposes. +func (t *Table[int32, string]) Masks() []uint64 { + return t.masks +} + +// Items returns the items of the table for testing purposes. +func (t *Table[int32, string]) Items() []string { + return t.items +} diff --git a/internal/descriptor/table_sys_test.go b/internal/descriptor/table_sys_test.go deleted file mode 100644 index 7f33a4cb31..0000000000 --- a/internal/descriptor/table_sys_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package descriptor_test - -import ( - "testing" - - "github.com/tetratelabs/wazero/internal/sys" - "github.com/tetratelabs/wazero/internal/testing/require" -) - -func TestFileTable(t *testing.T) { - table := new(sys.FileTable) - - n := table.Len() - require.Equal(t, 0, n, "new table is not empty: length=%d", n) - - // The id field is used as a sentinel value. - v0 := &sys.FileEntry{Name: "1"} - v1 := &sys.FileEntry{Name: "2"} - v2 := &sys.FileEntry{Name: "3"} - - k0, ok := table.Insert(v0) - require.True(t, ok) - k1, ok := table.Insert(v1) - require.True(t, ok) - k2, ok := table.Insert(v2) - require.True(t, ok) - - // Try to re-order, but to an invalid value - ok = table.InsertAt(v2, -1) - require.False(t, ok) - - for _, lookup := range []struct { - key int32 - val *sys.FileEntry - }{ - {key: k0, val: v0}, - {key: k1, val: v1}, - {key: k2, val: v2}, - } { - v, ok := table.Lookup(lookup.key) - require.True(t, ok, "value not found for key '%v'", lookup.key) - require.Equal(t, lookup.val.Name, v.Name, "wrong value returned for key '%v'", lookup.key) - } - - require.Equal(t, 3, table.Len(), "wrong table length: want=3 got=%d", table.Len()) - - k0Found := false - k1Found := false - k2Found := false - table.Range(func(k int32, v *sys.FileEntry) bool { - var want *sys.FileEntry - switch k { - case k0: - k0Found, want = true, v0 - case k1: - k1Found, want = true, v1 - case k2: - k2Found, want = true, v2 - } - require.Equal(t, want.Name, v.Name, "wrong value found ranging over table") - return true - }) - - for _, found := range []struct { - key int32 - ok bool - }{ - {key: k0, ok: k0Found}, - {key: k1, ok: k1Found}, - {key: k2, ok: k2Found}, - } { - require.True(t, found.ok, "key not found while ranging over table: %v", found.key) - } - - for i, deletion := range []struct { - key int32 - }{ - {key: k1}, - {key: k0}, - {key: k2}, - } { - table.Delete(deletion.key) - _, ok := table.Lookup(deletion.key) - require.False(t, ok, "item found after deletion of '%v'", deletion.key) - n, want := table.Len(), 3-(i+1) - require.Equal(t, want, n, "wrong table length after deletion: want=%d got=%d", want, n) - } -} diff --git a/internal/descriptor/table_test.go b/internal/descriptor/table_test.go index 6f347f8959..c765ca5768 100644 --- a/internal/descriptor/table_test.go +++ b/internal/descriptor/table_test.go @@ -1,32 +1,114 @@ -package descriptor +package descriptor_test import ( + "github.com/tetratelabs/wazero/internal/descriptor" + "github.com/tetratelabs/wazero/internal/sys" "testing" "github.com/tetratelabs/wazero/internal/testing/require" ) +func TestFileTable(t *testing.T) { + table := new(sys.FileTable) + + n := table.Len() + require.Equal(t, 0, n, "new table is not empty: length=%d", n) + + // The id field is used as a sentinel value. + v0 := &sys.FileEntry{Name: "1"} + v1 := &sys.FileEntry{Name: "2"} + v2 := &sys.FileEntry{Name: "3"} + + k0, ok := table.Insert(v0) + require.True(t, ok) + k1, ok := table.Insert(v1) + require.True(t, ok) + k2, ok := table.Insert(v2) + require.True(t, ok) + + // Try to re-order, but to an invalid value + ok = table.InsertAt(v2, -1) + require.False(t, ok) + + for _, lookup := range []struct { + key int32 + val *sys.FileEntry + }{ + {key: k0, val: v0}, + {key: k1, val: v1}, + {key: k2, val: v2}, + } { + v, ok := table.Lookup(lookup.key) + require.True(t, ok, "value not found for key '%v'", lookup.key) + require.Equal(t, lookup.val.Name, v.Name, "wrong value returned for key '%v'", lookup.key) + } + + require.Equal(t, 3, table.Len(), "wrong table length: want=3 got=%d", table.Len()) + + k0Found := false + k1Found := false + k2Found := false + table.Range(func(k int32, v *sys.FileEntry) bool { + var want *sys.FileEntry + switch k { + case k0: + k0Found, want = true, v0 + case k1: + k1Found, want = true, v1 + case k2: + k2Found, want = true, v2 + } + require.Equal(t, want.Name, v.Name, "wrong value found ranging over table") + return true + }) + + for _, found := range []struct { + key int32 + ok bool + }{ + {key: k0, ok: k0Found}, + {key: k1, ok: k1Found}, + {key: k2, ok: k2Found}, + } { + require.True(t, found.ok, "key not found while ranging over table: %v", found.key) + } + + for i, deletion := range []struct { + key int32 + }{ + {key: k1}, + {key: k0}, + {key: k2}, + } { + table.Delete(deletion.key) + _, ok := table.Lookup(deletion.key) + require.False(t, ok, "item found after deletion of '%v'", deletion.key) + n, want := table.Len(), 3-(i+1) + require.Equal(t, want, n, "wrong table length after deletion: want=%d got=%d", want, n) + } +} + func Test_sizeOfTable(t *testing.T) { tests := []struct { name string - operation func(*Table[int32, string]) + operation func(*descriptor.Table[int32, string]) expectedSize int }{ { name: "empty table", - operation: func(table *Table[int32, string]) {}, + operation: func(table *descriptor.Table[int32, string]) {}, expectedSize: 0, }, { name: "1 insert", - operation: func(table *Table[int32, string]) { + operation: func(table *descriptor.Table[int32, string]) { table.Insert("a") }, expectedSize: 1, }, { name: "32 inserts", - operation: func(table *Table[int32, string]) { + operation: func(table *descriptor.Table[int32, string]) { for i := 0; i < 32; i++ { table.Insert("a") } @@ -35,7 +117,7 @@ func Test_sizeOfTable(t *testing.T) { }, { name: "257 inserts", - operation: func(table *Table[int32, string]) { + operation: func(table *descriptor.Table[int32, string]) { for i := 0; i < 257; i++ { table.Insert("a") } @@ -44,28 +126,28 @@ func Test_sizeOfTable(t *testing.T) { }, { name: "1 insert at 63", - operation: func(table *Table[int32, string]) { + operation: func(table *descriptor.Table[int32, string]) { table.InsertAt("a", 63) }, expectedSize: 1, }, { name: "1 insert at 64", - operation: func(table *Table[int32, string]) { + operation: func(table *descriptor.Table[int32, string]) { table.InsertAt("a", 64) }, expectedSize: 2, }, { name: "1 insert at 257", - operation: func(table *Table[int32, string]) { + operation: func(table *descriptor.Table[int32, string]) { table.InsertAt("a", 257) }, expectedSize: 5, }, { name: "insert at until 320", - operation: func(table *Table[int32, string]) { + operation: func(table *descriptor.Table[int32, string]) { for i := int32(0); i < 320; i++ { table.InsertAt("a", i) } @@ -77,10 +159,10 @@ func Test_sizeOfTable(t *testing.T) { tc := tt t.Run(tc.name, func(t *testing.T) { - table := new(Table[int32, string]) + table := new(descriptor.Table[int32, string]) tc.operation(table) - require.Equal(t, tc.expectedSize, len(table.masks)) - require.Equal(t, tc.expectedSize*64, len(table.items)) + require.Equal(t, tc.expectedSize, len(table.Masks())) + require.Equal(t, tc.expectedSize*64, len(table.Items())) }) } } From 7e9c309c964b2b6e42afc9b4925c9550464c49f8 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Tue, 17 Sep 2024 10:04:42 +0900 Subject: [PATCH 6/9] fmt Signed-off-by: Takeshi Yoneda --- internal/descriptor/table_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/descriptor/table_test.go b/internal/descriptor/table_test.go index c765ca5768..906b26a367 100644 --- a/internal/descriptor/table_test.go +++ b/internal/descriptor/table_test.go @@ -1,10 +1,10 @@ package descriptor_test import ( - "github.com/tetratelabs/wazero/internal/descriptor" - "github.com/tetratelabs/wazero/internal/sys" "testing" + "github.com/tetratelabs/wazero/internal/descriptor" + "github.com/tetratelabs/wazero/internal/sys" "github.com/tetratelabs/wazero/internal/testing/require" ) From 0f5e0fedb3a449fbf82b54ae120dc0ed133a4e63 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Tue, 17 Sep 2024 10:08:20 +0900 Subject: [PATCH 7/9] generic Signed-off-by: Takeshi Yoneda --- internal/descriptor/export_test.go | 4 ++-- internal/descriptor/table_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/descriptor/export_test.go b/internal/descriptor/export_test.go index 069eb99138..1a2d377dda 100644 --- a/internal/descriptor/export_test.go +++ b/internal/descriptor/export_test.go @@ -1,11 +1,11 @@ package descriptor // Masks returns the masks of the table for testing purposes. -func (t *Table[int32, string]) Masks() []uint64 { +func Masks[Key ~int32, Item any](t *Table[Key, Item]) []uint64 { return t.masks } // Items returns the items of the table for testing purposes. -func (t *Table[int32, string]) Items() []string { +func Items[Key ~int32, Item any](t *Table[Key, Item]) []Item { return t.items } diff --git a/internal/descriptor/table_test.go b/internal/descriptor/table_test.go index 906b26a367..91e7061b79 100644 --- a/internal/descriptor/table_test.go +++ b/internal/descriptor/table_test.go @@ -161,8 +161,8 @@ func Test_sizeOfTable(t *testing.T) { t.Run(tc.name, func(t *testing.T) { table := new(descriptor.Table[int32, string]) tc.operation(table) - require.Equal(t, tc.expectedSize, len(table.Masks())) - require.Equal(t, tc.expectedSize*64, len(table.Items())) + require.Equal(t, tc.expectedSize, len(descriptor.Masks(table))) + require.Equal(t, tc.expectedSize*64, len(descriptor.Items(table))) }) } } From cc823461fcefd294d2a7d35ff4bf08679fdc8cdb Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Tue, 17 Sep 2024 10:10:45 +0900 Subject: [PATCH 8/9] more Signed-off-by: Takeshi Yoneda --- internal/descriptor/table_test.go | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/internal/descriptor/table_test.go b/internal/descriptor/table_test.go index 91e7061b79..1b44c4e753 100644 --- a/internal/descriptor/table_test.go +++ b/internal/descriptor/table_test.go @@ -88,6 +88,43 @@ func TestFileTable(t *testing.T) { } } +func BenchmarkFileTableInsert(b *testing.B) { + table := new(sys.FileTable) + entry := new(sys.FileEntry) + + for i := 0; i < b.N; i++ { + table.Insert(entry) + + if (i % 65536) == 0 { + table.Reset() // to avoid running out of memory + } + } +} + +func BenchmarkFileTableLookup(b *testing.B) { + const sentinel = "42" + const numFiles = 65536 + table := new(sys.FileTable) + files := make([]int32, numFiles) + entry := &sys.FileEntry{Name: sentinel} + + var ok bool + for i := range files { + files[i], ok = table.Insert(entry) + if !ok { + b.Fatal("unexpected failure to insert") + } + } + + var f *sys.FileEntry + for i := 0; i < b.N; i++ { + f, _ = table.Lookup(files[i%numFiles]) + } + if f.Name != sentinel { + b.Error("wrong file returned by lookup") + } +} + func Test_sizeOfTable(t *testing.T) { tests := []struct { name string From 24ef2a55b8143b029df7bf9363e2e5f357ee20c9 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Wed, 25 Sep 2024 12:20:23 +0100 Subject: [PATCH 9/9] Grow. Signed-off-by: Nuno Cruces --- internal/descriptor/table.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/internal/descriptor/table.go b/internal/descriptor/table.go index d13048b04f..a20e19100a 100644 --- a/internal/descriptor/table.go +++ b/internal/descriptor/table.go @@ -1,6 +1,9 @@ package descriptor -import "math/bits" +import ( + "math/bits" + "slices" +) // Table is a data structure mapping 32 bit descriptor to items. // @@ -40,16 +43,10 @@ func (t *Table[Key, Item]) Len() (n int) { // grow grows the table by n * 64 items. func (t *Table[Key, Item]) grow(n int) { total := len(t.masks) + n - if total > cap(t.masks) { - t.masks = append(t.masks, make([]uint64, n)...) - } - t.masks = t.masks[:total] + t.masks = slices.Grow(t.masks, n)[:total] total = len(t.items) + n*64 - if total > cap(t.items) { - t.items = append(t.items, make([]Item, n*64)...) - } - t.items = t.items[:total] + t.items = slices.Grow(t.items, n*64)[:total] } // Insert inserts the given item to the table, returning the key that it is