Optimize bottlenecks (#803)

This commit is contained in:
Michael Eisel 2020-04-03 12:31:48 -04:00 committed by GitHub
parent 01c6959f48
commit c3693d4098
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 82 additions and 19 deletions

View File

@ -1,6 +1,8 @@
# Change Log
## Next Version
#### Added
- Improve speed of metadata parsing and dependency resolution. [#803](https://github.com/yonaskolb/XcodeGen/pull/803) @michaeleisel
## 2.15.1

View File

@ -4,18 +4,51 @@ import PathKit
import Yams
extension Dictionary where Key: JSONKey {
public func json<T: NamedJSONDictionaryConvertible>(atKeyPath keyPath: JSONUtilities.KeyPath, invalidItemBehaviour: InvalidItemBehaviour<T> = .remove) throws -> [T] {
public func json<T: NamedJSONDictionaryConvertible>(atKeyPath keyPath: JSONUtilities.KeyPath, invalidItemBehaviour: InvalidItemBehaviour<T> = .remove, parallel: Bool = false) throws -> [T] {
guard let dictionary = json(atKeyPath: keyPath) as JSONDictionary? else {
return []
}
var items: [T] = []
for (key, _) in dictionary {
let jsonDictionary: JSONDictionary = try dictionary.json(atKeyPath: .key(key))
let item = try T(name: key, jsonDictionary: jsonDictionary)
items.append(item)
if parallel {
let defaultError = NSError(domain: "Unspecified error", code: 0, userInfo: nil)
var itemResults: [Result<T, Error>] = Array(repeating: .failure(defaultError), count: dictionary.count)
var ops: [BlockOperation] = []
var idx: Int = 0
for (key, _) in dictionary {
ops.append(BlockOperation { [idx] in
do {
let jsonDictionary: JSONDictionary = try dictionary.json(atKeyPath: .key(key))
let item = try T(name: key, jsonDictionary: jsonDictionary)
itemResults[idx] = .success(item)
} catch {
itemResults[idx] = .failure(error)
}
})
idx += 1
}
let queue = OperationQueue()
queue.qualityOfService = .userInteractive
queue.maxConcurrentOperationCount = 8
queue.addOperations(ops, waitUntilFinished: true)
var items = ContiguousArray<T>()
items.reserveCapacity(itemResults.count)
for result in itemResults {
switch result {
case .failure(let error):
throw error
case .success(let item):
items.append(item)
}
}
return Array(items)
} else {
var items: [T] = []
for (key, _) in dictionary {
let jsonDictionary: JSONDictionary = try dictionary.json(atKeyPath: .key(key))
let item = try T(name: key, jsonDictionary: jsonDictionary)
items.append(item)
}
return items
}
return items
}
public func json<T: NamedJSONConvertible>(atKeyPath keyPath: JSONUtilities.KeyPath, invalidItemBehaviour: InvalidItemBehaviour<T> = .remove) throws -> [T] {

View File

@ -171,7 +171,7 @@ extension Project {
let configs: [String: String] = jsonDictionary.json(atKeyPath: "configs") ?? [:]
self.configs = configs.isEmpty ? Config.defaultConfigs :
configs.map { Config(name: $0, type: ConfigType(rawValue: $1)) }.sorted { $0.name < $1.name }
targets = try jsonDictionary.json(atKeyPath: "targets").sorted { $0.name < $1.name }
targets = try jsonDictionary.json(atKeyPath: "targets", parallel: true).sorted { $0.name < $1.name }
aggregateTargets = try jsonDictionary.json(atKeyPath: "aggregateTargets").sorted { $0.name < $1.name }
projectReferences = try jsonDictionary.json(atKeyPath: "projectReferences").sorted { $0.name < $1.name }
schemes = try jsonDictionary.json(atKeyPath: "schemes")

View File

@ -10,11 +10,14 @@ import ProjectSpec
import PathKit
public class CarthageDependencyResolver {
static func getBuildPath(_ project: Project) -> String {
return project.options.carthageBuildPath ?? "Carthage/Build"
}
/// Carthage's base build path as specified by the
/// project's `SpecOptions`, or `Carthage/Build` by default
var buildPath: String {
project.options.carthageBuildPath ?? "Carthage/Build"
return CarthageDependencyResolver.getBuildPath(project)
}
/// Carthage's executable path as specified by the
@ -24,10 +27,11 @@ public class CarthageDependencyResolver {
}
private let project: Project
lazy var versionLoader = CarthageVersionLoader(buildPath: project.basePath + buildPath)
let versionLoader: CarthageVersionLoader
init(project: Project) {
self.project = project
versionLoader = CarthageVersionLoader(buildPath: project.basePath + CarthageDependencyResolver.getBuildPath(project))
}
/// Carthage's build path for the given platform

View File

@ -9,24 +9,48 @@ import Foundation
import PathKit
import ProjectSpec
class Mutex<T> {
var value: T
var semaphore: DispatchSemaphore = DispatchSemaphore(value: 1)
init(_ value: T) {
self.value = value
}
func get<U>(closure: (inout T) throws -> (U)) rethrows -> U {
semaphore.wait()
defer { semaphore.signal() }
return try closure(&value)
}
func get(closure: (inout T) -> ()) {
semaphore.wait()
closure(&value)
semaphore.signal()
}
}
// Note: this class can be accessed on multiple threads. It must therefore stay thread-safe.
class CarthageVersionLoader {
private let buildPath: Path
private var cachedFiles: [String: CarthageVersionFile] = [:]
private var cachedFilesMutex: Mutex<[String: CarthageVersionFile]> = Mutex([:])
init(buildPath: Path) {
self.buildPath = buildPath
}
func getVersionFile(for dependency: String) throws -> CarthageVersionFile {
if let versionFile = cachedFiles[dependency] {
return versionFile
return try cachedFilesMutex.get { cachedFiles in
if let versionFile = cachedFiles[dependency] {
return versionFile
}
let filePath = buildPath + ".\(dependency).version"
let data = try filePath.read()
let carthageVersionFile = try JSONDecoder().decode(CarthageVersionFile.self, from: data)
cachedFiles[dependency] = carthageVersionFile
return carthageVersionFile
}
let filePath = buildPath + ".\(dependency).version"
let data = try filePath.read()
let carthageVersionFile = try JSONDecoder().decode(CarthageVersionFile.self, from: data)
cachedFiles[dependency] = carthageVersionFile
return carthageVersionFile
}
}