Use fileKey instead of canonicalPath when deduping files

`resolveDependencies` showed up as a hotspot when profiling due to `canonicalPath` doing a lot of file system calls.
`fileKey` is more efficient and should guarantee uniqueness (`canonicalPath` may fail in the case of hardlinks).
The new code is 3x faster in our use case.
This commit is contained in:
Dave MacLachlan
2023-09-22 09:14:14 -07:00
committed by Space Team
parent 3673dff959
commit 722ddc9f91
2 changed files with 18 additions and 8 deletions
@@ -56,6 +56,18 @@ data class File constructor(internal val javaPath: Path) {
val listFilesOrEmpty: List<File>
get() = if (exists) listFiles else emptyList()
// A fileKey is an object that uniquely identifies the given file.
val fileKey: Any
get() {
// It is not guaranteed that all filesystems have fileKey. If not we fall
// back on canonicalPath which can be significantly slower to get.
var key = Files.readAttributes(javaPath, BasicFileAttributes::class.java).fileKey()
if (key == null) {
key = this.canonicalPath
}
return key
}
fun child(name: String) = File(this, name)
fun startsWith(another: File) = javaPath.startsWith(another.javaPath)
@@ -101,15 +101,14 @@ class KotlinLibraryResolverImpl<L: KotlinLibrary> internal constructor(
* 3. Creates resulting [KotlinLibraryResolveResult] object.
*/
override fun List<KotlinLibrary>.resolveDependencies(): KotlinLibraryResolveResult {
val rootLibraries = this.map { KotlinResolvedLibraryImpl(it) }
// As far as the list of root libraries is known from the very beginning, the result can be
// constructed from the very beginning as well.
val result = KotlinLibraryResolverResultImpl(rootLibraries)
val cache = mutableMapOf<File, KotlinResolvedLibrary>()
cache.putAll(rootLibraries.map { it.library.libraryFile.canonicalFile to it })
val cache = mutableMapOf<Any, KotlinResolvedLibrary>()
cache.putAll(rootLibraries.map { it.library.libraryFile.fileKey to it })
var newDependencies = rootLibraries
do {
@@ -119,12 +118,12 @@ class KotlinLibraryResolverImpl<L: KotlinLibrary> internal constructor(
.filterNot { searchPathResolver.isProvidedByDefault(it) }
.mapNotNull { searchPathResolver.resolve(it)?.let(::KotlinResolvedLibraryImpl) }
.map { resolved ->
val canonicalFile = resolved.library.libraryFile.canonicalFile
if (canonicalFile in cache) {
library.addDependency(cache[canonicalFile]!!)
val fileKey = resolved.library.libraryFile.fileKey
if (fileKey in cache) {
library.addDependency(cache[fileKey]!!)
null
} else {
cache.put(canonicalFile, resolved)
cache.put(fileKey, resolved)
library.addDependency(resolved)
resolved
}
@@ -133,7 +132,6 @@ class KotlinLibraryResolverImpl<L: KotlinLibrary> internal constructor(
.toList()
}.flatten()
} while (newDependencies.isNotEmpty())
return result
}
}