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 XPathQuery for repeated queries

  • Prefer attribute filtering over text filtering

  • Be specific in XPath expressions (avoid //)

  • Limit result sets with positional predicates

  • Reuse XMLDocument objects with reset()

  • Use XPath for bulk selection, iterate results in Python

  • Avoid unnecessary string conversions

  • For giant documents, prefer jsonify.stream_dump over building a full DOM -- see the memory panel above for what that trades away and gains