XML CLI benchmark · 4 августа 2026

Большой XML.
Честный latency.

26 реализаций на одном файле размером 2,32 MB. По 2000 запусков, строгая проверка malformed XML и отдельный namespace-контракт.

23
варианта в рейтинге
2000
наблюдений на строку
8.748
ms · лучший mean
CPU 0
один чистый контейнер

Практический выбор

Что ставить в CI

Mean определяет место. p99 и p99.9 показывают хвост, который среднее скрывает.

01без namespaces

TurboXml NativeAOT

8.748 ms mean

Самый быстрый вариант, если namespace semantics не входят в контракт.

Median
8.223
p99
14.495
p99.9
18.560
02namespaces ✓

XmlReader NativeAOT

10.112 ms mean

Лучший wrapper для полного namespace-контракта. В измеренной конфигурации DTD запрещены.

Median
9.612
p99
15.903
p99.9
20.562

23 strict-core варианта

Полный рейтинг

Все значения — миллисекунды. SD — выборочное стандартное отклонение.

полный namespace-контракт только strict core Рейтинг: арифметическое mean ↑

Таблица прокручивается по горизонтали →

Open-source XML CLI и wrapper-варианты, измеренные на XML fixture размером 2,319,739 bytes
Парсер / CLI Deployment и core Контракт Лицензия Min Mean Median p90 p95 p99 p99.9 SD Загрузка
1TurboXml 2.1.0C# · .NET 10 NativeAOT · streaming SAXstrict coreBSD-26.9608.7488.22310.91611.89114.49518.5601.603NuGet ↗
2System.Xml.XmlReaderC# · .NET 10 NativeAOT · streaming pullnamespaces ✓MIT7.86310.1129.61212.47513.53615.90320.5621.793.NET ↗
3Expat 2.8.2 · xmlwf -nC · Expat · streamingnamespaces ✓MIT10.57113.42612.84615.89917.23120.75924.0122.022Releases ↗
4Expat 2.8.2 · xmlwfC · Expat · streamingstrict coreMIT10.72213.74313.09116.72217.96021.56024.5772.192Releases ↗
5RXP 1.5.0 · -xsC · RXPstrict coreGPL-2.011.99115.54214.69118.99421.20627.65637.2363.116Upstream ↗
6RXP 1.5.0 · -NxsC · RXPnamespaces ✓GPL-2.012.23217.02215.93421.78624.43831.34651.4574.001Upstream ↗
7libxml2 2.9.14 · --streamC · libxml2strict coreMIT13.72017.23116.61220.57021.95425.02129.3482.438Releases ↗
8XMLStarlet 1.6.1C · libxml2strict coreMIT13.87618.08717.31822.04223.97328.35334.9043.019Download ↗
9Xerces-C 3.2.4 · -nC++ · SAX2 · namespaces disabledstrict coreApache-2.017.46622.52021.75726.73228.87433.51837.2843.158Download ↗
10Xerces-C 3.2.4C++ · SAX2 · namespace processingnamespaces ✓Apache-2.018.22623.60522.85428.05529.90334.52638.2333.298Download ↗
11sclable/xml-lint 0.9.0PHP 8.4 · DOM · libxml2namespaces ✓MIT45.86556.70655.50964.65168.09675.69895.4966.342Packagist ↗
12TurboXml 2.1.0C# · .NET 10 JIT · streaming SAXstrict coreBSD-250.20066.55164.60376.36781.71992.017106.2278.038NuGet ↗
13System.Xml.XmlReaderC# · .NET 10 JIT · streaming pullnamespaces ✓MIT51.42366.99465.98975.98179.95085.823115.1297.209.NET ↗
14mickaelandrieu/xml-linter 2.0.0PHP 8.4 · DOM · libxml2strict coreMIT55.68768.07466.90676.88880.28988.451104.9546.728Packagist ↗
15anyxml-cli 0.5.0Rust · anyxml 0.13.0strict coreMIT74.44689.69888.45799.902103.399112.467134.3328.012crates.io ↗
16JAXB RI 4.0.9 NativeGraalVM CE 25.2.4 · JDK SAX + JAXBnamespaces ✓EDL/BSD-379.01196.51594.793107.941112.867127.174155.0629.646GraalVM ↗
17xmltodict 1.0.4Python 3.13 · pyexpatstrict coreMIT115.697138.724135.937153.442161.614183.983223.88613.051PyPI ↗
18kislyuk/yq 4.1.2 · xqPython · xmltodict + jqstrict coreApache-2.0227.569263.784261.367286.390295.802323.214341.29818.012PyPI ↗
19XML::Twig 3.52Perl · XML::Parser + Expatstrict coreArtistic/GPL473.756555.101548.583606.071634.026696.985772.45942.246MetaCPAN ↗
20JAXB RI 4.0.9 JVMJava 21 · JDK SAX + JAXBnamespaces ✓EDL/BSD-3644.205737.767732.928792.378818.895892.022985.51246.131Maven ↗
21SaxonHE12NetXQuery 12.9.10.NET 10 · Saxon-HE via IKVMnamespaces ✓MPL-2.0933.3121040.6241036.0451099.3781121.0451178.2591347.42348.404NuGet ↗
22SaxonJ-HE 12.10Java 21 · Saxonnamespaces ✓MPL-2.01108.9231282.1921273.7631380.9641414.3441486.7131576.16175.003Download ↗
23BaseX 12.4Java 21 · BaseX + JDK SAXnamespaces ✓BSD-31221.0311462.7791452.1111565.4921607.3581706.8771858.79481.154Download ↗

