Skip to content

Commit d143131

Browse files
committed
Fix quadratic memory usage in GetLocatedImports
At startup `GetLocatedImports` is called on all known files. Say you have 10000 modules in your project then this leads to 10000 calls to GetLocatedImports running concurrently. In `GetLocatedImports` the known targets are consulted and the targetsMap is created by mapping the known targets. This map is used for introducing sharing amongst filepaths. This operation copies a local copy of the `target` map which is local to the rule. ``` let targetsMap = HMap.mapWithKey const targets ``` So now each rule has a hashmap of size 10000 held locally to it and depending on how the threads are scheduled there will be 10000^2 elements in total allocated in hashmaps. This used a lot of memory. Solution: Return the normalising map in the result of the `GetKnownTargets` rule so it is shared across threads. Fixes #4317
1 parent c11f32b commit d143131

File tree

5 files changed

+37
-11
lines changed

5 files changed

+37
-11
lines changed

ghcide/session-loader/Development/IDE/Session.hs

+2-3
Original file line numberDiff line numberDiff line change
@@ -505,13 +505,12 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
505505
return [(targetTarget, Set.fromList found)]
506506
hasUpdate <- atomically $ do
507507
known <- readTVar knownTargetsVar
508-
let known' = flip mapHashed known $ \k ->
509-
HM.unionWith (<>) k $ HM.fromList knownTargets
508+
let known' = flip mapHashed known $ \k -> unionKnownTargets k (mkKnownTargets knownTargets)
510509
hasUpdate = if known /= known' then Just (unhashed known') else Nothing
511510
writeTVar knownTargetsVar known'
512511
pure hasUpdate
513512
for_ hasUpdate $ \x ->
514-
logWith recorder Debug $ LogKnownFilesUpdated x
513+
logWith recorder Debug $ LogKnownFilesUpdated (targetMap x)
515514
return $ toNoFileKey GetKnownTargets
516515

517516
-- Create a new HscEnv from a hieYaml root and a set of options

ghcide/src/Development/IDE/Core/Rules.hs

+1-2
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,7 @@ getLocatedImportsRule :: Recorder (WithPriority Log) -> Rules ()
321321
getLocatedImportsRule recorder =
322322
define (cmapWithPrio LogShake recorder) $ \GetLocatedImports file -> do
323323
ModSummaryResult{msrModSummary = ms} <- use_ GetModSummaryWithoutTimestamps file
324-
targets <- useNoFile_ GetKnownTargets
325-
let targetsMap = HM.mapWithKey const targets
324+
(KnownTargets targets targetsMap) <- useNoFile_ GetKnownTargets
326325
let imports = [(False, imp) | imp <- ms_textual_imps ms] ++ [(True, imp) | imp <- ms_srcimps ms]
327326
env_eq <- use_ GhcSession file
328327
let env = hscEnvWithImportPaths env_eq

ghcide/src/Development/IDE/Core/Shake.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
module Development.IDE.Core.Shake(
2525
IdeState, shakeSessionInit, shakeExtras, shakeDb, rootDir,
2626
ShakeExtras(..), getShakeExtras, getShakeExtrasRules,
27-
KnownTargets, Target(..), toKnownFiles,
27+
KnownTargets(..), Target(..), toKnownFiles, unionKnownTargets, mkKnownTargets,
2828
IdeRule, IdeResult,
2929
GetModificationTime(GetModificationTime, GetModificationTime_, missingFileDiagnostics),
3030
shakeOpen, shakeShut,
@@ -691,7 +691,7 @@ shakeOpen recorder lspEnv defaultConfig idePlugins debouncer
691691
publishedDiagnostics <- STM.newIO
692692
semanticTokensCache <- STM.newIO
693693
positionMapping <- STM.newIO
694-
knownTargetsVar <- newTVarIO $ hashed HMap.empty
694+
knownTargetsVar <- newTVarIO $ hashed emptyKnownTargets
695695
let restartShakeSession = shakeRestart recorder ideState
696696
persistentKeys <- newTVarIO mempty
697697
indexPending <- newTVarIO HMap.empty

ghcide/src/Development/IDE/Plugin/Completions.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ getCompletionsLSP ide plId
180180
pm <- useWithStaleFast GetParsedModule npath
181181
binds <- fromMaybe (mempty, zeroMapping) <$> useWithStaleFast GetBindings npath
182182
knownTargets <- liftIO $ runAction "Completion" ide $ useNoFile GetKnownTargets
183-
let localModules = maybe [] Map.keys knownTargets
183+
let localModules = maybe [] (Map.keys . targetMap) knownTargets
184184
let lModules = mempty{importableModules = map toModueNameText localModules}
185185
-- set up the exports map including both package and project-level identifiers
186186
packageExportsMapIO <- fmap(envPackageExports . fst) <$> useWithStaleFast GhcSession npath

ghcide/src/Development/IDE/Types/KnownTargets.hs

+31-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
{-# LANGUAGE DeriveAnyClass #-}
22
{-# LANGUAGE DerivingStrategies #-}
3-
module Development.IDE.Types.KnownTargets (KnownTargets, Target(..), toKnownFiles) where
3+
module Development.IDE.Types.KnownTargets ( KnownTargets(..)
4+
, emptyKnownTargets
5+
, mkKnownTargets
6+
, unionKnownTargets
7+
, Target(..)
8+
, toKnownFiles) where
49

510
import Control.DeepSeq
611
import Data.Hashable
@@ -14,11 +19,34 @@ import Development.IDE.Types.Location
1419
import GHC.Generics
1520

1621
-- | A mapping of module name to known files
17-
type KnownTargets = HashMap Target (HashSet NormalizedFilePath)
22+
data KnownTargets = KnownTargets
23+
{ targetMap :: !(HashMap Target (HashSet NormalizedFilePath))
24+
-- normalising map is a cached copy of `HMap.mapKey const targetMap`
25+
, normalisingMap :: !(HashMap Target Target) } deriving Show
26+
27+
28+
unionKnownTargets :: KnownTargets -> KnownTargets -> KnownTargets
29+
unionKnownTargets (KnownTargets tm nm) (KnownTargets tm' nm') =
30+
KnownTargets (HMap.unionWith (<>) tm tm') (HMap.union nm nm')
31+
32+
mkKnownTargets :: [(Target, HashSet NormalizedFilePath)] -> KnownTargets
33+
mkKnownTargets vs = KnownTargets (HMap.fromList vs) (HMap.fromList [(k,k) | (k,_) <- vs ])
34+
35+
instance NFData KnownTargets where
36+
rnf (KnownTargets tm nm) = rnf tm `seq` rnf nm `seq` ()
37+
38+
instance Eq KnownTargets where
39+
k1 == k2 = targetMap k1 == targetMap k2
40+
41+
instance Hashable KnownTargets where
42+
hashWithSalt s (KnownTargets hm _) = hashWithSalt s hm
43+
44+
emptyKnownTargets :: KnownTargets
45+
emptyKnownTargets = KnownTargets HMap.empty HMap.empty
1846

1947
data Target = TargetModule ModuleName | TargetFile NormalizedFilePath
2048
deriving ( Eq, Ord, Generic, Show )
2149
deriving anyclass (Hashable, NFData)
2250

2351
toKnownFiles :: KnownTargets -> HashSet NormalizedFilePath
24-
toKnownFiles = HSet.unions . HMap.elems
52+
toKnownFiles = HSet.unions . HMap.elems . targetMap

0 commit comments

Comments
 (0)