Go Energy Analysis
Joule analyzes Go code with awareness of goroutines, channels, and Go's concurrency model. The energy cost of spawning goroutines, sending on channels, and slice operations is modeled at the picojoule level.
Quick Start
# Static energy analysis
joulec --lift go main.go
# Execute with energy tracking
joulec --lift-run go main.go
# Execute with energy optimization
joulec --energy-optimize --lift-run go main.go
Supported Features
| Category | Features |
|---|---|
| Types | int, int8/16/32/64, uint, float32/64, string, bool, byte, rune |
| Variables | var, := short declaration, const, multiple assignment |
| Functions | func, multiple return values, named returns, closures, variadic ... |
| Control flow | if/else (with init statement), for, for range, switch/case, select |
| Slices | Creation, append(), len(), cap(), slicing s[a:b], make(), copy() |
| Maps | map[K]V, make(map[...]), index, delete, len(), comma-ok pattern |
| Structs | Definition, field access, methods (value/pointer receiver), embedding |
| Concurrency | go (goroutine spawn), chan, <- send/receive, make(chan T, N), close() |
| Defer | defer statement (LIFO cleanup) |
| Error handling | Multiple return (result, error), if err != nil pattern |
| Packages | fmt.Println, fmt.Sprintf, math.Sqrt, strings.*, strconv.* |
Common Energy Anti-Patterns
1. Unbounded Goroutine Fan-Out
// BAD — spawns 100K goroutines, each has scheduling overhead
for _, item := range items {
go process(item) // 100K goroutines
}
// GOOD — bounded worker pool
ch := make(chan Item, 100)
for i := 0; i < runtime.NumCPU(); i++ {
go func() {
for item := range ch {
process(item)
}
}()
}
for _, item := range items {
ch <- item
}
close(ch)
Category: ALLOCATION | Severity: Critical | Savings: ~10x
Each goroutine has a minimum 2KB stack allocation. 100K goroutines = 200MB of stack memory + scheduling overhead.
2. Slice Append Without Pre-Allocation
// BAD — slice grows geometrically, copying data each time
var result []int
for i := 0; i < 10000; i++ {
result = append(result, i)
}
// GOOD — pre-allocate known capacity
result := make([]int, 0, 10000)
for i := 0; i < 10000; i++ {
result = append(result, i)
}
Category: ALLOCATION | Severity: Medium | Savings: ~2x
Without pre-allocation, append triggers ~14 reallocations and data copies to grow from 0 to 10,000 elements.
3. Map Iteration with Value Copy
// BAD — copies entire struct on each iteration
type BigStruct struct {
Data [1024]byte
Name string
}
for _, v := range bigMap {
process(v) // copies 1KB+ per iteration
}
// GOOD — iterate with index, access by reference
for k := range bigMap {
process(&bigMap[k])
}
Category: MEMORY | Severity: Medium | Savings: ~3x for large values
4. String Concatenation in Loops
// BAD — O(n^2) string building
result := ""
for _, s := range parts {
result += s
}
// GOOD — O(n) with strings.Builder
var b strings.Builder
for _, s := range parts {
b.WriteString(s)
}
result := b.String()
Category: STRING | Severity: High | Savings: ~10x
Worked Example
package main
import "fmt"
func counter(id int, ch chan int) {
sum := 0
for i := 0; i < 1000; i++ {
sum += i
}
ch <- sum
}
func main() {
ch := make(chan int, 10)
for i := 0; i < 10; i++ {
go counter(i, ch)
}
total := 0
for i := 0; i < 10; i++ {
total += <-ch
}
fmt.Println(total)
}
$ joulec --lift go counter.go
Energy Analysis: counter.go
counter 12.50 nJ (confidence: 0.70)
main 8.30 nJ (confidence: 0.65)
Total: 20.80 nJ
Note: 10 goroutines detected. Energy estimate reflects single-thread
execution model; actual concurrent execution may differ due to
scheduling and synchronization overhead.
Limitations
- No interface method dispatch (interfaces parsed but not resolved dynamically)
- No struct embedding for method promotion
- No generics (Go 1.18+ type parameters)
- No
selectwith complex multi-channel patterns - No
panic/recover(panic is treated as program exit) - No
init()functions - No package imports beyond
fmt,math,strings,strconv - Goroutines are analyzed sequentially (no parallel energy modeling)