Измерено отдельно

Без места в рейтинге

AngleSharp нарушил strict-core контракт; DOM-режим xmllint дублирует тот же parser core.

СтатусВариантMinMeanMedianp90p95p99p99.9SDПричина
исключёнAngleSharp.Xml NativeAOT71.15886.37184.79096.724101.543113.141133.8548.474Принял запрещённый U+0001
исключёнAngleSharp.Xml JIT261.431303.727299.987330.977342.988376.891431.94122.445Принял запрещённый U+0001
controlxmllint DOM18.31322.59821.79926.80728.23731.52935.8202.955Тот же libxml2, что у ranked stream

Цена запуска процесса

Native действительно меняет картину

Каждый hyperfine run запускает новый CLI-процесс. Это cold CLI latency, а не throughput долгоживущего сервиса.

7.61×

TurboXml

66.551 ms JIT → 8.748 ms NativeAOT

6.63×

XmlReader

66.994 ms JIT → 10.112 ms NativeAOT

7.64×

JAXB

737.767 ms JVM → 96.515 ms GraalVM

До измерения скорости

Correctness-контракт

Быстрый parser без надёжного ненулевого exit code не является CI gate.

01

Strict core

Два корректных XML должны завершиться с кодом 0. Восемь malformed fixtures — с ненулевым.

  • tag mismatch
  • unknown entity
  • два корня
  • усечённый документ
  • duplicate attribute
  • текст вне root
  • пустой input
  • запрещённый U+0001
02

Namespaces

Отдельно проверены unbound prefix, duplicate expanded name и запрещённая привязка префикса xml.

Полный контракт прошли 11 строк:

XmlReader AOT/JIT · xmlwf -n · RXP -Nxs · Xerces-C namespaces · PHP sclable · JAXB Native/JVM · Saxon .NET/J · BaseX

03

DTD — policy, не malformed

Legal internal DTD проверялся отдельно. XmlReader, TurboXml, PHP mickaelandrieu, xmltodict и xq его намеренно отвергли.

Это не доказывает безопасность external entities или entity amplification — эти риски требуют отдельной конфигурации parser-а.

Воспроизводимость

Методология замеров

Один вход, один образ, один CPU и одинаковое число наблюдений для каждого варианта.

  1. 01

    Вход

    XML fixture, 2,319,739 bytes. Содержимое, внутреннее имя и hash публично не раскрываются.

  2. 02

    Серия

    hyperfine: 10 warmup, 1000 запусков в прямом и 1000 в строго обратном порядке. Итого N=2000 на команду.

  3. 03

    Изоляция

    Docker Desktop x86_64, AMD Ryzen 7 5700X3D, cpuset=0, сеть отключена. Benchmark был главным процессом контейнера; in-container polling отсутствовал.

  4. 04

    Агрегация

    Forward и reverse raw times объединены. Все выбросы сохранены. Места отсортированы по arithmetic mean.

Median

Центральное наблюдение pooled-серии. Устойчивее mean к единичным длинным паузам.

p90 / p95 / p99 / p99.9

Hyndman–Fan type 7: h = (n − 1) × p, линейная интерполяция между соседними значениями.

Sample SD

Выборочное стандартное отклонение с делителем n − 1 по всем 2000 raw times.

Технические детали воспроизведения
  • Использован один и тот же custom benchmark image; точный digest сохранён с локальными артефактами.
  • --shell=none для всех команд, кроме stdin-only wrapper-а xmltodict.
  • CLI/runtime startup входит во время; Docker startup и build time не входят.
  • Предыдущая частичная серия полностью отброшена после обнаружения in-container polling.
  • Raw JSON, correctness TSV и скрипты опубликованы ниже без пересчёта и фильтрации.

Проверить самостоятельно

Данные и скрипты

Числа на странице автоматически сверяются с combined JSON перед публикацией.