@@ -76,7 +76,7 @@ type snapshot struct {
76
76
files map [span.URI ]source.VersionedFileHandle
77
77
78
78
// goFiles maps a parseKey to its parseGoHandle.
79
- goFiles map [ parseKey ] * parseGoHandle
79
+ goFiles * goFileMap
80
80
81
81
// TODO(rfindley): consider merging this with files to reduce burden on clone.
82
82
symbols map [span.URI ]* symbolHandle
@@ -663,16 +663,17 @@ func (s *snapshot) transitiveReverseDependencies(id PackageID, ids map[PackageID
663
663
func (s * snapshot ) getGoFile (key parseKey ) * parseGoHandle {
664
664
s .mu .Lock ()
665
665
defer s .mu .Unlock ()
666
- return s .goFiles [ key ]
666
+ return s .goFiles . get ( key )
667
667
}
668
668
669
669
func (s * snapshot ) addGoFile (key parseKey , pgh * parseGoHandle ) * parseGoHandle {
670
670
s .mu .Lock ()
671
671
defer s .mu .Unlock ()
672
- if existing , ok := s .goFiles [key ]; ok {
673
- return existing
672
+
673
+ if prev := s .goFiles .get (key ); prev != nil {
674
+ return prev
674
675
}
675
- s .goFiles [ key ] = pgh
676
+ s .goFiles . set ( key , pgh )
676
677
return pgh
677
678
}
678
679
@@ -811,6 +812,8 @@ func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]stru
811
812
patterns := map [string ]struct {}{
812
813
fmt .Sprintf ("**/*.{%s}" , extensions ): {},
813
814
}
815
+
816
+ // Add a pattern for each Go module in the workspace that is not within the view.
814
817
dirs := s .workspace .dirs (ctx , s )
815
818
for _ , dir := range dirs {
816
819
dirName := dir .Filename ()
@@ -830,14 +833,19 @@ func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]stru
830
833
// contain Go code (golang/go#42348). To handle this, explicitly watch all
831
834
// of the directories in the workspace. We find them by adding the
832
835
// directories of every file in the snapshot's workspace directories.
833
- var dirNames []string
834
- for _ , uri := range s .getKnownSubdirs (dirs ) {
835
- dirNames = append (dirNames , uri .Filename ())
836
- }
837
- sort .Strings (dirNames )
838
- if len (dirNames ) > 0 {
836
+ // There may be thousands.
837
+ knownSubdirs := s .getKnownSubdirs (dirs )
838
+ if n := len (knownSubdirs ); n > 0 {
839
+ dirNames := make ([]string , 0 , n )
840
+ for _ , uri := range knownSubdirs {
841
+ dirNames = append (dirNames , uri .Filename ())
842
+ }
843
+ sort .Strings (dirNames )
844
+ // The double allocation of Sprintf(Join()) accounts for 8%
845
+ // of DidChange, but specializing doesn't appear to help. :(
839
846
patterns [fmt .Sprintf ("{%s}" , strings .Join (dirNames , "," ))] = struct {}{}
840
847
}
848
+
841
849
return patterns
842
850
}
843
851
@@ -874,7 +882,7 @@ func (s *snapshot) getKnownSubdirs(wsDirs []span.URI) []span.URI {
874
882
}
875
883
s .unprocessedSubdirChanges = nil
876
884
877
- var result []span.URI
885
+ result := make ( []span.URI , 0 , len ( s . knownSubdirs ))
878
886
for uri := range s .knownSubdirs {
879
887
result = append (result , uri )
880
888
}
@@ -1719,7 +1727,7 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
1719
1727
packages : make (map [packageKey ]* packageHandle , len (s .packages )),
1720
1728
actions : make (map [actionKey ]* actionHandle , len (s .actions )),
1721
1729
files : make (map [span.URI ]source.VersionedFileHandle , len (s .files )),
1722
- goFiles : make ( map [ parseKey ] * parseGoHandle , len ( s .goFiles ) ),
1730
+ goFiles : s .goFiles . clone ( ),
1723
1731
symbols : make (map [span.URI ]* symbolHandle , len (s .symbols )),
1724
1732
workspacePackages : make (map [PackageID ]PackagePath , len (s .workspacePackages )),
1725
1733
unloadableFiles : make (map [span.URI ]struct {}, len (s .unloadableFiles )),
@@ -1764,12 +1772,27 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
1764
1772
result .parseWorkHandles [k ] = v
1765
1773
}
1766
1774
1767
- for k , v := range s .goFiles {
1768
- if _ , ok := changes [k .file .URI ]; ok {
1769
- continue
1775
+ // Copy the handles of all Go source files.
1776
+ // There may be tens of thousands of files,
1777
+ // but changes are typically few, so we
1778
+ // use a striped map optimized for this case
1779
+ // and visit its stripes in parallel.
1780
+ var (
1781
+ toDeleteMu sync.Mutex
1782
+ toDelete []parseKey
1783
+ )
1784
+ s .goFiles .forEachConcurrent (func (k parseKey , v * parseGoHandle ) {
1785
+ if changes [k .file .URI ] == nil {
1786
+ // no change (common case)
1787
+ newGen .Inherit (v .handle )
1788
+ } else {
1789
+ toDeleteMu .Lock ()
1790
+ toDelete = append (toDelete , k )
1791
+ toDeleteMu .Unlock ()
1770
1792
}
1771
- newGen .Inherit (v .handle )
1772
- result .goFiles [k ] = v
1793
+ })
1794
+ for _ , k := range toDelete {
1795
+ result .goFiles .delete (k )
1773
1796
}
1774
1797
1775
1798
// Copy all of the go.mod-related handles. They may be invalidated later,
@@ -1975,21 +1998,34 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
1975
1998
deleteInvalidMetadata := forceReloadMetadata || workspaceModeChanged
1976
1999
idsInSnapshot := map [PackageID ]bool {} // track all known IDs
1977
2000
for uri , ids := range s .meta .ids {
1978
- var resultIDs []PackageID
1979
- for _ , id := range ids {
2001
+ // Optimization: ids slices are typically numerous, short (<3),
2002
+ // and rarely modified by this loop, so don't allocate copies
2003
+ // until necessary.
2004
+ var resultIDs []PackageID // nil implies equal to ids[:i:i]
2005
+ for i , id := range ids {
1980
2006
if skipID [id ] || deleteInvalidMetadata && idsToInvalidate [id ] {
2007
+ resultIDs = ids [:i :i ] // unshare
1981
2008
continue
1982
2009
}
1983
2010
// The ID is not reachable from any workspace package, so it should
1984
2011
// be deleted.
1985
2012
if ! reachableID [id ] {
2013
+ resultIDs = ids [:i :i ] // unshare
1986
2014
continue
1987
2015
}
1988
2016
idsInSnapshot [id ] = true
1989
- resultIDs = append (resultIDs , id )
2017
+ if resultIDs != nil {
2018
+ resultIDs = append (resultIDs , id )
2019
+ }
2020
+ }
2021
+ if resultIDs == nil {
2022
+ resultIDs = ids
1990
2023
}
1991
2024
result .meta .ids [uri ] = resultIDs
1992
2025
}
2026
+ // TODO(adonovan): opt: represent PackageID as an index into a process-global
2027
+ // dup-free list of all package names ever seen, then use a bitmap instead of
2028
+ // a hash table for "PackageSet" (e.g. idsInSnapshot).
1993
2029
1994
2030
// Copy the package metadata. We only need to invalidate packages directly
1995
2031
// containing the affected file, and only if it changed in a relevant way.
@@ -2259,7 +2295,7 @@ func metadataChanges(ctx context.Context, lockedSnapshot *snapshot, oldFH, newFH
2259
2295
// lockedSnapshot must be locked.
2260
2296
func peekOrParse (ctx context.Context , lockedSnapshot * snapshot , fh source.FileHandle , mode source.ParseMode ) (* source.ParsedGoFile , error ) {
2261
2297
key := parseKey {file : fh .FileIdentity (), mode : mode }
2262
- if pgh := lockedSnapshot .goFiles [ key ] ; pgh != nil {
2298
+ if pgh := lockedSnapshot .goFiles . get ( key ) ; pgh != nil {
2263
2299
cached := pgh .handle .Cached (lockedSnapshot .generation )
2264
2300
if cached != nil {
2265
2301
cached := cached .(* parseGoData )
@@ -2547,3 +2583,107 @@ func readGoSum(dst map[module.Version][]string, file string, data []byte) error
2547
2583
}
2548
2584
return nil
2549
2585
}
2586
+
2587
+ // -- goFileMap --
2588
+
2589
+ // A goFileMap is conceptually a map[parseKey]*parseGoHandle,
2590
+ // optimized for cloning all or nearly all entries.
2591
+ type goFileMap struct {
2592
+ // The map is represented as a map of 256 stripes, one per
2593
+ // distinct value of the top 8 bits of key.file.Hash.
2594
+ // Each stripe has an associated boolean indicating whether it
2595
+ // is shared, and thus immutable, and thus must be copied before any update.
2596
+ // (The bits could be packed but it hasn't been worth it yet.)
2597
+ stripes [256 ]map [parseKey ]* parseGoHandle
2598
+ exclusive [256 ]bool // exclusive[i] means stripe[i] is not shared and may be safely mutated
2599
+ }
2600
+
2601
+ // newGoFileMap returns a new empty goFileMap.
2602
+ func newGoFileMap () * goFileMap {
2603
+ return new (goFileMap ) // all stripes are shared (non-exclusive) nil maps
2604
+ }
2605
+
2606
+ // clone returns a copy of m.
2607
+ // For concurrency, it counts as an update to m.
2608
+ func (m * goFileMap ) clone () * goFileMap {
2609
+ m .exclusive = [256 ]bool {} // original and copy are now nonexclusive
2610
+ copy := * m
2611
+ return & copy
2612
+ }
2613
+
2614
+ // get returns the value for key k.
2615
+ func (m * goFileMap ) get (k parseKey ) * parseGoHandle {
2616
+ return m .stripes [m .hash (k )][k ]
2617
+ }
2618
+
2619
+ // set updates the value for key k to v.
2620
+ func (m * goFileMap ) set (k parseKey , v * parseGoHandle ) {
2621
+ m .unshare (k )[k ] = v
2622
+ }
2623
+
2624
+ // delete deletes the value for key k, if any.
2625
+ func (m * goFileMap ) delete (k parseKey ) {
2626
+ // TODO(adonovan): opt?: skip unshare if k isn't present.
2627
+ delete (m .unshare (k ), k )
2628
+ }
2629
+
2630
+ // forEachConcurrent calls f for each entry in the map.
2631
+ // Calls may be concurrent.
2632
+ // f must not modify m.
2633
+ func (m * goFileMap ) forEachConcurrent (f func (parseKey , * parseGoHandle )) {
2634
+ // Visit stripes in parallel chunks.
2635
+ const p = 16 // concurrency level
2636
+ var wg sync.WaitGroup
2637
+ wg .Add (p )
2638
+ for i := 0 ; i < p ; i ++ {
2639
+ chunk := m .stripes [i * p : (i + 1 )* p ]
2640
+ go func () {
2641
+ for _ , stripe := range chunk {
2642
+ for k , v := range stripe {
2643
+ f (k , v )
2644
+ }
2645
+ }
2646
+ wg .Done ()
2647
+ }()
2648
+ }
2649
+ wg .Wait ()
2650
+ }
2651
+
2652
+ // -- internal--
2653
+
2654
+ // hash returns 8 bits from the key's file digest.
2655
+ func (m * goFileMap ) hash (k parseKey ) int {
2656
+ h := k .file .Hash
2657
+ if h == "" {
2658
+ // Sadly the Hash isn't always a hash because cache.GetFile may
2659
+ // successfully return a *fileHandle containing an error and no hash.
2660
+ // Lump the duds together for now.
2661
+ // TODO(adonovan): fix the underlying bug.
2662
+ return 0
2663
+ }
2664
+ return unhex (h [0 ])<< 4 | unhex (h [1 ])
2665
+ }
2666
+
2667
+ // unhex returns the value of a valid hex digit.
2668
+ func unhex (b byte ) int {
2669
+ if '0' <= b && b <= '9' {
2670
+ return int (b - '0' )
2671
+ }
2672
+ return int (b ) & ^ 0x20 - 'A' + 0xA // [a-fA-F]
2673
+ }
2674
+
2675
+ // unshare makes k's stripe exclusive, allocating a copy if needed, and returns it.
2676
+ func (m * goFileMap ) unshare (k parseKey ) map [parseKey ]* parseGoHandle {
2677
+ i := m .hash (k )
2678
+ if ! m .exclusive [i ] {
2679
+ m .exclusive [i ] = true
2680
+
2681
+ // Copy the map.
2682
+ copy := make (map [parseKey ]* parseGoHandle , len (m .stripes [i ]))
2683
+ for k , v := range m .stripes [i ] {
2684
+ copy [k ] = v
2685
+ }
2686
+ m .stripes [i ] = copy
2687
+ }
2688
+ return m .stripes [i ]
2689
+ }
0 commit comments