Benchmarks · XPath 1.0
XPath evaluation
The XPath engine implements all thirteen axes, the twenty-seven core functions and the fifteen operators of XPath 1.0, and passes the complete W3C test suite with 438 of 438 cases. The measurements below cover evaluation cost through the C API and through both bindings, on the same medium-size fixture across languages.
| XPath 1.0 — competitor | C / C++ | Ruby | Python |
|---|---|---|---|
| libxml2 | 3.73×+ | — | — |
| lxml | — | — | 2.85×+ |
| nokogiri | — | 3.03×+ | — |
A green cell with a single value means every measured operation in that race is ahead, and the value shown is the smallest margin. A neutral cell shows the range from the weakest to the strongest operation; hovering lists the operations that remain behind. All rows, including deficits, appear in the tables below.
C and C++
libleptris versus libxml2
Five expression shapes are measured on a medium-size document: a simple path, a predicate, an aggregate function call, a complex predicate, and a union. Both engines evaluate the identical expression against the identical parsed document in the same process.
| operation | leptris side | libxml2 2.9.13 | standing |
|---|---|---|---|
| XPath //book | 0.75 µs | 4.43 µs | 5.91× |
| XPath //book[@id='101'] | 0.73 µs | 14.67 µs | 20.10× |
| XPath count(//book) | 0.71 µs | 4.5 µs | 6.34× |
| XPath complex query | 2.53 µs | 16.02 µs | 6.33× |
| XPath //book | //magazine | 1.12 µs | 4.18 µs | 3.73× |
fresh runlibleptris 1.9.143-dev (main eea85cf)·mean of 1000 (Release + LTO)·Apple M1 Max, macOS 14.1.1, arm64··DOM / SAX / XPath lane benches (bench_dom_*, bench_sax_*, bench_xpath_*)
To reproduce: build the benchmark targets as described on theXML page and run thebench_xpath_* binaries.
Ruby
The leptris gem versus Nokogiri
The same five expression shapes, measured through the Ruby API. The predicate form is the widest margin because Nokogiri re-evaluates the predicate through its full expression pipeline for every candidate node.
| operation | leptris side | nokogiri 1.19.4 | standing |
|---|---|---|---|
| XPath count(//book) | 1.56 µs | 9.38 µs | 6.02× |
| XPath //book (100 nodes) | 2.93 µs | 9.81 µs | 3.35× |
| XPath //book[@id='50'] | 2.49 µs | 56.06 µs | 22.55× |
| XPath //book[price > 50] | 6.29 µs | 71.72 µs | 11.40× |
| XPath //author | //title | 7.32 µs | 22.16 µs | 3.03× |
fresh runleptris 1.9.121.0 (gem, lockstep with libleptris 1.9.121)·mean per iteration (µs)·Apple M1 Max, macOS 14.1.1, arm64; Ruby 3.4.8··leptris_vs_nokogiri.rb — same fixtures, same iteration counts
Python
The leptris package versus lxml
The Python matrix includes two additional shapes: a variable-bound predicate, which exercises the compiled-evaluation path with external variables, and a namespaced nodeset query.
| operation | leptris side | lxml 6.0.2 | standing |
|---|---|---|---|
| XPath count(//book) | 1.55 µs | 8.06 µs | 5.20× |
| XPath //book (100 nodes) | 3.3 µs | 11.79 µs | 3.57× |
| XPath //book[@id='50'] | 1.87 µs | 36.05 µs | 19.28× |
| XPath //book[price > 50] | 5.8 µs | 50.1 µs | 8.64× |
| XPath //author | //title | 9.67 µs | 27.54 µs | 2.85× |
| XPath //book[@id=$id] (variable-bound) | 7.42 µs | 37.09 µs | 5.00× |
fresh runleptris 1.9.121.0 (binding) on libleptris 1.9.143-dev·mean per iteration·Apple M1 Max, macOS 14.1.1, arm64; Python 3.10.5··benchmarks/matrix.py — same fixtures as the Ruby matrix
XPath
Measured deficits
Every XPath operation in the result data that remains behind its competitor. There are none at present; this section remains in place because it is generated from the same result files as the tables above and will list any deficit that a future measurement records.