LEPTRIS

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 — competitorC / C++RubyPython
libxml23.73×+
lxml2.85×+
nokogiri3.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.

operationleptris sidelibxml2 2.9.13standing
XPath //book0.75 µs4.43 µs5.91×
XPath //book[@id='101']0.73 µs14.67 µs20.10×
XPath count(//book)0.71 µs4.5 µs6.34×
XPath complex query2.53 µs16.02 µs6.33×
XPath //book | //magazine1.12 µs4.18 µs3.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.

operationleptris sidenokogiri 1.19.4standing
XPath count(//book)1.56 µs9.38 µs6.02×
XPath //book (100 nodes)2.93 µs9.81 µs3.35×
XPath //book[@id='50']2.49 µs56.06 µs22.55×
XPath //book[price > 50]6.29 µs71.72 µs11.40×
XPath //author | //title7.32 µs22.16 µs3.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.

operationleptris sidelxml 6.0.2standing
XPath count(//book)1.55 µs8.06 µs5.20×
XPath //book (100 nodes)3.3 µs11.79 µs3.57×
XPath //book[@id='50']1.87 µs36.05 µs19.28×
XPath //book[price > 50]5.8 µs50.1 µs8.64×
XPath //author | //title9.67 µs27.54 µs2.85×
XPath //book[@id=$id] (variable-bound)7.42 µs37.09 µs5.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.