Skip to content

Commit 3dec504

Browse files
authored
Make hackageIndexLayout faster (#343)
Before these patches `hackageIndexLayout` takes up to 50% of `cabal update` time. With the patches applied this reduces to ~10% of `cabal update`, making it almost 2x faster. I originally developed this code for [`hackage-revdeps`](https://github.com/Bodigrim/hackage-revdeps/blob/master/src/Hackage/RevDeps.hs#L133), so the approach has been exercised for some time. Besides, `hackageIndexLayout` is covered by tests for `hackage-security`.
2 parents a07aed0 + 2570117 commit 3dec504

1 file changed

Lines changed: 34 additions & 10 deletions

File tree

  • hackage-security/src/Hackage/Security/TUF/Layout

‎hackage-security/src/Hackage/Security/TUF/Layout/Index.hs‎

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ module Hackage.Security.TUF.Layout.Index (
1010
) where
1111

1212
import Prelude
13+
import Data.Char (ord)
1314
import Data.Kind (Type)
1415
import Distribution.Package
1516
import Distribution.Text
17+
import Distribution.Types.Version (mkVersion)
18+
import System.FilePath.Posix (isPathSeparator)
1619

1720
import Hackage.Security.TUF.Paths
1821
import Hackage.Security.TUF.Signed
@@ -91,17 +94,38 @@ hackageIndexLayout = IndexLayout {
9194
fromFragments :: [String] -> IndexPath
9295
fromFragments = rootPath . joinFragments
9396

97+
-- This function is called in a hot loop, take care when modifying it.
98+
-- Especially avoid 'splitFragments' / 'splitDirectories' and other high-level helpers,
99+
-- they are very slow.
94100
fromPath :: IndexPath -> Maybe (Some IndexFile)
95-
fromPath fp = case splitFragments (unrootPath fp) of
96-
[pkg, version, _file] -> do
97-
pkgId <- simpleParse (pkg ++ "-" ++ version)
98-
case takeExtension fp of
99-
".cabal" -> return $ Some $ IndexPkgCabal pkgId
100-
".json" -> return $ Some $ IndexPkgMetadata pkgId
101-
_otherwise -> Nothing
102-
[pkg, "preferred-versions"] ->
103-
Some . IndexPkgPrefs <$> simpleParse pkg
104-
_otherwise -> Nothing
101+
fromPath fp = case break isPathSeparator (toUnrootedFilePath (unrootPath fp)) of
102+
(pkg, "/preferred-versions") ->
103+
return $ Some $ IndexPkgPrefs $ mkPackageName pkg
104+
(pkg, rest) -> case break isPathSeparator (drop 1 rest) of
105+
(_, []) -> Nothing
106+
(version, basename) -> do
107+
let pkgName = mkPackageName pkg
108+
pkgVersion = mkVersion $ readVersion version
109+
pkgId = PackageIdentifier { pkgName, pkgVersion }
110+
case reverse basename of
111+
-- ".cabal" reversed
112+
'l' : 'a' : 'b' : 'a' : 'c' : '.' : _ ->
113+
return $ Some $ IndexPkgCabal pkgId
114+
-- ".json" reversed
115+
'n' : 'o' : 's' : 'j' : '.' : _ ->
116+
return $ Some $ IndexPkgMetadata pkgId
117+
_ -> Nothing
118+
119+
-- Convert "3.12.1.0" to [3,12,1,0].
120+
-- Copied from hackage-revdeps package.
121+
readVersion :: String -> [Int]
122+
readVersion = (\(acc, _mult, rest) -> acc : rest) . foldr go (0, 1, [])
123+
where
124+
go c (acc, mult, rest)
125+
| fromIntegral d < (10 :: Word) = (acc + d * mult, mult * 10, rest)
126+
| otherwise = (0, 1, acc : rest)
127+
where
128+
d = ord c - ord '0'
105129

106130
{-------------------------------------------------------------------------------
107131
Utility

0 commit comments

Comments
 (0)