Skip to content

Instantly share code, notes, and snippets.

@jupj
Last active March 12, 2023 13:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jupj/4976a941646146d609a6bb582bbd5bc5 to your computer and use it in GitHub Desktop.
Save jupj/4976a941646146d609a6bb582bbd5bc5 to your computer and use it in GitHub Desktop.
Benchmark for data-oriented design
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
}
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
}
// BenchmarkUnconfirmedEmail benchmarks listing the email addresses that haven't been confirmed
func BenchmarkUnconfirmedEmail(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("baseline", 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("data-oriented", 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")
})
}
$ go test -bench=UnconfirmedEmail data-oriented-design-benchmarks_test.go
Iterations Time Memory throughput Data processed
BenchmarkUnconfirmedEmail/baseline-4 100 10673319 ns/op 9743.92 MB/s 99.18 MB/op
BenchmarkUnconfirmedEmail/data-oriented-4 582 2255190 ns/op 515.10 MB/s 1.108 MB/op
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment