Benchmarking Golang Slice Allocation

— 7 minute read

Photo by Vincent Botta on Unsplash
Photo by Vincent Botta on Unsplash

I was read­ing The Impact of Pre-allocating Slice Memory on Performance in Golang and was cu­ri­ous about the bench­mark, so I wanted to try it too for Autograd.

Slice pre­al­lo­ca­tion means we spec­ify the len when cre­at­ing the slice and it will cre­ate a slice with n-empty items on it. Then, to as­sign a value to the slice, we need to use the in­dex. Example of pre­al­loc a userIDs

userIDs := make([]uuid.UUID, len(assignments))
for i, assignment := range assignments {
userIDs[i] = assignment.AssignedBy
}

In Autograd, there is a page where the ad­min can see a list of as­sign­ments. This page typ­i­cally con­tains 10-20 as­sign­ments with pag­i­na­tion. Then, I was cu­ri­ous if there is an im­pact if we pre­al­lo­cate for this slice of struct, since in the ar­ti­cle, the writer did­n’t ex­plain the struct lay­out. In the struct I tested, it has 8 fields, and 3 of them are also structs. In my hon­est opin­ion, this struct can rep­re­sent many back­end ser­vice codes.

var asg = assignments.Assignment{
ID: uuid.New(),
Name: "Assignment 1",
Description: "Description 1",
DeadlineAt: time.Now(),
Assigner: assignments.Assigner{
ID: uuid.New(),
Name: "Assigner 1",
Active: true,
},
CaseInputFile: assignments.CaseFile{
ID: uuid.New(),
URL: "http://example.com",
Type: "input",
TimestampMetadata: core.TimestampMetadata{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
DeletedAt: null.NewTime(time.Now(), false),
},
},
CaseOutputFile: assignments.CaseFile{
ID: uuid.New(),
URL: "http://example.com",
Type: "input",
TimestampMetadata: core.TimestampMetadata{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
DeletedAt: null.NewTime(time.Now(), false),
},
},
TimestampMetadata: core.NewTimestampMeta(time.Time{}),
}

For the bench­mark, I want to check for lengths of 10, 20, and 50 slices to rep­re­sent a real sce­nario. The re­sult is in­ter­est­ing, where the pre­al­lo­ca­tion has a big per­for­mance boost of 1000x! You can check the bench­mark here.

go test -benchmem -bench . github.com/fahmifan/autograd/pkg/core/assignments
goos: darwin
goarch: arm64
pkg: github.com/fahmifan/autograd/pkg/core/assignments
BenchmarkAssignmentNoPrealloc_10-8 363208 3327 ns/op 15808 B/op 5 allocs/op
BenchmarkAssignmentPrealloc_10-8 165713173 7.198 ns/op 0 B/op 0 allocs/op

BenchmarkAssignmentNoPrealloc_20-8 177516 6080 ns/op 32192 B/op 6 allocs/op
BenchmarkAssignmentPrealloc_20-8 96115022 12.32 ns/op 0 B/op 0 allocs/op

BenchmarkAssignmentNoPrealloc_50-8 99542 12601 ns/op 64960 B/op 7 allocs/op
BenchmarkAssignmentPrealloc_50-8 43292256 27.91 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/fahmifan/autograd/pkg/core/assignments 8.980s

So, it’s highly rec­om­mended to al­ways pre­al­lo­cate a slice if we know the size ahead of time. The over­head is small, but the per­for­mance boost is sig­nif­i­cant.