| name | kennedy-mechanical-sympathy |
| description | Write Go code in the style of Bill Kennedy, author of Go in Action. Emphasizes mechanical sympathy, data-oriented design, and understanding how Go code executes. Use when writing performance-critical Go or when teaching Go fundamentals. |
| tags | performance, data-oriented, cache, memory, profiling, benchmarking, goroutines, scheduler, hardware |
Bill Kennedy Style Guide
Overview
Bill Kennedy is the author of "Go in Action" and founder of Ardan Labs. His teaching emphasizes mechanical sympathy: understanding how software interacts with hardware. His "Ultimate Go" course is legendary for deep-dive explanations.
Core Philosophy
"Integrity, readability, and simplicity—in that order."
"If you don't understand the data, you don't understand the problem."
"Mechanical sympathy: understanding how the hardware and runtime work."
Kennedy believes that great Go code comes from understanding what happens beneath the surface: memory layout, garbage collection, scheduler behavior.
Design Principles
-
Data-Oriented Design: Design around data transformations, not object hierarchies.
-
Mechanical Sympathy: Write code that works with the hardware, not against it.
-
Value Semantics First: Prefer values over pointers unless you have a reason.
-
Integrity First: Correctness beats performance, readability beats cleverness.
When Writing Code
Always
- Understand the memory layout of your data structures
- Know when copies happen and when references are used
- Consider CPU cache behavior for hot paths
- Profile before optimizing
- Use value semantics by default
- Understand escape analysis
Never
- Optimize without profiling
- Use pointers just to "avoid copies" without measuring
- Create deep pointer chains (bad for cache)
- Ignore alignment and padding
- Assume you know what escapes to heap
Prefer
- Contiguous data (slices) over pointer-heavy structures
- Value receivers for small, immutable types
- Stack allocation over heap when possible
- Struct of arrays over array of structs for hot loops
- Understanding over blind rules
Code Patterns
Data-Oriented Design
type Node struct {
Value int
Children []*Node
}
type Tree struct {
Values []int
Children [][]int
}
type Particle struct {
X, Y, Z float64
VX, VY, VZ float64
Mass float64
}
particles := make([]Particle, 1000)
type Particles struct {
X, Y, Z []float64
VX, VY, VZ []float64
Mass []float64
}
p := Particles{
X: make([]float64, 1000),
Y: make([]float64, 1000),
}
for i := range p.X {
p.X[i] += p.VX[i]
p.Y[i] += p.VY[i]
p.Z[i] += p.VZ[i]
}
Value vs Pointer Semantics
type Time struct {
sec int64
nsec int32
}
func (t Time) Add(d Duration) Time {
return Time{sec: t.sec + int64(d), nsec: t.nsec}
}
type File struct {
fd int
name string
}
func (f *File) Read(b []byte) (int, error) {
}
Understanding Escape Analysis
func sumLocal() int {
numbers := [4]int{1, 2, 3, 4}
sum := 0
for _, n := range numbers {
sum += n
}
return sum
}
func sumHeap() *int {
sum := 0
for i := 0; i < 4; i++ {
sum += i
}
return &sum
}
func process(data []byte) {
}
Memory Layout Awareness
type BadLayout struct {
a bool
b int64
c bool
}
type GoodLayout struct {
b int64
a bool
c bool
}
Slice Internals
func modify(s []int) {
s[0] = 999
s = append(s, 4)
}
func main() {
original := []int{1, 2, 3}
modify(original)
}
func appendSafe(s []int, v int) []int {
return append(s, v)
}
original = appendSafe(original, 4)
Benchmarking Properly
func BenchmarkProcess(b *testing.B) {
data := generateTestData()
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := Process(data)
_ = result
}
}
func BenchmarkProcessV1(b *testing.B) { ... }
func BenchmarkProcessV2(b *testing.B) { ... }
Goroutine Pool Pattern
type Pool struct {
work chan func()
sem chan struct{}
}
func NewPool(size int) *Pool {
p := &Pool{
work: make(chan func()),
sem: make(chan struct{}, size),
}
return p
}
func (p *Pool) Submit(task func()) {
select {
case p.work <- task:
case p.sem <- struct{}{}:
go p.worker(task)
}
}
func (p *Pool) worker(task func()) {
defer func() { <-p.sem }()
for {
task()
task = <-p.work
}
}
Mental Model
Kennedy teaches by asking:
- What's the data? Understand it before writing code.
- Where does it live? Stack? Heap? How is it laid out?
- How does it flow? What transformations happen?
- What's the cost? Allocations, copies, cache misses?
Kennedy's Priorities
- Integrity: Code must be correct
- Readability: Code must be maintainable
- Simplicity: Don't over-engineer
- Performance: After the above are satisfied
In that order.