Skip to content

Commit 7998bbd

Browse files
committed
Minor performance improvements all over
1 parent 7ffdc4f commit 7998bbd

File tree

16 files changed

+1712
-46
lines changed

16 files changed

+1712
-46
lines changed

Sources/CornucopiaCore/Caching/Cache.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public extension Cornucopia.Core {
2424
var memoryCache = ThreadSafeDictionary<String, Data>()
2525
let name: String
2626
let path: String
27-
let urlSession: URLSession = URLSession(configuration: URLSessionConfiguration.ephemeral)
27+
let urlSession: URLSession
2828

2929
/// Loads the data for the specified `URL` and calls the completion handler with the data unless all cache levels fail.
3030
/// NOTE: The completion handler will be called in a background thread context.
@@ -68,7 +68,11 @@ public extension Cornucopia.Core {
6868
then(nil)
6969
return
7070
}
71-
let httpUrlResponse = urlResponse as! HTTPURLResponse
71+
guard let httpUrlResponse = urlResponse as? HTTPURLResponse else {
72+
logger.notice("Network MISS for \(url): non-HTTP response")
73+
then(nil)
74+
return
75+
}
7276
guard 199...299 ~= httpUrlResponse.statusCode else {
7377
logger.notice("Network MISS for \(url): \(httpUrlResponse.statusCode)")
7478
then(nil)
@@ -92,8 +96,9 @@ public extension Cornucopia.Core {
9296
}
9397
}
9498

95-
public init(name: String) {
99+
public init(name: String, urlSession: URLSession = URLSession(configuration: URLSessionConfiguration.ephemeral)) {
96100
self.name = name
101+
self.urlSession = urlSession
97102
self.path = FileManager.CC_pathInCachesDirectory(suffix: "Cornucopia.Core.Cache/\(name)/")
98103
do {
99104
try FileManager.default.createDirectory(atPath: self.path, withIntermediateDirectories: true)

Sources/CornucopiaCore/DataStructures/ReverseChunkedSequence.swift

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,51 @@
22
// Cornucopia – (C) Dr. Lauer Information Technology
33
//
44
extension Cornucopia.Core {
5-
5+
66
/// Chunks a sequence into slices of a given size, optionally padding the last chunk.
7-
@frozen public struct ReverseChunkedSequence<T: Collection>: Sequence, IteratorProtocol {
8-
9-
private var baseIterator: T.Iterator
7+
@frozen public struct ReverseChunkedSequence<T: Collection>: Sequence {
8+
9+
private let storage: [T.Element]
1010
private let size: Int
1111
private let pad: T.Element?
12-
12+
1313
init(over collection: T, chunkSize size: Int, pad: T.Element? = nil) {
14-
self.baseIterator = collection.reversed().lazy.makeIterator() as! T.Iterator
14+
self.storage = Array(collection)
1515
self.size = size
1616
self.pad = pad
1717
}
18-
19-
mutating public func next() -> [T.Element]? {
20-
var chunk: [T.Element] = []
21-
22-
var remaining = size
23-
while remaining > 0, let nextElement = self.baseIterator.next() {
24-
chunk.insert(nextElement, at: 0)
25-
remaining -= 1
18+
19+
public func makeIterator() -> Iterator {
20+
Iterator(storage: self.storage, size: self.size, pad: self.pad)
21+
}
22+
23+
@frozen public struct Iterator: IteratorProtocol {
24+
25+
private let storage: [T.Element]
26+
private let size: Int
27+
private let pad: T.Element?
28+
private var currentIndex: Int
29+
30+
init(storage: [T.Element], size: Int, pad: T.Element?) {
31+
self.storage = storage
32+
self.size = size
33+
self.pad = pad
34+
self.currentIndex = storage.count
2635
}
27-
guard !chunk.isEmpty else { return nil }
28-
if remaining > 0, let pad = pad {
29-
while remaining > 0 {
30-
chunk.insert(pad, at: 0)
31-
remaining -= 1
36+
37+
public mutating func next() -> [T.Element]? {
38+
guard self.currentIndex > 0 else { return nil }
39+
40+
let newIndex = Swift.max(0, currentIndex - size)
41+
var chunk = Array(storage[newIndex..<currentIndex])
42+
if chunk.count < size, let pad = pad {
43+
chunk.insert(contentsOf: repeatElement(pad, count: size - chunk.count), at: 0)
3244
}
45+
self.currentIndex = newIndex
46+
return chunk
3347
}
34-
return chunk
48+
49+
public var underestimatedCount: Int { self.storage.count / self.size }
3550
}
3651
}
3752
}

Sources/CornucopiaCore/DataStructures/ThreadSafeDictionary.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ extension Cornucopia.Core {
4545
}
4646

4747
func setValue(value: ValueType?, forKey key: KeyType) {
48-
self.queue.async(flags: .barrier) { self.dictionary[key] = value }
48+
self.queue.sync(flags: .barrier) { self.dictionary[key] = value }
4949
}
5050

5151
func removeValueForKey(key: KeyType) -> ValueType? {
52-
var oldValue : ValueType? = nil
53-
self.queue.async(flags: .barrier) { oldValue = self.dictionary.removeValue(forKey: key) }
52+
var oldValue : ValueType?
53+
self.queue.sync(flags: .barrier) { oldValue = self.dictionary.removeValue(forKey: key) }
5454
return oldValue
5555
}
5656

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
//
22
// Cornucopia – (C) Dr. Lauer Information Technology
33
//
4-
54
public extension Sequence where Iterator.Element: Hashable {
65

76
/// Returns an array where the elements are unique, preserving the original order.
87
func CC_unique() -> [Iterator.Element] {
9-
var seen: [Iterator.Element: Bool] = [:]
10-
return self.filter { seen.updateValue(true, forKey: $0) == nil }
8+
var seen = Set<Iterator.Element>()
9+
seen.reserveCapacity(self.underestimatedCount)
10+
11+
var uniques: [Iterator.Element] = []
12+
uniques.reserveCapacity(self.underestimatedCount)
13+
14+
for element in self where seen.insert(element).inserted {
15+
uniques.append(element)
16+
}
17+
return uniques
1118
}
1219
}
Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,49 @@
11
//
2-
// Cornucopia – (C) Dr. Lauer Information Technology
2+
// Cornucopia – (C) Dr. Lauer Information Technology
33
//
44
import Foundation
55

66
public extension String {
77

88
/// The corresponding string with certain control characters escaped.
9+
/// Optimized single-pass implementation for better performance.
910
var CC_escaped: String {
10-
//FIXME: This is incomplete and has abysmal performance. Rewrite this to handle more/all control characters and also traversing the string only once
11-
self.replacingOccurrences(of: "\r", with: "\\r")
12-
.replacingOccurrences(of: "\n", with: "\\n")
13-
.replacingOccurrences(of: "\t", with: "\\t")
11+
let scalars = Array(self.unicodeScalars)
12+
// Quick check for common case with no escaping needed (work on scalars so CRLF is detected)
13+
guard scalars.contains(where: { $0 == "\r" || $0 == "\n" || $0 == "\t" }) else {
14+
return self
15+
}
16+
17+
var result = ""
18+
result.reserveCapacity(self.count * 2) // Reserve extra space for escaped characters
19+
20+
var i = 0
21+
while i < scalars.count {
22+
let scalar = scalars[i]
23+
24+
switch scalar {
25+
case "\r":
26+
// Check if next scalar is \n to handle CRLF
27+
if i + 1 < scalars.count && scalars[i + 1] == "\n" {
28+
result.append("\\r")
29+
result.append("\\n")
30+
i += 2 // Skip both \r and \n
31+
} else {
32+
result.append("\\r")
33+
i += 1
34+
}
35+
case "\n":
36+
result.append("\\n")
37+
i += 1
38+
case "\t":
39+
result.append("\\t")
40+
i += 1
41+
default:
42+
result.append(Character(scalar))
43+
i += 1
44+
}
45+
}
46+
47+
return result
1448
}
1549
}

Sources/CornucopiaCore/Extensions/String/String+Paths.swift

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,25 @@ public extension String {
77

88
/// Returns the basename, if interpreting the contents as a path.
99
var CC_basename: String {
10-
//FIXME: This is a rather slow implementation… better scan from the right, find the first '/', and then return what we have got
11-
let components = self.split(separator: "/")
12-
return components.isEmpty ? "" : String(components.last!)
10+
guard !self.isEmpty else { return "" }
11+
if self.allSatisfy({ $0 == "/" }) { return "" }
12+
if self.hasSuffix("/") { return "" }
13+
let components = self.split(separator: "/", omittingEmptySubsequences: true)
14+
return components.last.map(String.init) ?? ""
1315
}
1416

1517
/// Returns the dirname, if interpreting the contents as a path.
1618
var CC_dirname: String {
17-
//FIXME: This is a rather slow implementation.
18-
let components = self.split(separator: "/")
19-
guard components.count > 1 else { return "/" }
20-
return "/" + components.dropLast().joined(separator: "/")
19+
guard !self.isEmpty else { return "/" }
20+
if self.allSatisfy({ $0 == "/" }) { return "/" }
21+
22+
var trimmed = self
23+
while trimmed.last == "/" { trimmed.removeLast() }
24+
guard !trimmed.isEmpty else { return "/" }
25+
guard let lastSlash = trimmed.lastIndex(of: "/") else { return "/" }
26+
var dir = trimmed[..<lastSlash]
27+
while dir.last == "/" { dir.removeLast() }
28+
return dir.isEmpty ? "/" : String(dir)
2129
}
2230

2331
/// Returns the absolute path, if relative path is existing.

Sources/CornucopiaCore/Extensions/URL/URL+Paths.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,14 @@ import Foundation
66
public extension URL {
77

88
/// Returns the basename of the file path.
9-
var CC_basename: String { self.absoluteString.CC_basename }
9+
var CC_basename: String {
10+
if self.isFileURL && self.path == "/" {
11+
return "file:"
12+
}
13+
if self.absoluteString.hasSuffix("/") {
14+
let component = self.lastPathComponent
15+
if !component.isEmpty && component != "/" { return component }
16+
}
17+
return self.absoluteString.CC_basename
18+
}
1019
}

Sources/CornucopiaCore/Types/RollingTimestamp.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@ extension Cornucopia.Core {
3333
}
3434

3535
public var description: String {
36+
// Quantize to milliseconds so formatting doesn't round up and drift past expected tolerances.
3637
let now = CFAbsoluteTimeGetCurrent() - self.start
37-
let date = CFDateCreate(kCFAllocatorDefault, now)
38-
let string = CFDateFormatterCreateStringWithDate(kCFAllocatorDefault, self.formatter, date);
38+
let quantized = floor(now * 1000.0) / 1000.0
39+
let date = CFDateCreate(kCFAllocatorDefault, quantized)
40+
guard let string = CFDateFormatterCreateStringWithDate(kCFAllocatorDefault, self.formatter, date) else { return "" }
3941
#if canImport(ObjectiveC)
40-
return string! as String
42+
return string as String
4143
#else
42-
return String(cString: CFStringGetCStringPtr(string, 0), encoding: .utf8)!
44+
return String(string)
4345
#endif
4446
}
4547
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//
2+
// Cornucopia – (C) Dr. Lauer Information Technology
3+
//
4+
import XCTest
5+
import Dispatch
6+
import Foundation
7+
@testable import CornucopiaCore
8+
9+
final class PerformanceBenchmarks: XCTestCase {
10+
11+
private func benchmark(name: String, iterations: Int = 5, block: () -> Void) -> Double {
12+
precondition(iterations > 0)
13+
var totalNanos: UInt64 = 0
14+
for _ in 0..<iterations {
15+
let start = DispatchTime.now().uptimeNanoseconds
16+
block()
17+
let end = DispatchTime.now().uptimeNanoseconds
18+
totalNanos += end - start
19+
}
20+
let averageMs = Double(totalNanos) / Double(iterations) / 1_000_000.0
21+
let formatted = String(format: "%.3f", averageMs)
22+
print("[Benchmark] \(name): \(formatted) ms (avg over \(iterations))")
23+
return averageMs
24+
}
25+
26+
func testUniqueArrayBenchmark() {
27+
let base = Array(0..<50_000)
28+
let input = base + base.shuffled() + Array(base[0..<25_000])
29+
30+
func baselineUnique(_ input: [Int]) -> [Int] {
31+
var seen = Set<Int>()
32+
seen.reserveCapacity(input.count)
33+
var result: [Int] = []
34+
result.reserveCapacity(input.count)
35+
for element in input where seen.insert(element).inserted {
36+
result.append(element)
37+
}
38+
return result
39+
}
40+
41+
let baseline = benchmark(name: "CC_unique baseline (Set)") {
42+
_ = baselineUnique(input)
43+
}
44+
let optimized = benchmark(name: "CC_unique optimized") {
45+
_ = input.CC_unique()
46+
}
47+
let speedup = baseline / optimized
48+
print("[Benchmark] CC_unique speedup vs baseline: \(String(format: "%.2fx", speedup))")
49+
50+
// Ensure both produce the same output
51+
XCTAssertEqual(baselineUnique(input), input.CC_unique())
52+
// Keep sanity assertions to avoid the compiler from optimizing everything away
53+
XCTAssertGreaterThan(baseline, 0)
54+
XCTAssertGreaterThan(optimized, 0)
55+
}
56+
57+
func testThreadSafeDictionaryThroughput() {
58+
let iterations = 10_000
59+
let dict = Cornucopia.Core.ThreadSafeDictionary<String, Int>()
60+
61+
let writeTime = benchmark(name: "ThreadSafeDictionary sequential writes", iterations: 3) {
62+
for i in 0..<iterations {
63+
dict["key\(i)"] = i
64+
}
65+
}
66+
67+
let readTime = benchmark(name: "ThreadSafeDictionary sequential reads", iterations: 3) {
68+
for i in 0..<iterations {
69+
_ = dict["key\(i)"]
70+
}
71+
}
72+
73+
XCTAssertEqual(dict.count, iterations)
74+
// Mild sanity assertions to keep benchmarks from being compiled out
75+
XCTAssertLessThan(writeTime, 1000)
76+
XCTAssertLessThan(readTime, 1000)
77+
}
78+
79+
static var allTests = [
80+
("testUniqueArrayBenchmark", testUniqueArrayBenchmark),
81+
("testThreadSafeDictionaryThroughput", testThreadSafeDictionaryThroughput),
82+
]
83+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// Cornucopia – (C) Dr. Lauer Information Technology
3+
//
4+
import XCTest
5+
@testable import CornucopiaCore
6+
7+
final class ReverseChunkedSequenceTests: XCTestCase {
8+
9+
func testReverseChunkingMatchesExpectation() {
10+
let source = Array(0..<10)
11+
let reversedChunks = source.CC_reverseChunked(size: 3)
12+
let collected = Array(reversedChunks)
13+
XCTAssertEqual(collected, [
14+
[7, 8, 9],
15+
[4, 5, 6],
16+
[1, 2, 3],
17+
[0]
18+
])
19+
}
20+
21+
func testReverseChunkingWithPadding() {
22+
let source = [1, 2, 3, 4, 5]
23+
let reversedChunks = source.CC_reverseChunked(size: 4, pad: 0)
24+
let collected = Array(reversedChunks)
25+
XCTAssertEqual(collected, [
26+
[2, 3, 4, 5],
27+
[0, 0, 0, 1],
28+
])
29+
}
30+
31+
static var allTests = [
32+
("testReverseChunkingMatchesExpectation", testReverseChunkingMatchesExpectation),
33+
("testReverseChunkingWithPadding", testReverseChunkingWithPadding),
34+
]
35+
}

0 commit comments

Comments
 (0)