Skip to content

Commit 0be049e

Browse files
xaionaro@dx.centerxaionaro@dx.center
authored andcommitted
perf: parallelize genparcelspec across CPU cores
Process Java files concurrently with one ANTLR parser per worker goroutine. Reduces wall time from 55s to 20s (2.75x speedup) by utilizing all CPU cores for parsing.
1 parent a8c98f1 commit 0be049e

1 file changed

Lines changed: 57 additions & 17 deletions

File tree

tools/cmd/genparcelspec/main.go

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import (
55
"fmt"
66
"os"
77
"path/filepath"
8+
"runtime"
89
"runtime/pprof"
910
"strings"
11+
"sync"
12+
"sync/atomic"
1013

1114
"github.com/xaionaro-go/binder/tools/pkg/parcelspec"
1215
"gopkg.in/yaml.v3"
@@ -46,14 +49,21 @@ func main() {
4649
}
4750
}
4851

52+
type fileWork struct {
53+
path string
54+
packageName string
55+
src []byte
56+
}
57+
4958
func run(
5059
frameworksBase string,
5160
outputDir string,
5261
) error {
53-
generated := 0
54-
skipped := 0
55-
extractor := parcelspec.NewJavaExtractor()
62+
var generated atomic.Int64
63+
var skipped atomic.Int64
5664

65+
// Collect files to process.
66+
var files []fileWork
5767
err := filepath.Walk(frameworksBase, func(path string, info os.FileInfo, err error) error {
5868
if err != nil {
5969
return err
@@ -78,26 +88,56 @@ func run(
7888
return nil
7989
}
8090

81-
specs := extractor.ExtractSpecs(string(src), packageName)
82-
for _, spec := range specs {
83-
if len(spec.Fields) == 0 {
84-
skipped++
85-
continue
86-
}
87-
88-
if err := writeSpec(outputDir, spec); err != nil {
89-
return fmt.Errorf("writing spec for %s.%s: %w", spec.Package, spec.Type, err)
90-
}
91-
generated++
92-
}
93-
91+
files = append(files, fileWork{path: path, packageName: packageName, src: src})
9492
return nil
9593
})
9694
if err != nil {
9795
return fmt.Errorf("walking %s: %w", frameworksBase, err)
9896
}
9997

100-
fmt.Fprintf(os.Stderr, "genparcelspec: generated %d specs, skipped %d empty specs\n", generated, skipped)
98+
// Process files in parallel. Each worker has its own ANTLR parser
99+
// (not thread-safe), fed from a shared channel.
100+
work := make(chan fileWork, len(files))
101+
for _, f := range files {
102+
work <- f
103+
}
104+
close(work)
105+
106+
numWorkers := runtime.NumCPU()
107+
var wg sync.WaitGroup
108+
var firstErr atomic.Value
109+
110+
for range numWorkers {
111+
wg.Add(1)
112+
go func() {
113+
defer wg.Done()
114+
extractor := parcelspec.NewJavaExtractor()
115+
116+
for fw := range work {
117+
specs := extractor.ExtractSpecs(string(fw.src), fw.packageName)
118+
for _, spec := range specs {
119+
if len(spec.Fields) == 0 {
120+
skipped.Add(1)
121+
continue
122+
}
123+
124+
if err := writeSpec(outputDir, spec); err != nil {
125+
firstErr.CompareAndSwap(nil, fmt.Errorf("writing spec for %s.%s: %w", spec.Package, spec.Type, err))
126+
return
127+
}
128+
generated.Add(1)
129+
}
130+
}
131+
}()
132+
}
133+
134+
wg.Wait()
135+
136+
if v := firstErr.Load(); v != nil {
137+
return v.(error)
138+
}
139+
140+
fmt.Fprintf(os.Stderr, "genparcelspec: generated %d specs, skipped %d empty specs\n", generated.Load(), skipped.Load())
101141
return nil
102142
}
103143

0 commit comments

Comments
 (0)