Skip to content

Instantly share code, notes, and snippets.

@arunlakshman
Created January 12, 2026 00:34
Show Gist options
  • Select an option

  • Save arunlakshman/2cedd03cd9513f7c80d986abb4355cb5 to your computer and use it in GitHub Desktop.

Select an option

Save arunlakshman/2cedd03cd9513f7c80d986abb4355cb5 to your computer and use it in GitHub Desktop.
mutex-atomic-benchmark
package main
import (
"fmt"
"runtime"
"strings"
"sync"
"sync/atomic"
"time"
)
// MutexCounter uses a mutex to protect an integer counter
type MutexCounter struct {
mu sync.Mutex
value int64
}
func (c *MutexCounter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *MutexCounter) Get() int64 {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
// AtomicCounter uses atomic operations for thread-safe integer operations
type AtomicCounter struct {
value int64
}
func (c *AtomicCounter) Increment() {
atomic.AddInt64(&c.value, 1)
}
func (c *AtomicCounter) Get() int64 {
return atomic.LoadInt64(&c.value)
}
// BenchmarkConfig holds configuration for benchmarks
type BenchmarkConfig struct {
NumGoroutines int
NumOperations int
WarmupRuns int
BenchmarkRuns int
}
// BenchmarkResult holds the results of a benchmark run
type BenchmarkResult struct {
Name string
TotalOps int64
Duration time.Duration
OpsPerSecond float64
AvgTimePerOp time.Duration
FinalValue int64
NumGoroutines int
}
func runMutexBenchmark(config BenchmarkConfig) BenchmarkResult {
var wg sync.WaitGroup
counter := &MutexCounter{}
start := time.Now()
// Run operations
for i := 0; i < config.NumGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < config.NumOperations; j++ {
counter.Increment()
}
}()
}
wg.Wait()
duration := time.Since(start)
totalOps := int64(config.NumGoroutines * config.NumOperations)
opsPerSecond := float64(totalOps) / duration.Seconds()
return BenchmarkResult{
Name: "Mutex",
TotalOps: totalOps,
Duration: duration,
OpsPerSecond: opsPerSecond,
AvgTimePerOp: duration / time.Duration(totalOps),
FinalValue: counter.Get(),
NumGoroutines: config.NumGoroutines,
}
}
func runAtomicBenchmark(config BenchmarkConfig) BenchmarkResult {
var wg sync.WaitGroup
counter := &AtomicCounter{}
start := time.Now()
// Run operations
for i := 0; i < config.NumGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < config.NumOperations; j++ {
counter.Increment()
}
}()
}
wg.Wait()
duration := time.Since(start)
totalOps := int64(config.NumGoroutines * config.NumOperations)
opsPerSecond := float64(totalOps) / duration.Seconds()
return BenchmarkResult{
Name: "Atomic",
TotalOps: totalOps,
Duration: duration,
OpsPerSecond: opsPerSecond,
AvgTimePerOp: duration / time.Duration(totalOps),
FinalValue: counter.Get(),
NumGoroutines: config.NumGoroutines,
}
}
func printResults(results []BenchmarkResult) {
fmt.Println("\n" + strings.Repeat("=", 80))
fmt.Printf("%-15s | %-12s | %-15s | %-18s | %-15s | %-12s\n",
"Method", "Goroutines", "Total Ops", "Duration", "Ops/Second", "Final Value")
fmt.Println(strings.Repeat("-", 80))
for _, result := range results {
fmt.Printf("%-15s | %-12d | %-15d | %-18s | %-15.0f | %-12d\n",
result.Name,
result.NumGoroutines,
result.TotalOps,
result.Duration.Round(time.Microsecond),
result.OpsPerSecond,
result.FinalValue)
}
fmt.Println(strings.Repeat("=", 80))
}
func printComparison(mutexResult, atomicResult BenchmarkResult) {
fmt.Println("\n" + strings.Repeat("=", 80))
fmt.Println("PERFORMANCE COMPARISON")
fmt.Println(strings.Repeat("=", 80))
speedup := mutexResult.Duration.Seconds() / atomicResult.Duration.Seconds()
fmt.Printf("Atomic is %.2fx faster than Mutex\n", speedup)
fmt.Printf("Mutex Duration: %v\n", mutexResult.Duration.Round(time.Microsecond))
fmt.Printf("Atomic Duration: %v\n", atomicResult.Duration.Round(time.Microsecond))
fmt.Printf("Time Saved: %v\n", (mutexResult.Duration - atomicResult.Duration).Round(time.Microsecond))
fmt.Printf("\nMutex Ops/Second: %.0f\n", mutexResult.OpsPerSecond)
fmt.Printf("Atomic Ops/Second: %.0f\n", atomicResult.OpsPerSecond)
fmt.Printf("Difference: %.0f ops/sec (%.1f%% improvement)\n",
atomicResult.OpsPerSecond-mutexResult.OpsPerSecond,
((atomicResult.OpsPerSecond-mutexResult.OpsPerSecond)/mutexResult.OpsPerSecond)*100)
fmt.Println(strings.Repeat("=", 80))
}
func main() {
fmt.Println("Mutex vs Atomic Integer Benchmark")
fmt.Println("==================================")
fmt.Printf("CPU Cores: %d\n", runtime.NumCPU())
fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0))
// Benchmark configurations
configs := []BenchmarkConfig{
{NumGoroutines: 1, NumOperations: 1000000, WarmupRuns: 2, BenchmarkRuns: 5},
{NumGoroutines: 2, NumOperations: 1000000, WarmupRuns: 2, BenchmarkRuns: 5},
{NumGoroutines: 4, NumOperations: 1000000, WarmupRuns: 2, BenchmarkRuns: 5},
{NumGoroutines: 8, NumOperations: 1000000, WarmupRuns: 2, BenchmarkRuns: 5},
{NumGoroutines: 16, NumOperations: 1000000, WarmupRuns: 2, BenchmarkRuns: 5},
{NumGoroutines: 32, NumOperations: 1000000, WarmupRuns: 2, BenchmarkRuns: 5},
{NumGoroutines: 64, NumOperations: 1000000, WarmupRuns: 2, BenchmarkRuns: 5},
}
var allResults []BenchmarkResult
for _, config := range configs {
fmt.Printf("\nRunning benchmark with %d goroutines, %d operations per goroutine...\n",
config.NumGoroutines, config.NumOperations)
// Warmup runs
for i := 0; i < config.WarmupRuns; i++ {
runMutexBenchmark(config)
runAtomicBenchmark(config)
}
// Actual benchmark runs - average the results
var mutexDurations []time.Duration
var atomicDurations []time.Duration
var mutexFinalValue int64
var atomicFinalValue int64
for i := 0; i < config.BenchmarkRuns; i++ {
mutexResult := runMutexBenchmark(config)
atomicResult := runAtomicBenchmark(config)
mutexDurations = append(mutexDurations, mutexResult.Duration)
atomicDurations = append(atomicDurations, atomicResult.Duration)
mutexFinalValue = mutexResult.FinalValue
atomicFinalValue = atomicResult.FinalValue
}
// Calculate averages
var mutexAvgDuration, atomicAvgDuration time.Duration
for _, d := range mutexDurations {
mutexAvgDuration += d
}
for _, d := range atomicDurations {
atomicAvgDuration += d
}
mutexAvgDuration /= time.Duration(len(mutexDurations))
atomicAvgDuration /= time.Duration(len(atomicDurations))
totalOps := int64(config.NumGoroutines * config.NumOperations)
mutexResult := BenchmarkResult{
Name: "Mutex",
TotalOps: totalOps,
Duration: mutexAvgDuration,
OpsPerSecond: float64(totalOps) / mutexAvgDuration.Seconds(),
AvgTimePerOp: mutexAvgDuration / time.Duration(totalOps),
FinalValue: mutexFinalValue,
NumGoroutines: config.NumGoroutines,
}
atomicResult := BenchmarkResult{
Name: "Atomic",
TotalOps: totalOps,
Duration: atomicAvgDuration,
OpsPerSecond: float64(totalOps) / atomicAvgDuration.Seconds(),
AvgTimePerOp: atomicAvgDuration / time.Duration(totalOps),
FinalValue: atomicFinalValue,
NumGoroutines: config.NumGoroutines,
}
allResults = append(allResults, mutexResult, atomicResult)
// Print comparison for this configuration
printComparison(mutexResult, atomicResult)
}
// Print summary table
printResults(allResults)
fmt.Println("\nBenchmark completed!")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment