Skip to content

Commit b7e54d8

Browse files
committed
cmd/go: make 'mod verify' use multiple CPUs
'go mod verify' checksums one module zip at a time, which is CPU-intensive on most modern machines with fast disks. As a result, one can see a CPU bottleneck when running the command on, for example, a module where 'go list -m all' lists ~440 modules: $ /usr/bin/time go mod verify all modules verified 11.47user 0.77system 0:09.41elapsed 130%CPU (0avgtext+0avgdata 24284maxresident)k 0inputs+0outputs (0major+4156minor)pagefaults 0swaps Instead, verify up to GOMAXPROCS zips at once, which should line up pretty well with the amount of processors we can use on a machine. The results below are obtained via 'benchcmd -n 5 GoModVerify go mod verify' on the same large module. name old time/op new time/op delta GoModVerify 9.35s ± 1% 3.03s ± 2% -67.60% (p=0.008 n=5+5) name old user-time/op new user-time/op delta GoModVerify 11.2s ± 1% 16.3s ± 3% +45.38% (p=0.008 n=5+5) name old sys-time/op new sys-time/op delta GoModVerify 841ms ± 9% 865ms ± 8% ~ (p=0.548 n=5+5) name old peak-RSS-bytes new peak-RSS-bytes delta GoModVerify 27.8MB ±13% 50.7MB ±27% +82.01% (p=0.008 n=5+5) The peak memory usage nearly doubles, and there is some extra overhead, but it seems clearly worth the tradeoff given that we see a ~3x speedup on my laptop with 4 physical cores. The vast majority of developer machines nowadays should have 2-4 cores at least. No test or benchmark is included; one can benchmark 'go mod verify' directly, as I did above. The existing tests also cover correctness, including any data races via -race. Fixes #38623. Change-Id: I45d8154687a6f3a6a9fb0e2b13da4190f321246c Reviewed-on: https://go-review.googlesource.com/c/go/+/229817 Run-TryBot: Daniel Martí <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]>
1 parent e87b064 commit b7e54d8

File tree

1 file changed

+39
-16
lines changed

1 file changed

+39
-16
lines changed

src/cmd/go/internal/modcmd/verify.go

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"fmt"
1111
"io/ioutil"
1212
"os"
13+
"runtime"
1314

1415
"cmd/go/internal/base"
1516
"cmd/go/internal/cfg"
@@ -52,17 +53,41 @@ func runVerify(cmd *base.Command, args []string) {
5253
base.Fatalf("go: cannot find main module; see 'go help modules'")
5354
}
5455
}
56+
57+
// Only verify up to GOMAXPROCS zips at once.
58+
type token struct{}
59+
sem := make(chan token, runtime.GOMAXPROCS(0))
60+
61+
// Use a slice of result channels, so that the output is deterministic.
62+
mods := modload.LoadBuildList()[1:]
63+
errsChans := make([]<-chan []error, len(mods))
64+
65+
for i, mod := range mods {
66+
sem <- token{}
67+
errsc := make(chan []error, 1)
68+
errsChans[i] = errsc
69+
mod := mod // use a copy to avoid data races
70+
go func() {
71+
errsc <- verifyMod(mod)
72+
<-sem
73+
}()
74+
}
75+
5576
ok := true
56-
for _, mod := range modload.LoadBuildList()[1:] {
57-
ok = verifyMod(mod) && ok
77+
for _, errsc := range errsChans {
78+
errs := <-errsc
79+
for _, err := range errs {
80+
base.Errorf("%s", err)
81+
ok = false
82+
}
5883
}
5984
if ok {
6085
fmt.Printf("all modules verified\n")
6186
}
6287
}
6388

64-
func verifyMod(mod module.Version) bool {
65-
ok := true
89+
func verifyMod(mod module.Version) []error {
90+
var errs []error
6691
zip, zipErr := modfetch.CachePath(mod, "zip")
6792
if zipErr == nil {
6893
_, zipErr = os.Stat(zip)
@@ -73,10 +98,10 @@ func verifyMod(mod module.Version) bool {
7398
if zipErr != nil && errors.Is(zipErr, os.ErrNotExist) &&
7499
dirErr != nil && errors.Is(dirErr, os.ErrNotExist) {
75100
// Nothing downloaded yet. Nothing to verify.
76-
return true
101+
return nil
77102
}
78-
base.Errorf("%s %s: missing ziphash: %v", mod.Path, mod.Version, err)
79-
return false
103+
errs = append(errs, fmt.Errorf("%s %s: missing ziphash: %v", mod.Path, mod.Version, err))
104+
return errs
80105
}
81106
h := string(bytes.TrimSpace(data))
82107

@@ -85,11 +110,10 @@ func verifyMod(mod module.Version) bool {
85110
} else {
86111
hZ, err := dirhash.HashZip(zip, dirhash.DefaultHash)
87112
if err != nil {
88-
base.Errorf("%s %s: %v", mod.Path, mod.Version, err)
89-
return false
113+
errs = append(errs, fmt.Errorf("%s %s: %v", mod.Path, mod.Version, err))
114+
return errs
90115
} else if hZ != h {
91-
base.Errorf("%s %s: zip has been modified (%v)", mod.Path, mod.Version, zip)
92-
ok = false
116+
errs = append(errs, fmt.Errorf("%s %s: zip has been modified (%v)", mod.Path, mod.Version, zip))
93117
}
94118
}
95119
if dirErr != nil && errors.Is(dirErr, os.ErrNotExist) {
@@ -98,13 +122,12 @@ func verifyMod(mod module.Version) bool {
98122
hD, err := dirhash.HashDir(dir, mod.Path+"@"+mod.Version, dirhash.DefaultHash)
99123
if err != nil {
100124

101-
base.Errorf("%s %s: %v", mod.Path, mod.Version, err)
102-
return false
125+
errs = append(errs, fmt.Errorf("%s %s: %v", mod.Path, mod.Version, err))
126+
return errs
103127
}
104128
if hD != h {
105-
base.Errorf("%s %s: dir has been modified (%v)", mod.Path, mod.Version, dir)
106-
ok = false
129+
errs = append(errs, fmt.Errorf("%s %s: dir has been modified (%v)", mod.Path, mod.Version, dir))
107130
}
108131
}
109-
return ok
132+
return errs
110133
}

0 commit comments

Comments
 (0)