EventReader.next(), both initializers, and the Event alias are public (an internal typealias in a split file had been shadowing the public Parsing.Event); a new PureXMLPublicAPITests target compiles without testable import so facade gaps fail CI. The large-corpus benchmarks exposed document-order sorting as quadratic on flat fan-outs; the fix's first attempt was itself worse (an eager sibling table built per predicate evaluation), caught by sampling the live run, and replaced with a lazy table: single-node sets never touch order keys and a parent's table builds on its second lookup. XPath at 42 MB drops 27.1 s to 1.57 s (190x to 11.7x); at 210 MB PureXML completes in 9.4 s the query libxml2 refuses at its 10M node-set cap (#143 tracks our configurable budget). Benchmark harness: size series to 210 MB, CORPUS mode for real files (NASA, SwissProt measured), SAX stage, refusal-aware table, NULL-safe C driver. All conformance suites, Linux, and WASM verified. Closes #142.
64 lines
2.2 KiB
Swift
64 lines
2.2 KiB
Swift
// PureXML side of the benchmark (scripts/benchmark.sh). Times the same
|
|
// operations the C driver times, over the same file, with internal timing.
|
|
// Output: CSV lines "library,operation,bytes,seconds".
|
|
// Foundation is used for file access and timing only; the measured code is
|
|
// the Foundation-free PureXML library.
|
|
import Foundation
|
|
import PureXML
|
|
|
|
let arguments = CommandLine.arguments
|
|
guard arguments.count >= 3, let iterations = Int(arguments[2]) else {
|
|
FileHandle.standardError.write(Data("usage: bench-purexml <file.xml> <iterations>\n".utf8))
|
|
exit(1)
|
|
}
|
|
|
|
let path = arguments[1]
|
|
let source = try String(contentsOfFile: path, encoding: .utf8)
|
|
let bytes = source.utf8.count
|
|
|
|
func best(_ iterations: Int, _ body: () throws -> Void) rethrows -> Double {
|
|
var bestTime = Double.greatestFiniteMagnitude
|
|
for _ in 0 ..< iterations {
|
|
let start = Date()
|
|
try body()
|
|
bestTime = min(bestTime, -start.timeIntervalSinceNow)
|
|
}
|
|
return bestTime
|
|
}
|
|
|
|
// Parse.
|
|
var tree: PureXML.Model.TreeNode!
|
|
let parseTime = try best(iterations) {
|
|
tree = try PureXML.parseTree(source, limits: .init(allowDoctype: true))
|
|
}
|
|
|
|
print("purexml,parse,\(bytes),\(String(format: "%.6f", parseTime))")
|
|
|
|
/// SAX (event stream only): isolates scanner cost from tree construction.
|
|
let saxTime = try best(iterations) {
|
|
var reader = PureXML.events(source, limits: .init(allowDoctype: true))
|
|
while try reader.next() != nil {}
|
|
}
|
|
|
|
print("purexml,sax,\(bytes),\(String(format: "%.6f", saxTime))")
|
|
|
|
// Serialize.
|
|
let node = tree.node
|
|
var serialized = ""
|
|
let serializeTime = best(iterations) {
|
|
serialized = PureXML.serialize(node)
|
|
}
|
|
|
|
print("purexml,serialize,\(bytes),\(String(format: "%.6f", serializeTime))")
|
|
FileHandle.standardError.write(Data("purexml serialized bytes: \(serialized.utf8.count)\n".utf8))
|
|
|
|
// XPath: the same broad selection the C driver counts.
|
|
let query = try PureXML.XPath.Query("count(//item[@kind='even'])")
|
|
var count = 0.0
|
|
let xpathTime = try best(iterations) {
|
|
count = try query.value(at: tree).number
|
|
}
|
|
|
|
print("purexml,xpath,\(bytes),\(String(format: "%.6f", xpathTime))")
|
|
FileHandle.standardError.write(Data("purexml xpath count: \(Int(count))\n".utf8))
|