Performance¶
pygixml is designed for high-performance XML processing, leveraging the power of pugixml’s C++ implementation (and a streaming yxml layer for oversized documents) through Cython.
Benchmarks¶
The numbers come from the full benchmark suite in benchmarks/ --
parsing, dictify, objectify, and jsonify measured against lxml,
ElementTree, xmltodict, and xmljson, plus memory-at-scale and install
footprint. Nothing here is manually transcribed or rounded off: the
interactive report is generated straight from those runs.
That full interactive report lives at its own page, not embedded here -- see the benchmark report.
The report itself is refreshed on a schedule
(.github/workflows/benchmark.yml, weekly, plus on-demand), not on every
commit -- benchmark timing needs a stable, quiet machine to mean anything,
which a shared per-PR CI runner isn’t. So this can lag the very latest commit
by up to a week; it won’t ever be wildly out of date, but if you changed
something performance-sensitive today, don’t expect to see it reflected here
yet.
Reproducing or updating these numbers is one CMake build away -- see benchmarks/README.md for the full methodology (corpus generation, why timing uses best-of-N, and a real memory-measurement gotcha we hit building this exact report).
Performance Tips¶
Use XPathQuery for Repeated Queries¶
# ✅ Good: compile once, evaluate many times
query = pygixml.XPathQuery("book[@category='fiction']")
for _ in range(1000):
results = query.evaluate_node_set(root)
# ❌ Bad: re-compile every iteration
for _ in range(1000):
results = root.select_nodes("book[@category='fiction']")
Be Specific in XPath Expressions¶
# ✅ Good: specific path
books = root.select_nodes("library/book")
# ❌ Bad: descendant-axis search
books = root.select_nodes("//book")
Use Attributes for Filtering¶
# ✅ Good: fast attribute comparison
books = root.select_nodes("book[@id='123']")
# ❌ Bad: slower text-node comparison
books = root.select_nodes("book[id='123']")
Limit Result Sets¶
# ✅ Good: limit in the query
first_10 = root.select_nodes("book[position() <= 10]")
# ❌ Bad: fetch all then slice
all_books = root.select_nodes("book")
first_10 = all_books[:10]
Memory Management¶
Automatic Cleanup¶
pygixml automatically manages memory through C++ destructors:
# Memory is automatically freed when objects go out of scope
def process_large_xml():
doc = pygixml.parse_file("large_file.xml")
# ... process XML ...
# Memory automatically freed when function returns
Document Reset¶
# Reuse document to avoid reallocation
doc = pygixml.XMLDocument()
for filename in large_file_list:
doc.reset() # Clear existing content
doc.load_file(filename)
# ... process ...
Optimization Checklist¶
Use
XPathQueryfor repeated queriesPrefer attribute filtering over text filtering
Be specific in XPath expressions (avoid
//)Limit result sets with positional predicates
Reuse
XMLDocumentobjects withreset()Use XPath for bulk selection, iterate results in Python
Avoid unnecessary string conversions
For giant documents, prefer
jsonify.stream_dumpover building a full DOM -- see the memory panel above for what that trades away and gains