Created
April 29, 2023 08:54
-
-
Save jupj/e79d7f29c291fb470c35aae3642bcade to your computer and use it in GitHub Desktop.
Benchmark for data-oriented design part 2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"math/rand" | |
"testing" | |
"unsafe" | |
) | |
const ( | |
N = 1000000 | |
MB = 1024 * 1024 | |
) | |
type user struct { | |
ID int | |
IsActive bool | |
Name string | |
Username string | |
PasswordHash []byte | |
MustChangePassword bool | |
Email string | |
EmailConfirmed bool | |
} | |
func getUnconfirmedEmailAddresses(users []user) []string { | |
var unconfirmed []string | |
for _, u := range users { | |
if !u.EmailConfirmed { | |
unconfirmed = append(unconfirmed, u.Email) | |
} | |
} | |
return unconfirmed | |
} | |
func getUnconfirmedEmailAddressesRef(users []*user) []string { | |
var unconfirmed []string | |
for _, u := range users { | |
if !u.EmailConfirmed { | |
unconfirmed = append(unconfirmed, u.Email) | |
} | |
} | |
return unconfirmed | |
} | |
type userData struct { | |
ID []int | |
IsActive []bool | |
Name []string | |
Username []string | |
PasswordHash [][]byte | |
MustChangePassword []bool | |
Email []string | |
EmailConfirmed []bool | |
} | |
func (u userData) getUnconfirmedEmailAddresses() []string { | |
var unconfirmed []string | |
for i, confirmed := range u.EmailConfirmed { | |
if !confirmed { | |
unconfirmed = append(unconfirmed, u.Email[i]) | |
} | |
} | |
return unconfirmed | |
} | |
type userRefData struct { | |
ID []*int | |
IsActive []*bool | |
Name []*string | |
Username []*string | |
PasswordHash []*[]byte | |
MustChangePassword []*bool | |
Email []*string | |
EmailConfirmed []*bool | |
} | |
func (u userRefData) getUnconfirmedEmailAddresses() []string { | |
var unconfirmed []string | |
for i, confirmed := range u.EmailConfirmed { | |
if !*confirmed { | |
unconfirmed = append(unconfirmed, *u.Email[i]) | |
} | |
} | |
return unconfirmed | |
} | |
// BenchmarkValueTypes benchmarks arrays of value types. | |
// Benchmark lists the email addresses that haven't been confirmed | |
func BenchmarkValueTypes(b *testing.B) { | |
var userArray []user | |
var usersStruct userData | |
// Initialize user data | |
for i := 0; i < N; i++ { | |
// Generate random password hash | |
passwordHash := make([]byte, 60) | |
rand.Read(passwordHash) | |
u := user{ | |
ID: i, | |
// 95 % of users are not active | |
IsActive: rand.Int()%100 < 95, | |
Name: fmt.Sprintf("User%.4d", i), | |
Username: fmt.Sprintf("account%.4d", i), | |
PasswordHash: passwordHash, | |
// 1 % of users must change password | |
MustChangePassword: rand.Int()%100 == 0, | |
Email: fmt.Sprintf("user.%.4d@example.com", i), | |
// 99 % of users have confirmed their email address | |
EmailConfirmed: rand.Int()%100 < 99, | |
} | |
userArray = append(userArray, u) | |
usersStruct.ID = append(usersStruct.ID, u.ID) | |
usersStruct.IsActive = append(usersStruct.IsActive, u.IsActive) | |
usersStruct.Name = append(usersStruct.Name, u.Name) | |
usersStruct.Username = append(usersStruct.Username, u.Username) | |
usersStruct.PasswordHash = append(usersStruct.PasswordHash, u.PasswordHash) | |
usersStruct.MustChangePassword = append(usersStruct.MustChangePassword, u.MustChangePassword) | |
usersStruct.Email = append(usersStruct.Email, u.Email) | |
usersStruct.EmailConfirmed = append(usersStruct.EmailConfirmed, u.EmailConfirmed) | |
} | |
// Calculate sizes of data types | |
userStructSize := int(unsafe.Sizeof(user{})) | |
if userStructSize%int(unsafe.Alignof(user{})) != 0 { | |
// Add padding | |
userStructSize += int(unsafe.Alignof(user{})) - (userStructSize % int(unsafe.Alignof(user{}))) | |
} | |
boolSize := int(unsafe.Sizeof(false)) | |
stringSize := int(unsafe.Sizeof("")) | |
b.Logf("userStructSize: %dB, boolSize: %dB, stringSize: %dB\n", userStructSize, boolSize, stringSize) | |
b.Run("array-of-structs", func(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
getUnconfirmedEmailAddresses(userArray) | |
} | |
b.SetBytes(int64(userStructSize * len(userArray))) | |
b.ReportMetric(float64(userStructSize*len(userArray))/MB, "MB/op") | |
}) | |
b.Run("struct-of-arrays", func(b *testing.B) { | |
// Run function once to get number of emails to account for | |
unconfirmed := usersStruct.getUnconfirmedEmailAddresses() | |
b.ResetTimer() | |
for i := 0; i < b.N; i++ { | |
usersStruct.getUnconfirmedEmailAddresses() | |
} | |
b.SetBytes(int64(boolSize*len(usersStruct.EmailConfirmed) + stringSize*len(unconfirmed))) | |
b.ReportMetric(float64(boolSize*len(usersStruct.EmailConfirmed)+stringSize*len(unconfirmed))/MB, "MB/op") | |
}) | |
} | |
// BenchmarkRefTypes benchmarks arrays of reference types. | |
// Benchmark lists the email addresses that haven't been confirmed | |
func BenchmarkRefTypes(b *testing.B) { | |
var userArray []*user | |
var usersStruct userRefData | |
// Initialize user data | |
for i := 0; i < N; i++ { | |
// Generate random password hash | |
passwordHash := make([]byte, 60) | |
rand.Read(passwordHash) | |
u := user{ | |
ID: i, | |
// 95 % of users are not active | |
IsActive: rand.Int()%100 < 95, | |
Name: fmt.Sprintf("User%.4d", i), | |
Username: fmt.Sprintf("account%.4d", i), | |
PasswordHash: passwordHash, | |
// 1 % of users must change password | |
MustChangePassword: rand.Int()%100 == 0, | |
Email: fmt.Sprintf("user.%.4d@example.com", i), | |
// 99 % of users have confirmed their email address | |
EmailConfirmed: rand.Int()%100 < 99, | |
} | |
userArray = append(userArray, &u) | |
usersStruct.ID = append(usersStruct.ID, &u.ID) | |
usersStruct.IsActive = append(usersStruct.IsActive, &u.IsActive) | |
usersStruct.Name = append(usersStruct.Name, &u.Name) | |
usersStruct.Username = append(usersStruct.Username, &u.Username) | |
usersStruct.PasswordHash = append(usersStruct.PasswordHash, &u.PasswordHash) | |
usersStruct.MustChangePassword = append(usersStruct.MustChangePassword, &u.MustChangePassword) | |
usersStruct.Email = append(usersStruct.Email, &u.Email) | |
usersStruct.EmailConfirmed = append(usersStruct.EmailConfirmed, &u.EmailConfirmed) | |
} | |
// Calculate sizes of data types | |
pointerSize := int(unsafe.Sizeof(uintptr(0))) | |
userStructSize := int(unsafe.Sizeof(user{})) | |
if userStructSize%int(unsafe.Alignof(user{})) != 0 { | |
// Add padding | |
userStructSize += int(unsafe.Alignof(user{})) - (userStructSize % int(unsafe.Alignof(user{}))) | |
} | |
boolSize := int(unsafe.Sizeof(false)) | |
stringSize := int(unsafe.Sizeof("")) | |
b.Logf("userStructSize: %dB, boolSize: %dB, stringSize: %dB\n", userStructSize, boolSize, stringSize) | |
b.Run("array-of-references", func(b *testing.B) { | |
for i := 0; i < b.N; i++ { | |
getUnconfirmedEmailAddressesRef(userArray) | |
} | |
b.SetBytes(int64((pointerSize + userStructSize) * len(userArray))) | |
b.ReportMetric(float64((pointerSize+userStructSize)*len(userArray))/MB, "MB/op") | |
}) | |
b.Run("struct-of-ref-arrays", func(b *testing.B) { | |
// Run function once to get number of emails to account for | |
unconfirmed := usersStruct.getUnconfirmedEmailAddresses() | |
b.ResetTimer() | |
for i := 0; i < b.N; i++ { | |
usersStruct.getUnconfirmedEmailAddresses() | |
} | |
b.SetBytes(int64((pointerSize+boolSize)*len(usersStruct.EmailConfirmed) + (pointerSize+stringSize)*len(unconfirmed))) | |
b.ReportMetric(float64((pointerSize+boolSize)*len(usersStruct.EmailConfirmed)+(pointerSize+stringSize)*len(unconfirmed))/MB, "MB/op") | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ go test -bench=. data-oriented-design-benchmarks-2_test.go | |
Iterations Time Memory throughput Data processed | |
BenchmarkValueTypes/array-of-structs-4 121 9906752 ns/op 10497.89 MB/s 99.18 MB/op | |
BenchmarkValueTypes/struct-of-arrays-4 588 1974807 ns/op 586.80 MB/s 1.105 MB/op | |
BenchmarkRefTypes/array-of-references-4 127 9240654 ns/op 12120.35 MB/s 106.8 MB/op | |
BenchmarkRefTypes/struct-of-ref-arrays-4 123 9478477 ns/op 974.58 MB/s 8.810 MB/op |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment