From 9a49961cc7c2ab3e32fbaab2738ff9d182641046 Mon Sep 17 00:00:00 2001 From: hayabusa-cloud Date: Thu, 5 Feb 2026 13:42:28 +0900 Subject: [PATCH 1/4] chore: add benchmarks and Makefile --- Makefile | 17 ++++ benchmark_test.go | 244 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 Makefile create mode 100644 benchmark_test.go diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..df45b80 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +.PHONY: test bench cover vet clean + +test: + go test -race ./... + +bench: + go test -bench=. -benchmem ./... + +cover: + go test -coverprofile=coverage.out ./... + go tool cover -func=coverage.out | tail -1 + +vet: + go vet ./... + +clean: + rm -f coverage.out diff --git a/benchmark_test.go b/benchmark_test.go new file mode 100644 index 0000000..2c25d58 --- /dev/null +++ b/benchmark_test.go @@ -0,0 +1,244 @@ +// ©Hayabusa Cloud Co., Ltd. 2026. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package spin_test + +import ( + "runtime" + "sync" + "testing" + + "code.hybscloud.com/spin" +) + +// ---------------------------------------------------------------------------- +// Lock Benchmarks +// ---------------------------------------------------------------------------- + +// BenchmarkLock measures lock performance across contention levels. +func BenchmarkLock(b *testing.B) { + b.Run("Uncontended", func(b *testing.B) { + var lk spin.Lock + for i := range b.N { + _ = i + lk.Lock() + lk.Unlock() + } + }) + + b.Run("Try/Uncontended", func(b *testing.B) { + var lk spin.Lock + for range b.N { + if lk.Try() { + lk.Unlock() + } + } + }) + + // Contention scaling benchmarks + for _, procs := range []int{2, 4, 8, 16} { + b.Run("Contended/GOMAXPROCS="+itoa(procs), func(b *testing.B) { + oldProcs := runtime.GOMAXPROCS(procs) + defer runtime.GOMAXPROCS(oldProcs) + + var lk spin.Lock + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + lk.Lock() + lk.Unlock() + } + }) + }) + } +} + +// BenchmarkLockVsMutex compares spin.Lock against sync.Mutex. +func BenchmarkLockVsMutex(b *testing.B) { + b.Run("spin.Lock/Uncontended", func(b *testing.B) { + var lk spin.Lock + for range b.N { + lk.Lock() + lk.Unlock() + } + }) + + b.Run("sync.Mutex/Uncontended", func(b *testing.B) { + var mu sync.Mutex + for range b.N { + mu.Lock() + mu.Unlock() + } + }) + + b.Run("spin.Lock/Contended", func(b *testing.B) { + var lk spin.Lock + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + lk.Lock() + lk.Unlock() + } + }) + }) + + b.Run("sync.Mutex/Contended", func(b *testing.B) { + var mu sync.Mutex + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + mu.Lock() + mu.Unlock() + } + }) + }) +} + +// BenchmarkLockWithWork measures lock performance with simulated work. +func BenchmarkLockWithWork(b *testing.B) { + work := func() { + // Simulate ~10ns of work + x := 0 + for i := range 10 { + x += i + } + _ = x + } + + b.Run("spin.Lock", func(b *testing.B) { + var lk spin.Lock + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + lk.Lock() + work() + lk.Unlock() + } + }) + }) + + b.Run("sync.Mutex", func(b *testing.B) { + var mu sync.Mutex + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + mu.Lock() + work() + mu.Unlock() + } + }) + }) +} + +// ---------------------------------------------------------------------------- +// Wait Benchmarks +// ---------------------------------------------------------------------------- + +// BenchmarkWait measures Wait performance. +func BenchmarkWait(b *testing.B) { + b.Run("Once/Fresh", func(b *testing.B) { + for range b.N { + var sw spin.Wait + sw.Once() + } + }) + + b.Run("Once/Repeated", func(b *testing.B) { + var sw spin.Wait + for range b.N { + sw.Once() + } + }) + + b.Run("WillYield", func(b *testing.B) { + var sw spin.Wait + for range b.N { + _ = sw.WillYield() + } + }) + + b.Run("Reset", func(b *testing.B) { + var sw spin.Wait + for i := range b.N { + if i%16 == 0 { + sw.Reset() + } + sw.Once() + } + }) +} + +// ---------------------------------------------------------------------------- +// Pause Benchmarks +// ---------------------------------------------------------------------------- + +// BenchmarkPauseCycles measures Pause across different cycle counts. +func BenchmarkPauseCycles(b *testing.B) { + for _, cycles := range []int{1, 10, 30, 50, 100} { + b.Run("cycles="+itoa(cycles), func(b *testing.B) { + for range b.N { + spin.Pause(cycles) + } + }) + } +} + +// BenchmarkPauseLoop measures pause in a tight loop (common pattern). +func BenchmarkPauseLoop(b *testing.B) { + b.Run("10_iterations", func(b *testing.B) { + for range b.N { + for range 10 { + spin.Pause() + } + } + }) + + b.Run("100_iterations", func(b *testing.B) { + for range b.N { + for range 100 { + spin.Pause() + } + } + }) +} + +// ---------------------------------------------------------------------------- +// Spin-Wait Pattern Benchmarks +// ---------------------------------------------------------------------------- + +// BenchmarkSpinWaitPattern measures realistic spin-wait scenarios. +func BenchmarkSpinWaitPattern(b *testing.B) { + // Pattern 1: Spin until condition (Wait helper) + b.Run("Wait/UntilCondition", func(b *testing.B) { + for range b.N { + var sw spin.Wait + for i := range 16 { + _ = i + sw.Once() + } + } + }) + + // Pattern 2: Raw pause loop + b.Run("Pause/RawLoop", func(b *testing.B) { + for range b.N { + for range 16 { + spin.Pause() + } + } + }) +} + +// ---------------------------------------------------------------------------- +// Helpers +// ---------------------------------------------------------------------------- + +func itoa(n int) string { + if n == 0 { + return "0" + } + var buf [20]byte + i := len(buf) + for n > 0 { + i-- + buf[i] = byte('0' + n%10) + n /= 10 + } + return string(buf[i:]) +} From f9ccbb7927dd2c9544ee3cebaca0448ee4656883 Mon Sep 17 00:00:00 2001 From: hayabusa-cloud Date: Thu, 5 Feb 2026 13:43:02 +0900 Subject: [PATCH 2/4] chore: add GitHub Sponsors funding config --- .github/FUNDING.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..3040112 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: hayabusa-cloud + From 5b0217bc66318092bfc4a8daadf0bca57593f50a Mon Sep 17 00:00:00 2001 From: hayabusa-cloud Date: Thu, 5 Feb 2026 13:51:19 +0900 Subject: [PATCH 3/4] Update benchmark_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- benchmark_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/benchmark_test.go b/benchmark_test.go index 2c25d58..e4870b3 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -20,8 +20,7 @@ import ( func BenchmarkLock(b *testing.B) { b.Run("Uncontended", func(b *testing.B) { var lk spin.Lock - for i := range b.N { - _ = i + for range b.N { lk.Lock() lk.Unlock() } From 4af0ae550f7a111a0c973f4c7082b09887adf263 Mon Sep 17 00:00:00 2001 From: hayabusa-cloud Date: Thu, 5 Feb 2026 13:51:28 +0900 Subject: [PATCH 4/4] Update benchmark_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- benchmark_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/benchmark_test.go b/benchmark_test.go index e4870b3..379f0c3 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -207,8 +207,7 @@ func BenchmarkSpinWaitPattern(b *testing.B) { b.Run("Wait/UntilCondition", func(b *testing.B) { for range b.N { var sw spin.Wait - for i := range 16 { - _ = i + for range 16 { sw.Once() } }