Skip to content

Commit 855f66f

Browse files
committed
Merge dependencies more effectively in D.Solver.Modular.IndexConversion.
This commit is a more thorough fix for the problem that 4f7ac10 attempted to fix. 4f7ac10 combined all dependencies on the same package within the same build-depends or build-tool-depends field, in order to avoid performance problems in the solver QuickCheck tests. That wasn't enough to prevent the solver from creating many duplicate dependencies when converting the PackageDescription to the solver-specific format, though. When a package contained many conditionals that each contained the same dependency in both branches, the solver lifted those dependencies out of the conditionals without deduplicating them. This commit moves the step that merges dependencies after the step that lifts common dependencies out of conditionals.
1 parent 1cf9cea commit 855f66f

File tree

4 files changed

+72
-31
lines changed

4 files changed

+72
-31
lines changed

cabal-install/Distribution/Solver/Modular/Dependency.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ data Dep qpn = Dep (Maybe UnqualComponentName) qpn CI -- ^ dependency on a pack
123123
-- everything needed for creating ConflictSets or describing conflicts in solver
124124
-- log messages.
125125
data DependencyReason qpn = DependencyReason qpn (Map Flag FlagValue) (S.Set Stanza)
126-
deriving (Functor, Eq, Show)
126+
deriving (Functor, Eq, Ord, Show)
127127

128128
-- | Print the reason that a dependency was introduced.
129129
showDependencyReason :: DependencyReason QPN -> String

cabal-install/Distribution/Solver/Modular/Flag.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ newtype WeakOrTrivial = WeakOrTrivial { unWeakOrTrivial :: Bool }
8080
-- | Value shown for a flag in a solver log message. The message can refer to
8181
-- only the true choice, only the false choice, or both choices.
8282
data FlagValue = FlagTrue | FlagFalse | FlagBoth
83-
deriving (Eq, Show)
83+
deriving (Eq, Ord, Show)
8484

8585
showQFNBool :: QFN -> Bool -> String
8686
showQFNBool qfn@(FN qpn _f) b = showQPN qpn ++ ":" ++ showFBool qfn b

cabal-install/Distribution/Solver/Modular/IndexConversion.hs

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ module Distribution.Solver.Modular.IndexConversion
33
) where
44

