From c61df6cc7301a07dec1b0121ac7c4daa42e50367 Mon Sep 17 00:00:00 2001 From: Matthew Rothenberg Date: Sat, 19 Aug 2023 16:46:46 -0400 Subject: [PATCH] feat: bounded concurrency for binary inspection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uses a semaphore to bound concurrency to GOMAXPROCS value. Fixes #14. On my system, a cached --dry-run goes from ~820ms to ~130ms, and an uncached one that needs to hit the network goes from ~2s to ~300ms. Benchmark results from my laptop: ``` $ hyperfine --warmup 1 'go-global-update --dry-run' './go-global-update-fork --dry-run' Benchmark 1: go-global-update --dry-run Time (mean ± σ): 821.7 ms ± 7.7 ms [User: 171.8 ms, System: 139.0 ms] Range (min … max): 806.2 ms … 832.8 ms 10 runs Benchmark 2: ./go-global-update-fork --dry-run Time (mean ± σ): 129.5 ms ± 4.2 ms [User: 152.8 ms, System: 187.8 ms] Range (min … max): 120.3 ms … 137.3 ms 22 runs ``` Results with no modcache (cleared via `go clean -modcache`): ``` hyperfine --prepare 'go clean -modcache' 'go-global-update --dry-run' './go-global-update-fork --dry-run' Benchmark 1: go-global-update --dry-run Time (mean ± σ): 1.936 s ± 0.072 s [User: 0.236 s, System: 0.243 s] Range (min … max): 1.875 s … 2.120 s 10 runs Benchmark 2: ./go-global-update-fork --dry-run Time (mean ± σ): 303.4 ms ± 22.4 ms [User: 193.1 ms, System: 261.4 ms] Range (min … max): 284.7 ms … 361.6 ms 10 runs ``` --- internal/gobinaries/introspect_binaries.go | 42 +++++++++++++++------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/internal/gobinaries/introspect_binaries.go b/internal/gobinaries/introspect_binaries.go index e36049f..06176d6 100644 --- a/internal/gobinaries/introspect_binaries.go +++ b/internal/gobinaries/introspect_binaries.go @@ -1,6 +1,10 @@ package gobinaries -import "fmt" +import ( + "fmt" + "runtime" + "sync" +) type IntrospectionResult struct { Binary GoBinary @@ -8,19 +12,31 @@ type IntrospectionResult struct { } func IntrospectBinaries(introspecter *Introspecter, binaryNames []string) []IntrospectionResult { - var results []IntrospectionResult - - for _, binaryName := range binaryNames { - binary, err := introspecter.Introspect(binaryName) - if err != nil { - err = fmt.Errorf("could not introspect binary %s: %w", binaryName, err) - } - - results = append(results, IntrospectionResult{ - Binary: binary, - Error: err, - }) + results := make([]IntrospectionResult, len(binaryNames)) + + var wg sync.WaitGroup + semaphore := make(chan struct{}, runtime.GOMAXPROCS(0)) + for i, binaryName := range binaryNames { + i, binaryName := i, binaryName + + wg.Add(1) + semaphore <- struct{}{} + go func() { + defer wg.Done() + defer func() { <-semaphore }() + + binary, err := introspecter.Introspect(binaryName) + if err != nil { + err = fmt.Errorf("could not introspect binary %s: %w", binaryName, err) + } + + results[i] = IntrospectionResult{ + Binary: binary, + Error: err, + } + }() } + wg.Wait() return results }