internal/lsp/cache: two minor optimizations

1. Avoid unnecessary intermediate map updates.
2. Avoid accumulating defers in a loop when the control is simple.

Yield: -10% CPU, -37% allocs.

Typical results:

$ go test -v ./gopls/internal/regtest/bench -run=TestBenchmarkDidChange -didchange_dir=$HOME/w/kubernetes -didchange_file=pkg/util/hash/hash.go
Before:
BenchmarkStatistics	     100	  25932206 ns/op	11684109 B/op	   75458 allocs/op
After:
BenchmarkStatistics	     100	  23294195 ns/op	11293472 B/op	   47299 allocs/op

Also, move profiling logic outside the loop so that later runs
don't overwrite earlier runs. (This doesn't appear to be a problem
in practice, presumably because the last run is the big one.)

Updates golang/go#45686

Change-Id: I538ca6bb88cc18f1eefe35d2db29a62e5190280e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/410697
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/gopls/internal/regtest/bench/bench_test.go b/gopls/internal/regtest/bench/bench_test.go
index f37c2f6..5e4eb5f 100644
--- a/gopls/internal/regtest/bench/bench_test.go
+++ b/gopls/internal/regtest/bench/bench_test.go
@@ -163,19 +163,22 @@
 		env.Await(env.DoneWithOpen())
 		// Insert the text we'll be modifying at the top of the file.
 		env.EditBuffer(*benchFile, fake.Edit{Text: "// __REGTEST_PLACEHOLDER_0__\n"})
-		result := testing.Benchmark(func(b *testing.B) {
-			if *benchProfile != "" {
-				profile, err := os.Create(*benchProfile)
-				if err != nil {
-					t.Fatal(err)
-				}
-				defer profile.Close()
-				if err := pprof.StartCPUProfile(profile); err != nil {
-					t.Fatal(err)
-				}
-				defer pprof.StopCPUProfile()
+
+		// Run the profiler after the initial load,
+		// across all benchmark iterations.
+		if *benchProfile != "" {
+			profile, err := os.Create(*benchProfile)
+			if err != nil {
+				t.Fatal(err)
 			}
-			b.ResetTimer()
+			defer profile.Close()
+			if err := pprof.StartCPUProfile(profile); err != nil {
+				t.Fatal(err)
+			}
+			defer pprof.StopCPUProfile()
+		}
+
+		result := testing.Benchmark(func(b *testing.B) {
 			for i := 0; i < b.N; i++ {
 				env.EditBuffer(*benchFile, fake.Edit{
 					Start: fake.Pos{Line: 0, Column: 0},
@@ -185,7 +188,6 @@
 				})
 				env.Await(StartedChange(uint64(i + 1)))
 			}
-			b.StopTimer()
 		})
 		printBenchmarkResults(result)
 	})
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index 9beb9e6..c7cd337 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -1985,9 +1985,9 @@
 	deleteInvalidMetadata := forceReloadMetadata || workspaceModeChanged
 	idsInSnapshot := map[PackageID]bool{} // track all known IDs
 	for uri, ids := range s.ids {
+		var resultIDs []PackageID
 		for _, id := range ids {
-			invalidateMetadata := idsToInvalidate[id]
-			if skipID[id] || (invalidateMetadata && deleteInvalidMetadata) {
+			if skipID[id] || deleteInvalidMetadata && idsToInvalidate[id] {
 				continue
 			}
 			// The ID is not reachable from any workspace package, so it should
@@ -1996,8 +1996,9 @@
 				continue
 			}
 			idsInSnapshot[id] = true
-			result.ids[uri] = append(result.ids[uri], id)
+			resultIDs = append(resultIDs, id)
 		}
+		result.ids[uri] = resultIDs
 	}
 
 	// Copy the package metadata. We only need to invalidate packages directly
diff --git a/internal/memoize/memoize.go b/internal/memoize/memoize.go
index b3f93b6..89f79c6 100644
--- a/internal/memoize/memoize.go
+++ b/internal/memoize/memoize.go
@@ -241,11 +241,11 @@
 		}
 
 		h.mu.Lock()
-		defer h.mu.Unlock()
 		if h.state == stateDestroyed {
 			panic(fmt.Sprintf("inheriting destroyed handle %#v (type %T) into generation %v", h.key, h.key, g.name))
 		}
 		h.generations[g] = struct{}{}
+		h.mu.Unlock()
 	}
 }