TurboXml NativeAOT
8.748 ms mean
Самый быстрый вариант, если namespace semantics не входят в контракт.
- Median
- 8.223
- p99
- 14.495
- p99.9
- 18.560
XML CLI benchmark · 4 августа 2026
34 реализации на одном файле размером 2,32 MB. По 2000 запусков, строгая проверка malformed XML и отдельный namespace-контракт.
Практический выбор
Mean определяет место. p99 и p99.9 показывают хвост, который среднее скрывает.
8.748 ms mean
Самый быстрый вариант, если namespace semantics не входят в контракт.
10.112 ms mean
Лучший wrapper для полного namespace-контракта. В измеренной конфигурации DTD запрещены.
13.426 ms mean
Рекомендация по умолчанию: open-source CLI без собственного кода, с namespaces и legal DTD.
29 strict-core вариантов
Все значения — миллисекунды. SD — выборочное стандартное отклонение.
Таблица прокручивается по горизонтали →
| № | Парсер / CLI | Deployment и core | Контракт | Лицензия | Min | Mean | Median | p90 | p95 | p99 | p99.9 | SD | Загрузка |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | TurboXml 2.1.0 | C# · .NET 10 NativeAOT · streaming SAX | strict core | BSD-2 | 6.960 | 8.748 | 8.223 | 10.916 | 11.891 | 14.495 | 18.560 | 1.603 | NuGet ↗ |
| 2 | System.Xml.XmlReader | C# · .NET 10 NativeAOT · streaming pull | namespaces ✓ | MIT | 7.863 | 10.112 | 9.612 | 12.475 | 13.536 | 15.903 | 20.562 | 1.793 | .NET ↗ |
| 3 | Expat 2.8.2 · xmlwf -n | C · Expat · streaming | namespaces ✓ | MIT | 10.571 | 13.426 | 12.846 | 15.899 | 17.231 | 20.759 | 24.012 | 2.022 | Releases ↗ |
| 4 | Expat 2.8.2 · xmlwf | C · Expat · streaming | strict core | MIT | 10.722 | 13.743 | 13.091 | 16.722 | 17.960 | 21.560 | 24.577 | 2.192 | Releases ↗ |
| 5 | RXP 1.5.0 · -xs | C · RXP | strict core | GPL-2.0 | 11.991 | 15.542 | 14.691 | 18.994 | 21.206 | 27.656 | 37.236 | 3.116 | Upstream ↗ |
| 6 | Woodstox 7.2.1 | GraalVM CE 25.2.4 · native · streaming StAX | namespaces ✓ | Apache-2.0 | 12.826 | 16.920 | 16.066 | 20.777 | 22.405 | 28.301 | 35.464 | 3.022 | Releases ↗ |
| 7 | RXP 1.5.0 · -Nxs | C · RXP | namespaces ✓ | GPL-2.0 | 12.232 | 17.022 | 15.934 | 21.786 | 24.438 | 31.346 | 51.457 | 4.001 | Upstream ↗ |
| 8 | libxml2 2.9.14 · --stream | C · libxml2 | strict core | MIT | 13.720 | 17.231 | 16.612 | 20.570 | 21.954 | 25.021 | 29.348 | 2.438 | Releases ↗ |
| 9 | XMLStarlet 1.6.1 | C · libxml2 | strict core | MIT | 13.876 | 18.087 | 17.318 | 22.042 | 23.973 | 28.353 | 34.904 | 3.019 | Download ↗ |
| 10 | JDK StAX | GraalVM CE 25.2.4 · native · streaming cursor | namespaces ✓ | GPL-2 + CE | 16.032 | 20.200 | 19.586 | 23.996 | 25.578 | 29.386 | 35.514 | 2.813 | GraalVM ↗ |
| 11 | JDK SAX | GraalVM CE 25.2.4 · native · streaming events | namespaces ✓ | GPL-2 + CE | 16.229 | 21.193 | 20.368 | 25.637 | 27.907 | 32.413 | 38.119 | 3.381 | GraalVM ↗ |
| 12 | Xerces-C 3.2.4 · -n | C++ · SAX2 · namespaces disabled | strict core | Apache-2.0 | 17.466 | 22.520 | 21.757 | 26.732 | 28.874 | 33.518 | 37.284 | 3.158 | Download ↗ |
| 13 | Xerces-C 3.2.4 | C++ · SAX2 · namespace processing | namespaces ✓ | Apache-2.0 | 18.226 | 23.605 | 22.854 | 28.055 | 29.903 | 34.526 | 38.233 | 3.298 | Download ↗ |
| 14 | sclable/xml-lint 0.9.0 | PHP 8.4 · DOM · libxml2 | namespaces ✓ | MIT | 45.865 | 56.706 | 55.509 | 64.651 | 68.096 | 75.698 | 95.496 | 6.342 | Packagist ↗ |
| 15 | TurboXml 2.1.0 | C# · .NET 10 JIT · streaming SAX | strict core | BSD-2 | 50.200 | 66.551 | 64.603 | 76.367 | 81.719 | 92.017 | 106.227 | 8.038 | NuGet ↗ |
| 16 | System.Xml.XmlReader | C# · .NET 10 JIT · streaming pull | namespaces ✓ | MIT | 51.423 | 66.994 | 65.989 | 75.981 | 79.950 | 85.823 | 115.129 | 7.209 | .NET ↗ |
| 17 | mickaelandrieu/xml-linter 2.0.0 | PHP 8.4 · DOM · libxml2 | strict core | MIT | 55.687 | 68.074 | 66.906 | 76.888 | 80.289 | 88.451 | 104.954 | 6.728 | Packagist ↗ |
| 18 | anyxml-cli 0.5.0 | Rust · anyxml 0.13.0 | strict core | MIT | 74.446 | 89.698 | 88.457 | 99.902 | 103.399 | 112.467 | 134.332 | 8.012 | crates.io ↗ |
| 19 | JAXB RI 4.0.9 Native | GraalVM CE 25.2.4 · JDK SAX + JAXB | namespaces ✓ | EDL/BSD-3 | 79.011 | 96.515 | 94.793 | 107.941 | 112.867 | 127.174 | 155.062 | 9.646 | GraalVM ↗ |
| 20 | xmltodict 1.0.4 | Python 3.13 · pyexpat | strict core | MIT | 115.697 | 138.724 | 135.937 | 153.442 | 161.614 | 183.983 | 223.886 | 13.051 | PyPI ↗ |
| 21 | JDK StAX | OpenJDK 21.0.11 · JIT · streaming cursor | namespaces ✓ | GPL-2 + CE | 188.967 | 233.022 | 231.486 | 254.221 | 261.310 | 282.070 | 345.828 | 17.905 | OpenJDK ↗ |
| 22 | JDK SAX | OpenJDK 21.0.11 · JIT · streaming events | namespaces ✓ | GPL-2 + CE | 198.326 | 251.774 | 249.531 | 274.967 | 284.015 | 316.897 | 371.026 | 20.087 | OpenJDK ↗ |
| 23 | kislyuk/yq 4.1.2 · xq | Python · xmltodict + jq | strict core | Apache-2.0 | 227.569 | 263.784 | 261.367 | 286.390 | 295.802 | 323.214 | 341.298 | 18.012 | PyPI ↗ |
| 24 | Woodstox 7.2.1 | OpenJDK 21.0.11 · JIT · streaming StAX | namespaces ✓ | Apache-2.0 | 230.319 | 276.358 | 274.489 | 300.570 | 307.765 | 326.732 | 372.519 | 18.906 | Releases ↗ |
| 25 | XML::Twig 3.52 | Perl · XML::Parser + Expat | strict core | Artistic/GPL | 473.756 | 555.101 | 548.583 | 606.071 | 634.026 | 696.985 | 772.459 | 42.246 | MetaCPAN ↗ |
| 26 | JAXB RI 4.0.9 JVM | Java 21 · JDK SAX + JAXB | namespaces ✓ | EDL/BSD-3 | 644.205 | 737.767 | 732.928 | 792.378 | 818.895 | 892.022 | 985.512 | 46.131 | Maven ↗ |
| 27 | SaxonHE12NetXQuery 12.9.10 | .NET 10 · Saxon-HE via IKVM | namespaces ✓ | MPL-2.0 | 933.312 | 1040.624 | 1036.045 | 1099.378 | 1121.045 | 1178.259 | 1347.423 | 48.404 | NuGet ↗ |
| 28 | SaxonJ-HE 12.10 | Java 21 · Saxon | namespaces ✓ | MPL-2.0 | 1108.923 | 1282.192 | 1273.763 | 1380.964 | 1414.344 | 1486.713 | 1576.161 | 75.003 | Download ↗ |
| 29 | BaseX 12.4 | Java 21 · BaseX + JDK SAX | namespaces ✓ | BSD-3 | 1221.031 | 1462.779 | 1452.111 | 1565.492 | 1607.358 | 1706.877 | 1858.794 | 81.154 | Download ↗ |
Измерено отдельно
AngleSharp и Aalto нарушили correctness-контракт; DOM-режим xmllint дублирует тот же parser core.
| Статус | Вариант | Min | Mean | Median | p90 | p95 | p99 | p99.9 | SD | Причина |
|---|---|---|---|---|---|---|---|---|---|---|
| исключён | AngleSharp.Xml NativeAOT | 71.158 | 86.371 | 84.790 | 96.724 | 101.543 | 113.141 | 133.854 | 8.474 | Принял запрещённый U+0001 |
| исключён | AngleSharp.Xml JIT | 261.431 | 303.727 | 299.987 | 330.977 | 342.988 | 376.891 | 431.941 | 22.445 | Принял запрещённый U+0001 |
| исключён | Aalto 1.4.0 Native | 10.570 | 13.690 | 13.014 | 16.710 | 18.015 | 21.380 | 25.152 | 2.240 | Принял overlong UTF-8 и >U+10FFFF |
| исключён | Aalto 1.4.0 JIT | 160.081 | 198.261 | 196.423 | 218.598 | 225.125 | 243.663 | 264.065 | 15.361 | Принял overlong UTF-8 и >U+10FFFF |
| control | xmllint DOM | 18.313 | 22.598 | 21.799 | 26.807 | 28.237 | 31.529 | 35.820 | 2.955 | Тот же libxml2, что у ranked stream |
Цена запуска процесса
Каждый hyperfine run запускает новый CLI-процесс. Это cold CLI latency, а не throughput долгоживущего сервиса.
66.551 ms JIT → 8.748 ms NativeAOT
66.994 ms JIT → 10.112 ms NativeAOT
737.767 ms JVM → 96.515 ms GraalVM
233.022 ms JIT → 20.200 ms GraalVM
251.774 ms JIT → 21.193 ms GraalVM
276.358 ms JIT → 16.920 ms GraalVM
До измерения скорости
Быстрый parser без надёжного ненулевого exit code не является CI gate.
Два корректных XML должны завершиться с кодом 0. Восемь malformed fixtures — с ненулевым.
Для Java addendum добавлены external DTD, overlong UTF-8 и code point выше U+10FFFF. Aalto принял два последних и исключён.
Отдельно проверены unbound prefix, duplicate expanded name и запрещённая привязка префикса xml.
Полный контракт прошли 17 ranked-строк:
XmlReader AOT/JIT · xmlwf -n · RXP -Nxs · Xerces-C namespaces · PHP sclable · JDK SAX/StAX AOT/JIT · Woodstox AOT/JIT · JAXB Native/JVM · Saxon .NET/J · BaseX
Legal internal DTD проверялся отдельно. Java wrappers addendum работают в DTD-prohibited режиме: DTD и external entities отключены, resolver запрещает external access.
Это policy для CI gate, не DTD validation. XmlReader, TurboXml, PHP mickaelandrieu, xmltodict и xq также намеренно отвергли legal DTD.
Воспроизводимость
Один вход, один CPU и одинаковые hyperfine-настройки; baseline и Java addendum измерены последовательно.
XML fixture, 2,319,739 bytes. Перед каждой серией он копировался внутрь Linux filesystem контейнера и читался оттуда. Содержимое, внутреннее имя и hash публично не раскрываются.
Неизменённый baseline из 26 строк и отдельный Java addendum из 8. Старые команды повторно не запускались.
hyperfine: 10 warmup, 1000 запусков forward и 1000 в строго обратном порядке. N=2000 на команду.
Docker Desktop x86_64, AMD Ryzen 7 5700X3D, cpuset=0, сеть отключена; benchmark — главный процесс контейнера.
Центральное наблюдение pooled-серии. Устойчивее mean к единичным длинным паузам.
Hyndman–Fan type 7: h = (n − 1) × p, линейная интерполяция между соседними значениями.
Выборочное стандартное отклонение с делителем n − 1 по всем 2000 raw times.
BASE_IMAGE; локальное имя image намеренно не раскрывается.--shell=none для всех команд, кроме stdin-only wrapper-а xmltodict.Проверить самостоятельно
Числа на странице автоматически сверяются с combined JSON перед публикацией.