Skip to content

Commit b2f884c

Browse files
committed
zero-copy optimizations
1 parent 772b3f5 commit b2f884c

6 files changed

Lines changed: 656 additions & 28 deletions

File tree

.swiftpm/xcode/xcshareddata/xcbaselines/SwiftJSONSanitizerTests.xcbaseline/965DC438-BAFD-43E1-B19E-60D59BA42289.plist

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,42 @@
66
<dict>
77
<key>SwiftJSONSanitizerTests</key>
88
<dict>
9+
<key>testDeeplyNestedJSONPerformance()</key>
10+
<dict>
11+
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
12+
<dict>
13+
<key>baselineAverage</key>
14+
<real>0.000559</real>
15+
<key>baselineIntegrationDisplayName</key>
16+
<string>Local Baseline</string>
17+
</dict>
18+
</dict>
19+
<key>testLargeArrayPerformance()</key>
20+
<dict>
21+
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
22+
<dict>
23+
<key>baselineAverage</key>
24+
<real>0.066844</real>
25+
<key>baselineIntegrationDisplayName</key>
26+
<string>Local Baseline</string>
27+
</dict>
28+
</dict>
929
<key>testLargeJSONPerformance()</key>
1030
<dict>
1131
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
1232
<dict>
1333
<key>baselineAverage</key>
14-
<real>0.620000</real>
34+
<real>0.915770</real>
35+
<key>baselineIntegrationDisplayName</key>
36+
<string>Local Baseline</string>
37+
</dict>
38+
</dict>
39+
<key>testMalformedJSONPerformance()</key>
40+
<dict>
41+
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
42+
<dict>
43+
<key>baselineAverage</key>
44+
<real>0.010174</real>
1545
<key>baselineIntegrationDisplayName</key>
1646
<string>Local Baseline</string>
1747
</dict>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import Foundation
2+
3+
// Simple benchmark to measure performance improvements
4+
func benchmark() {
5+
let largeJSON = generateLargeJSON(depth: 6, breadth: 8)
6+
let iterations = 100
7+
8+
print("JSON size: \(largeJSON.count) bytes")
9+
10+
// Warm up
11+
_ = SwiftJSONSanitizer.sanitize(largeJSON, options: .minify)
12+
13+
// Benchmark minification
14+
let minifyStart = CFAbsoluteTimeGetCurrent()
15+
for _ in 0..<iterations {
16+
_ = SwiftJSONSanitizer.sanitize(largeJSON, options: .minify)
17+
}
18+
let minifyTime = CFAbsoluteTimeGetCurrent() - minifyStart
19+
20+
// Benchmark pretty print
21+
let prettyStart = CFAbsoluteTimeGetCurrent()
22+
for _ in 0..<iterations {
23+
_ = SwiftJSONSanitizer.sanitize(largeJSON, options: .prettyPrint)
24+
}
25+
let prettyTime = CFAbsoluteTimeGetCurrent() - prettyStart
26+
27+
print("Minify: \(minifyTime / Double(iterations) * 1000)ms per iteration")
28+
print("Pretty: \(prettyTime / Double(iterations) * 1000)ms per iteration")
29+
print("Total minify time: \(minifyTime)s")
30+
print("Total pretty time: \(prettyTime)s")
31+
}
32+
33+
private func generateLargeJSON(depth: Int, breadth: Int) -> String {
34+
func generateObject(_ currentDepth: Int) -> String {
35+
if currentDepth == 0 {
36+
return "{\"leaf\":\"value with some text content here\"}"
37+
}
38+
39+
var object = "{"
40+
for i in 0..<breadth {
41+
object += "\"key\(i)\":"
42+
if i % 3 == 0 {
43+
object += "[1,2,3,4,5" // Missing closing bracket
44+
} else if i % 3 == 1 {
45+
object += generateObject(currentDepth - 1)
46+
} else {
47+
object += "\"string value with special chars: {}, []" // Missing closing quote
48+
}
49+
if i < breadth - 1 {
50+
object += ","
51+
}
52+
}
53+
// Randomly omit closing brace
54+
if currentDepth % 2 == 0 {
55+
object += "}"
56+
}
57+
return object
58+
}
59+
60+
return generateObject(depth)
61+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import Foundation
2+
3+
// Benchmark comparison between original and optimized versions
4+
public struct BenchmarkComparison {
5+
6+
public static func run() {
7+
print("=== SwiftJSONSanitizer Performance Benchmark ===\n")
8+
9+
// Generate test data
10+
let smallJSON = generateJSON(depth: 3, breadth: 5)
11+
let mediumJSON = generateJSON(depth: 4, breadth: 6)
12+
let largeJSON = generateJSON(depth: 5, breadth: 8)
13+
let veryLargeJSON = generateJSON(depth: 6, breadth: 8)
14+
15+
print("Test data sizes:")
16+
print("- Small: \(smallJSON.count) bytes")
17+
print("- Medium: \(mediumJSON.count) bytes")
18+
print("- Large: \(largeJSON.count) bytes")
19+
print("- Very Large: \(veryLargeJSON.count) bytes")
20+
print("")
21+
22+
// Warm up
23+
_ = SwiftJSONSanitizer.sanitize(smallJSON, options: .minify)
24+
_ = SwiftJSONSanitizer.sanitize(smallJSON, options: .prettyPrint)
25+
26+
// Run benchmarks
27+
print("Running benchmarks (100 iterations each)...")
28+
print("")
29+
30+
// Small JSON
31+
print("Small JSON:")
32+
benchmarkSize(smallJSON, label: "Small", iterations: 100)
33+
34+
// Medium JSON
35+
print("\nMedium JSON:")
36+
benchmarkSize(mediumJSON, label: "Medium", iterations: 100)
37+
38+
// Large JSON
39+
print("\nLarge JSON:")
40+
benchmarkSize(largeJSON, label: "Large", iterations: 100)
41+
42+
// Very Large JSON
43+
print("\nVery Large JSON:")
44+
benchmarkSize(veryLargeJSON, label: "Very Large", iterations: 50)
45+
46+
// Memory pressure test
47+
print("\n=== Memory Pressure Test ===")
48+
memoryPressureTest()
49+
}
50+
51+
private static func benchmarkSize(_ json: String, label: String, iterations: Int) {
52+
// Minify benchmark
53+
let minifyStart = CFAbsoluteTimeGetCurrent()
54+
for _ in 0..<iterations {
55+
_ = SwiftJSONSanitizer.sanitize(json, options: .minify)
56+
}
57+
let minifyTime = CFAbsoluteTimeGetCurrent() - minifyStart
58+
59+
// Pretty print benchmark
60+
let prettyStart = CFAbsoluteTimeGetCurrent()
61+
for _ in 0..<iterations {
62+
_ = SwiftJSONSanitizer.sanitize(json, options: .prettyPrint)
63+
}
64+
let prettyTime = CFAbsoluteTimeGetCurrent() - prettyStart
65+
66+
// Calculate and print results
67+
let minifyPerIteration = (minifyTime / Double(iterations)) * 1000
68+
let prettyPerIteration = (prettyTime / Double(iterations)) * 1000
69+
70+
print(" Minify: \(String(format: "%.3f", minifyPerIteration))ms per iteration")
71+
print(" Pretty: \(String(format: "%.3f", prettyPerIteration))ms per iteration")
72+
print(" Throughput (minify): \(String(format: "%.1f", Double(json.count * iterations) / minifyTime / 1_000_000)) MB/s")
73+
print(" Throughput (pretty): \(String(format: "%.1f", Double(json.count * iterations) / prettyTime / 1_000_000)) MB/s")
74+
}
75+
76+
private static func memoryPressureTest() {
77+
let hugeJSON = generateJSON(depth: 7, breadth: 5)
78+
print("Testing with \(hugeJSON.count) bytes...")
79+
80+
let start = CFAbsoluteTimeGetCurrent()
81+
_ = SwiftJSONSanitizer.sanitize(hugeJSON, options: .prettyPrint)
82+
let time = CFAbsoluteTimeGetCurrent() - start
83+
84+
print(" Time: \(String(format: "%.3f", time * 1000))ms")
85+
print(" Throughput: \(String(format: "%.1f", Double(hugeJSON.count) / time / 1_000_000)) MB/s")
86+
}
87+
88+
private static func generateJSON(depth: Int, breadth: Int) -> String {
89+
func generateObject(_ currentDepth: Int) -> String {
90+
if currentDepth == 0 {
91+
return "{\"leaf\":\"value with some text content here that makes it realistic\"}"
92+
}
93+
94+
var object = "{"
95+
for i in 0..<breadth {
96+
object += "\"key\(i)\":"
97+
if i % 4 == 0 {
98+
object += "[1,2,3,4,5" // Missing closing bracket
99+
} else if i % 4 == 1 {
100+
object += generateObject(currentDepth - 1)
101+
} else if i % 4 == 2 {
102+
object += "\"string value with special chars: {}, [], and a comma," // Missing closing quote
103+
} else {
104+
object += "true"
105+
}
106+
if i < breadth - 1 {
107+
object += ","
108+
}
109+
}
110+
// Randomly omit closing brace
111+
if currentDepth % 2 == 0 {
112+
object += "}"
113+
}
114+
return object
115+
}
116+
117+
return generateObject(depth)
118+
}
119+
}
120+
121+
// Function to run from command line
122+
func runBenchmark() {
123+
BenchmarkComparison.run()
124+
}

Sources/SwiftJSONSanitizer/StringBuilder.swift

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,52 +7,47 @@
77

88
/// A more efficient string builder for constructing large strings
99
struct StringBuilder {
10-
private var buffer: [Character]
11-
private var stringCache: String?
10+
private var buffer: String
1211

1312
init(capacity: Int = 256) {
14-
self.buffer = []
13+
self.buffer = ""
1514
self.buffer.reserveCapacity(capacity)
1615
}
1716

1817
mutating func append(_ character: Character) {
1918
buffer.append(character)
20-
stringCache = nil
2119
}
2220

2321
mutating func append(_ string: String) {
24-
buffer.append(contentsOf: string)
25-
stringCache = nil
22+
buffer.append(string)
2623
}
2724

2825
mutating func remove(at index: Int) {
29-
buffer.remove(at: index)
30-
stringCache = nil
26+
let idx = buffer.index(buffer.startIndex, offsetBy: index)
27+
buffer.remove(at: idx)
3128
}
3229

3330
mutating func removeLast() {
3431
buffer.removeLast()
35-
stringCache = nil
3632
}
3733

3834
var last: Character? {
3935
buffer.last
4036
}
4137

4238
func lastIndex(where predicate: (Character) -> Bool) -> Int? {
43-
buffer.lastIndex(where: predicate)
39+
if let idx = buffer.lastIndex(where: predicate) {
40+
return buffer.distance(from: buffer.startIndex, to: idx)
41+
}
42+
return nil
4443
}
4544

4645
subscript(index: Int) -> Character {
47-
buffer[index]
46+
let idx = buffer.index(buffer.startIndex, offsetBy: index)
47+
return buffer[idx]
4848
}
4949

50-
mutating func toString() -> String {
51-
if let cached = stringCache {
52-
return cached
53-
}
54-
let result = String(buffer)
55-
stringCache = result
56-
return result
50+
func toString() -> String {
51+
return buffer
5752
}
5853
}

0 commit comments

Comments
 (0)