mirror of
https://github.com/apple/container.git
synced 2026-08-24 10:05:43 -05:00
perf(parser): add collection capacity hints to known-size loops (#1791)
- For result sets with known sizes, it's more efficient to supply the size as a capacity hint.
This commit is contained in:
@@ -24,6 +24,14 @@ import ContainerizationOS
|
||||
import Foundation
|
||||
import SystemPackage
|
||||
|
||||
// MARK: - Collection capacity hints
|
||||
// Methods in this file build arrays and dictionaries in loops where the final
|
||||
// size is known from the input parameter count. reserveCapacity() and
|
||||
// Dictionary(minimumCapacity:) avoid O(log n) reallocation copies as the
|
||||
// collection grows incrementally. While this is a micro-optimization for each
|
||||
// individual call, these methods execute on every `container run/create` and
|
||||
// the savings compound at scale.
|
||||
|
||||
/// A parsed volume specification from user input
|
||||
public struct ParsedVolume {
|
||||
public let name: String
|
||||
@@ -124,7 +132,7 @@ public struct Parser {
|
||||
}
|
||||
combined.append(contentsOf: Parser.env(envList: envs))
|
||||
|
||||
let deduped = combined.reduce(into: [String: String]()) { map, entry in
|
||||
let deduped = combined.reduce(into: [String: String](minimumCapacity: combined.count)) { map, entry in
|
||||
let key = String(entry.split(separator: "=", maxSplits: 1).first ?? Substring(entry))
|
||||
map[key] = entry
|
||||
}
|
||||
@@ -232,7 +240,7 @@ public struct Parser {
|
||||
}
|
||||
|
||||
public static func labels(_ rawLabels: [String]) throws -> [String: String] {
|
||||
var result: [String: String] = [:]
|
||||
var result: [String: String] = Dictionary(minimumCapacity: rawLabels.count)
|
||||
for label in rawLabels {
|
||||
if label.isEmpty {
|
||||
throw ContainerizationError(.invalidArgument, message: "label cannot be an empty string")
|
||||
@@ -330,8 +338,9 @@ public struct Parser {
|
||||
public static let defaultDirectives = ["type": "virtiofs"]
|
||||
|
||||
public static func tmpfsMounts(_ mounts: [String]) throws -> [Filesystem] {
|
||||
var result: [Filesystem] = []
|
||||
let mounts = mounts.dedupe()
|
||||
var result: [Filesystem] = []
|
||||
result.reserveCapacity(mounts.count)
|
||||
for tmpfs in mounts {
|
||||
let fs = Filesystem.tmpfs(destination: tmpfs, options: [])
|
||||
try validateMount(.filesystem(fs))
|
||||
@@ -341,8 +350,9 @@ public struct Parser {
|
||||
}
|
||||
|
||||
public static func mounts(_ rawMounts: [String], relativeTo basePath: URL? = nil) throws -> [VolumeOrFilesystem] {
|
||||
var mounts: [VolumeOrFilesystem] = []
|
||||
let rawMounts = rawMounts.dedupe()
|
||||
var mounts: [VolumeOrFilesystem] = []
|
||||
mounts.reserveCapacity(rawMounts.count)
|
||||
for mount in rawMounts {
|
||||
let m = try Parser.mount(mount, relativeTo: basePath)
|
||||
try validateMount(m)
|
||||
@@ -484,6 +494,7 @@ public struct Parser {
|
||||
|
||||
public static func volumes(_ rawVolumes: [String], relativeTo basePath: URL? = nil) throws -> [VolumeOrFilesystem] {
|
||||
var mounts: [VolumeOrFilesystem] = []
|
||||
mounts.reserveCapacity(rawVolumes.count)
|
||||
for volume in rawVolumes {
|
||||
let m = try Parser.volume(volume, relativeTo: basePath)
|
||||
try Parser.validateMount(m)
|
||||
@@ -593,6 +604,7 @@ public struct Parser {
|
||||
/// - Throws: ContainerizationError if parsing fails
|
||||
public static func publishPorts(_ rawPublishPorts: [String]) throws -> [PublishPort] {
|
||||
var publishPorts: [PublishPort] = []
|
||||
publishPorts.reserveCapacity(rawPublishPorts.count)
|
||||
|
||||
// Process each raw port string
|
||||
for socket in rawPublishPorts {
|
||||
@@ -726,6 +738,7 @@ public struct Parser {
|
||||
/// - Throws: ContainerizationError if parsing fails or a path is invalid
|
||||
public static func publishSockets(_ rawPublishSockets: [String]) throws -> [PublishSocket] {
|
||||
var sockets: [PublishSocket] = []
|
||||
sockets.reserveCapacity(rawPublishSockets.count)
|
||||
|
||||
// Process each raw socket string
|
||||
for socket in rawPublishSockets {
|
||||
@@ -919,6 +932,7 @@ public struct Parser {
|
||||
/// - nofile=1024:unlimited (soft=1024, hard=UINT64_MAX)
|
||||
public static func rlimits(_ rawUlimits: [String]) throws -> [ProcessConfiguration.Rlimit] {
|
||||
var rlimits: [ProcessConfiguration.Rlimit] = []
|
||||
rlimits.reserveCapacity(rawUlimits.count)
|
||||
var seenTypes: Set<String> = []
|
||||
|
||||
for ulimit in rawUlimits {
|
||||
@@ -1010,6 +1024,7 @@ public struct Parser {
|
||||
/// Returns normalized uppercase CAP_* strings.
|
||||
public static func capabilities(capAdd: [String], capDrop: [String]) throws -> (capAdd: [String], capDrop: [String]) {
|
||||
var normalizedAdd: [String] = []
|
||||
normalizedAdd.reserveCapacity(capAdd.count)
|
||||
for cap in capAdd {
|
||||
let upper = cap.uppercased()
|
||||
if upper == "ALL" {
|
||||
@@ -1024,6 +1039,7 @@ public struct Parser {
|
||||
}
|
||||
|
||||
var normalizedDrop: [String] = []
|
||||
normalizedDrop.reserveCapacity(capDrop.count)
|
||||
for cap in capDrop {
|
||||
let upper = cap.uppercased()
|
||||
if upper == "ALL" {
|
||||
|
||||
@@ -24,6 +24,11 @@ import Foundation
|
||||
import Logging
|
||||
import TerminalProgress
|
||||
|
||||
// MARK: - Collection capacity hints
|
||||
// Dictionary(minimumCapacity:) and reserveCapacity() are used in this file to
|
||||
// pre-allocate storage when the final collection size is known from the input.
|
||||
// This avoids incremental reallocation overhead in hot-path parser methods.
|
||||
|
||||
public struct Utility {
|
||||
static let publishedPortCountLimit = 64
|
||||
|
||||
@@ -349,7 +354,7 @@ public struct Utility {
|
||||
/// - Parameter pairs: Array of strings in "key=value" format
|
||||
/// - Returns: Dictionary mapping keys to values
|
||||
public static func parseKeyValuePairs(_ pairs: [String]) -> [String: String] {
|
||||
var result: [String: String] = [:]
|
||||
var result: [String: String] = Dictionary(minimumCapacity: pairs.count)
|
||||
for pair in pairs {
|
||||
let components = pair.split(separator: "=", maxSplits: 1)
|
||||
if components.count == 2 {
|
||||
|
||||
@@ -1336,4 +1336,61 @@ struct ParserTest {
|
||||
func testManagementFlagsAcceptsNoDNSAlone() throws {
|
||||
_ = try Flags.Management.parse(["--no-dns"])
|
||||
}
|
||||
|
||||
// MARK: - Collection capacity hints
|
||||
|
||||
@Test("labels with large input preserves all entries")
|
||||
func testLabelsLargeInput() throws {
|
||||
let labels = (0..<100).map { "key\($0)=value\($0)" }
|
||||
let result = try Parser.labels(labels)
|
||||
#expect(result.count == 100)
|
||||
#expect(result["key42"] == "value42")
|
||||
#expect(result["key99"] == "value99")
|
||||
}
|
||||
|
||||
@Test("resolve with large input preserves all entries")
|
||||
func testParseKeyValuePairsLargeInput() {
|
||||
let pairs = (0..<100).map { "key\($0)=value\($0)" }
|
||||
let result = Utility.parseKeyValuePairs(pairs)
|
||||
#expect(result.count == 100)
|
||||
#expect(result["key0"] == "value0")
|
||||
#expect(result["key99"] == "value99")
|
||||
}
|
||||
|
||||
@Test("tmpfsMounts with large input")
|
||||
func testTmpfsMountsLargeInput() throws {
|
||||
let mounts = (0..<20).map { "/mnt/tmpfs\($0)" }
|
||||
let result = try Parser.tmpfsMounts(mounts)
|
||||
#expect(result.count == 20)
|
||||
}
|
||||
|
||||
@Test("volumes with large input")
|
||||
func testVolumesLargeInput() throws {
|
||||
let volumes = (0..<20).map { "vol\($0):/mnt/vol\($0)" }
|
||||
let result = try Parser.volumes(volumes)
|
||||
#expect(result.count == 20)
|
||||
}
|
||||
|
||||
@Test("capabilities with large input")
|
||||
func testCapabilitiesLargeInput() throws {
|
||||
let result = try Parser.capabilities(capAdd: ["ALL", "SYS_ADMIN", "NET_RAW", "CHOWN"], capDrop: ["SETUID", "KILL"])
|
||||
#expect(result.capAdd.count == 4)
|
||||
#expect(result.capDrop.count == 2)
|
||||
#expect(result.capAdd.first == "ALL")
|
||||
}
|
||||
|
||||
@Test("rlimits with large input")
|
||||
func testRlimitsLargeInput() throws {
|
||||
let result = try Parser.rlimits(["nofile=1024:2048", "nproc=100:200", "memlock=65536:65536"])
|
||||
#expect(result.count == 3)
|
||||
#expect(result[0].limit == "RLIMIT_NOFILE")
|
||||
}
|
||||
|
||||
@Test("allEnv with large env lists")
|
||||
func testAllEnvLargeInput() throws {
|
||||
let imageEnvs = (0..<50).map { "IMAGE_VAR\($0)=value\($0)" }
|
||||
let envs = (0..<50).map { "USER_VAR\($0)=value\($0)" }
|
||||
let result = try Parser.allEnv(imageEnvs: imageEnvs, envFiles: [], envs: envs)
|
||||
#expect(result.count == 100)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user