55
import Data.List as L
6-
import Data.Map (Map)
7-
import qualified Data.Map as M
6+
import Distribution.Compat.Map.Strict (Map)
7+
import qualified Distribution.Compat.Map.Strict as M
88
import Data.Maybe
99
import Data.Monoid as Mon
1010
import Data.Set as S
@@ -263,8 +263,14 @@ convCondTree :: Map FlagName Bool -> DependencyReason PN -> PackageDescription -
263263
SolveExecutables ->
264264
CondTree ConfVar [Dependency] a -> FlaggedDeps PN
265265
convCondTree flags dr pkg os arch cinfo pn fds comp getInfo ipns solveExes@(SolveExecutables solveExes') (CondNode info ds branches) =
266+
-- Merge all library and build-tool dependencies at every level in
267+
-- the tree of flagged dependencies. Otherwise 'extractCommon'
268+
-- could create duplicate dependencies, and the number of
269+
-- duplicates could grow exponentially from the leaves to the root
270+
-- of the tree.
271+
mergeSimpleDeps $
266272
L.map (\d -> D.Simple (convLibDep dr d) comp)
267-
(mergeDeps $ mapMaybe (filterIPNs ipns) ds) -- unconditional package dependencies
273+
(mapMaybe (filterIPNs ipns) ds) -- unconditional package dependencies
268274
++ L.map (\e -> D.Simple (LDep dr (Ext e)) comp) (PD.allExtensions bi) -- unconditional extension dependencies
269275
++ L.map (\l -> D.Simple (LDep dr (Lang l)) comp) (PD.allLanguages bi) -- unconditional language dependencies
270276
++ L.map (\(PkgconfigDependency pkn vr) -> D.Simple (LDep dr (Pkg pkn vr)) comp) (PD.pkgconfigDepends bi) -- unconditional pkg-config dependencies
@@ -274,27 +280,35 @@ convCondTree flags dr pkg os arch cinfo pn fds comp getInfo ipns solveExes@(Solv
274280
-- is True. It might be false in the legacy solver
275281
-- codepath, in which case there won't be any record of
276282
-- an executable we need.
277-
++ L.map (\d -> D.Simple (convExeDep dr d) comp)
278-
(mergeExeDeps
279-
[ exeDep
283+
++ [ D.Simple (convExeDep dr exeDep) comp
280284
| solveExes'
281285
, exeDep <- getAllToolDependencies pkg bi
282286
, not $ isInternal pkg exeDep
283-
])
287+
]
284288
where
285289
bi = getInfo info
286290

287-
-- Combine dependencies on the same package.
288-
mergeDeps :: [Dependency] -> [Dependency]
289-
mergeDeps deps =
290-
L.map (uncurry Dependency) $ M.toList $
291-
M.fromListWith (.&&.) [(p, vr) | Dependency p vr <- deps]
292-
293-
-- Combine dependencies on the same package and executable.
294-
mergeExeDeps :: [ExeDependency] -> [ExeDependency]
295-
mergeExeDeps deps =
296-
L.map (\((p, exe), vr) -> ExeDependency p exe vr) $ M.toList $
297-
M.fromListWith (.&&.) [((p, exe), vr) | ExeDependency p exe vr <- deps]
291+
data SimpleFlaggedDepKey qpn =
292+
SimpleFlaggedDepKey (DependencyReason qpn) (Maybe UnqualComponentName) qpn Component
293+
deriving (Eq, Ord)
294+
295+
-- | Merge 'Simple' dependencies that apply to the same library or build-tool.
296+
mergeSimpleDeps :: Ord qpn => FlaggedDeps qpn -> FlaggedDeps qpn
297+
mergeSimpleDeps deps = L.map (uncurry toFlaggedDep) (M.toList merged) ++ unmerged
298+
where
299+
(merged, unmerged) = L.foldl' f (M.empty, []) deps
300+
where
301+
f :: Ord qpn
302+
=> (Map (SimpleFlaggedDepKey qpn) VR, FlaggedDeps qpn)
303+
-> FlaggedDep qpn
304+
-> (Map (SimpleFlaggedDepKey qpn) VR, FlaggedDeps qpn)
305+
f (merged', unmerged') (D.Simple (LDep dr (Dep mExe qpn (Constrained vr))) comp) =
306+
(M.insertWith (.&&.) (SimpleFlaggedDepKey dr mExe qpn comp) vr merged', unmerged')
307+
f (merged', unmerged') unmergeableDep = (merged', unmergeableDep : unmerged')
308+
309+
toFlaggedDep :: SimpleFlaggedDepKey qpn -> VR -> FlaggedDep qpn
310+
toFlaggedDep (SimpleFlaggedDepKey dr mExe qpn comp) vr =
311+
D.Simple (LDep dr (Dep mExe qpn (Constrained vr))) comp
298312

299313
-- | Branch interpreter. Mutually recursive with 'convCondTree'.
300314
--

cabal-install/tests/UnitTests/Distribution/Solver/Modular/MemoryUsage.hs

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ tests = [
1212
, runTest $ flagsTest "package with many flags"
1313
, runTest $ issue2899 "issue #2899"
1414
, runTest $ duplicateDependencies "duplicate dependencies"
15+
, runTest $ duplicateFlaggedDependencies "duplicate flagged dependencies"
1516
]
1617

1718
-- | This test solves for n packages that each have two versions. There is no
@@ -50,17 +51,14 @@ flagsTest name =
5051

5152
pkgs :: ExampleDb
5253
pkgs = [Right $ exAv "pkg" 1 $
53-
[exFlagged (flagName n) [ExAny "unknown1"] [ExAny "unknown2"]]
54+
[exFlagged (numberedFlag n) [ExAny "unknown1"] [ExAny "unknown2"]]
5455

5556
-- The remaining flags have no effect:
56-
++ [exFlagged (flagName i) [] [] | i <- [1..n - 1]]
57+
++ [exFlagged (numberedFlag i) [] [] | i <- [1..n - 1]]
5758
]
5859

59-
flagName :: Int -> ExampleFlagName
60-
flagName x = "flag-" ++ show x
61-
6260
orderedFlags :: [ExampleVar]
63-
orderedFlags = [F QualNone "pkg" (flagName i) | i <- [1..n]]
61+
orderedFlags = [F QualNone "pkg" (numberedFlag i) | i <- [1..n]]
6462

6563
-- | Test for a space leak caused by sharing of search trees under packages with
6664
-- link choices (issue #2899).
@@ -132,7 +130,7 @@ issue2899 name =
132130
-- pattern in the example above.
133131
--
134132
-- Now the solver avoids this issue by combining all dependencies on the same
135-
-- package within a build-depends field before lifting them out of conditionals.
133+
-- package before lifting them out of conditionals.
136134
--
137135
-- This test case is an expanded version of the example above, with library and
138136
-- build-tool dependencies.
@@ -153,11 +151,40 @@ duplicateDependencies name =
153151
flaggedDependencies :: Int -> [ExampleDependency]
154152
flaggedDependencies n
155153
| n > depth = buildDepends
156-
| otherwise = [exFlagged (flagName n) buildDepends
157-
(flaggedDependencies (n + 1))]
154+
| otherwise = [exFlagged (numberedFlag n) buildDepends
155+
(flaggedDependencies (n + 1))]
158156
where
159157
buildDepends = replicate copies (ExFix "B" 1)
160158
++ replicate copies (ExBuildToolFix "B" "exe" 1)
161159

162-
flagName :: Int -> ExampleFlagName
163-
flagName x = "flag-" ++ show x
160+
-- | This test is similar to duplicateDependencies, except that every dependency
161+
-- on B is replaced by a conditional that contains B in both branches. It tests
162+
-- that the solver doesn't just combine dependencies within one build-depends or
163+
-- build-tool-depends field; it also needs to combine dependencies after they
164+
-- are lifted out of conditionals.
165+
duplicateFlaggedDependencies :: String -> SolverTest
166+
duplicateFlaggedDependencies name =
167+
mkTest pkgs name ["A"] $ solverSuccess [("A", 1), ("B", 1)]
168+
where
169+
copies, depth :: Int
170+
copies = 15
171+
depth = 15
172+
173+
pkgs :: ExampleDb
174+
pkgs = [
175+
Right $ exAv "A" 1 (flaggedDependencies 1)
176+
, Right $ exAv "B" 1 [] `withExe` ExExe "exe" []
177+
]
178+
179+
flaggedDependencies :: Int -> [ExampleDependency]
180+
flaggedDependencies n
181+
| n > depth = buildDepends
182+
| otherwise = [exFlagged (numberedFlag n) buildDepends
183+
(flaggedDependencies (n + 1))]
184+
where
185+
buildDepends = replicate copies flaggedDep
186+
flaggedDep = exFlagged (numberedFlag n) deps deps
187+
deps = [ExFix "B" 1, ExBuildToolFix "B" "exe" 1]
188+
189+
numberedFlag :: Int -> ExampleFlagName
190+
numberedFlag n = "flag-" ++ show n

0 commit comments

Comments
 (0)