콘텐츠로 이동
StAX-XML

벤치마크

StAX-XML 벤치마크는 순수 JavaScript 파서를 기준으로 합니다. latest 문서와 v1.0.0 릴리스 결과는 같은 측정 기준을 사용합니다. JavaScript에서 실제 호출하는 공개 API가 문자열과 객체를 만드는 비용까지 포함해 측정합니다.

  • pnpm --dir packages/benchmark bench:sync: 동기 parser와 builder 경로.
  • pnpm --dir packages/benchmark bench:async: 비동기 parser와 writer 경로.
  • pnpm --dir packages/benchmark bench:runtime-matrix: Node, Bun, Deno가 설치되어 있을 때 같은 JavaScript reader 작업을 런타임별로 비교.
  • pnpm --filter benchmark run release:writer:cross-runtime: Node/stax-xml, Java/Woodstox, Rust/quick-xml의 실제 파일 writer 작업을 비교.
  • pnpm --filter benchmark run release:reader:cross-runtime: Node/stax-xml, Java/Woodstox, Rust/quick-xml의 동일 pull-reader 작업을 비교.
  • pnpm --filter benchmark run release:expanded: 전체 release benchmark set을 다시 생성합니다. Runtime matrix, 4 GiB StreamReaderSync, converter batch-plan, 1 GiB writer, parser fixture series, npm XML parser 비교, 1 MiB부터 4 GiB까지의 size series, writer small/big/async 행이 포함됩니다.

Parser benchmark는 라이브러리 기본값인 fragment mode의 공개 API로 JavaScript 문자열과 객체를 반환하는 경로를 측정합니다. raw byte만 훑는 작업은 완료된 parse로 보지 않습니다. 대용량 XML은 heap과 RSS를 별도로 기록하고, JavaScript 문자열 크기 제한을 피하면서 stream/event/converter API의 pull 방식 소비를 측정합니다.

생성된 환경과 runtime 버전은 아래에 표시됩니다. Runtime 행은 생성된 16 MiB fixture, warmup 1회, 측정 3회를 기준으로 합니다.

  • Generated: 2026-07-18T17:16:41.036Z
  • Run ID: 2026-07-18T17-16-41-036Z
  • CPU: Apple M4 (~2.40 GHz)
  • 런타임: node 26.5.0 (arm64-darwin) --expose-gc
  • 패키지 매니저: pnpm@11.12.0
  • Canonical Set: parser 2 KiB / 4 KiB / 13 MiB / 98 MiB, maintained Node XML parser comparison, StreamReaderSync 1 MiB to 4 GiB size series, runtime matrix, converter compiled batch plan, writer small / big / async / 1 GiB
  • 소스: release aggregation
해결된 패키지와 런타임 버전
Benchmark packageResolved versionDeclared rangeSource
fast-xml-parser5.7.2^5.2.5npm
htmlparser212.0.0^12.0.0npm
mitata1.0.34^1.0.34npm
sax1.6.0^1.4.1npm
saxes6.0.0^6.0.0npm
saxophone0.8.0^0.8.0npm
stax-xml1.0.0workspace:*workspace
txml5.2.1^5.1.1npm
xml-stream0.4.5^0.4.5npm
xml2js0.6.2^0.6.2npm

아래 행은 같은 fixture에서 동일한 최종 JavaScript object shape를 만드는 공개 StAX-XML surface들을 비교합니다. 별도의 npm parser 비교나 대용량 traversal 표와 섞어서 보지 말고 fixture 크기 순서대로 읽는 것이 기준입니다.

벤치마크 소스: parser-2kb.mjs, release aggregation

Surface평균 시간초당 작업 수Heap비고
EventReaderSync176.77 us5,657 ops/sec64.9 KiBString event iterator building the same object shape manually
StreamReaderSync161.26 us6,201 ops/sec32.6 KiBCurrent-token reader avoiding event object allocation
Converter195.94 us5,104 ops/sec45.0 KiBDeclarative schema returning the same object shape
fast-xml-parser XMLParser256.96 us3,892 ops/sec201.4 KiBObject parser plus canonical projection
txml parse7.73 us129,348 ops/sec3.9 KiBObject parser plus canonical projection
xml2js parseString322.96 us3,096 ops/sec198.2 KiBCallback object parser plus canonical projection

벤치마크 소스: parser-4kb.mjs, release aggregation

Surface평균 시간초당 작업 수Heap비고
EventReaderSync284.06 us3,520 ops/sec132.1 KiBString event iterator building the same object shape manually
StreamReaderSync260.87 us3,833 ops/sec69.6 KiBCurrent-token reader avoiding event object allocation
Converter299.33 us3,341 ops/sec125.9 KiBDeclarative schema returning the same object shape
fast-xml-parser XMLParser382.64 us2,613 ops/sec691.1 KiBObject parser plus canonical projection
txml parse17.57 us56,914 ops/sec2.9 KiBObject parser plus canonical projection
xml2js parseString487.47 us2,051 ops/sec578.1 KiBCallback object parser plus canonical projection

벤치마크 소스: parser-13mb.mjs, release aggregation

Surface평균 시간초당 작업 수Heap비고
EventReaderSync122.59 ms8.16 ops/sec19.1 MiBString event iterator building the same object shape manually
StreamReaderSync105.05 ms9.52 ops/sec17.8 MiBCurrent-token reader avoiding event object allocation
Converter104.16 ms9.6 ops/sec12.0 MiBDeclarative schema returning the same object shape
fast-xml-parser XMLParser504.04 ms1.98 ops/sec134.6 MiBObject parser plus canonical projection
txml parse98.82 ms10.12 ops/sec126.9 MiBObject parser plus canonical projection
xml2js parseString468.81 ms2.13 ops/sec94.9 MiBCallback object parser plus canonical projection

벤치마크 소스: parser-98mb.mjs, release aggregation

Surface평균 시간초당 작업 수Heap비고
EventReaderSync883.15 ms1.13 ops/sec38.0 MiBString event iterator building the same object shape manually
StreamReaderSync709.20 ms1.41 ops/sec40.5 MiBCurrent-token reader avoiding event object allocation
Converter664.24 ms1.51 ops/sec32.7 MiBDeclarative schema returning the same object shape
fast-xml-parser XMLParser3.72 s0.27 ops/sec951.3 MiBObject parser plus canonical projection
txml parse745.66 ms1.34 ops/sec873.8 MiBObject parser plus canonical projection
xml2js parseString3.49 s0.29 ops/sec645.6 MiBCallback object parser plus canonical projection

이 표는 현재 benchmark workspace에 설치된 공개 npm XML parser package를 같은 books.xml fixture로 비교합니다. Object parser는 각자의 string-to-object API를 사용하고, event parser는 event name, text, attribute를 checksum으로 접어 parse 작업이 제거되지 않도록 합니다.

벤치마크 소스: release aggregation, books fixture

라이브러리평균 시간초당 작업 수Heap비고
stax-xml EventReaderSync299.24 us3,342 ops/sec158.0 KiBPublic string event reader with checksum folding
stax-xml StreamReaderSync278.47 us3,591 ops/sec101.9 KiBPublic current-token reader with accessor-based checksum folding
fast-xml-parser XMLParser390.05 us2,564 ops/sec768.8 KiBObject parser
txml parse70.92 us14,100 ops/sec82.7 KiBLightweight object parser
xml2js parseString505.31 us1,979 ops/sec603.1 KiBCallback object parser
sax strict event parser407.04 us2,457 ops/sec387.0 KiBStrict SAX event parser
saxes event parser268.13 us3,730 ops/sec92.4 KiBMaintained SAX-style parser
htmlparser2 xmlMode parser328.90 us3,040 ops/sec286.4 KiBHTML/XML event parser in xmlMode

각 측정은 동일한 UTF-8 파일을 읽고 parsing하는 전체 구간을 포함합니다. 각 공개 pull reader가 element 이름, 공백이 아닌 text, attribute 이름과 값을 모두 materialize하며, event 수와 checksum이 일치해야 결과를 기록합니다.

벤치마크 소스: cross-language reader

ReaderMedian throughputMedian time이벤트 수Checksum
stax-xml StreamReaderSync (v26.5.0)91.6 MiB/s174.75 ms967,96536104832
Woodstox 6.7.0 (Java 25.0.2)333.4 MiB/s47.99 ms967,96536104832
quick-xml 0.40.1 (Rust 1.95.0)570.1 MiB/s28.07 ms967,96536104832

대용량 input series는 생성된 byte batch와 공개 StreamReaderSync index API를 사용합니다. byte offset 기반 reader가 전체 XML을 하나의 JavaScript string으로 만들지 않고도 문자열 크기 제한을 넘어설 수 있다는 release evidence입니다.

벤치마크 소스: release aggregation, 4 GiB harness

크기처리량평균Heap deltaRSS delta이벤트 수Checksum
(1MiB generated chunks)48.89 MiB/s20.45 ms4.1 MiB464.0 KiB139,8181349859144
(10MiB generated chunks)74.35 MiB/s134.50 ms4.3 MiB528.0 KiB1,398,1064191398664
(100MiB generated chunks)77.50 MiB/s1.29 s7.4 MiB33.2 MiB13,981,0182933107464
(1GiB generated chunks)77.84 MiB/s13.16 s27.8 MiB67.2 MiB143,165,5861039532941
(4GiB generated chunks)84.97 MiB/s48.21 s27.8 MiB78.1 MiB572,662,314659690454

1 GiB reader 행과 writer 행을 나란히 보면 이상해 보일 수 있습니다. 두 행은 의도적으로 다른 작업을 측정합니다. 하드코딩된 처리량 수치 대신 생성된 표를 기준으로 비교하세요.

Writer는 대부분 deterministic append 작업입니다. 호출자는 element name, attribute, text value를 이미 알고 있습니다. 그래서 WriterSyncSink는 자체 writer state를 검증하고, 이미 존재하는 JavaScript string을 encoding한 뒤, 큰 sequential chunk를 sink나 file descriptor에 flush합니다. 임의의 XML에서 delimiter를 찾거나, chunk boundary를 넘어 token을 복구하거나, incoming byte에서 name/text/attribute를 새로 발견하지 않습니다. 그래서 sync file writer는 다른 runtime의 native writer와 같은 넓은 처리량 구간에 들어갈 수 있습니다.

StreamReaderSync는 CPU-bound parsing입니다. 현재 1 GiB 행은 file I/O가 아니라 generated byte batch를 사용하므로 storage speed가 병목이 아닙니다. Reader는 모든 byte를 scan하고, markup과 text를 분류하고, XML state를 유지하고, event batch를 만들고, 안정적인 accessor API를 유지하면서, consumer가 요청할 때 name/text/ attribute를 JavaScript string으로 decode/materialize해야 합니다. 핵심 제한은 안정적인 StAX API 아래에서 수행되는 pure-JavaScript byte scanning과 UTF-8 span decode/string materialization입니다. Woodstox나 quick-xml 같은 native parser는 delimiter search와 tokenization을 JVM/Rust 코드와 더 낮은 수준의 buffer access에 둘 수 있습니다.

Native addon이나 FFI-style 실험도 이 public contract 경계를 제거하지는 못했습니다. Rust, C, C++ tokenizer는 delimiter search 비용을 줄일 수 있고, 더 낮은 수준의 boundary는 pointer, buffer, span을 더 직접적으로 노출할 수 있습니다. 하지만 일반 JavaScript consumer에게 JavaScript string을 포함한 zero-copy StAX event stream을 완성된 형태로 건넬 수는 없습니다. Benchmark contract가 JavaScript string과 event를 요구하는 순간, V8 heap object를 만들거나 copy해야 하며, 이 materialization 비용이 tokenizer 구현 언어나 boundary 선택보다 더 지배적입니다.

이것이 순수 JavaScript release의 핵심 tradeoff입니다. Bounded memory와 portable byte-offset parsing을 우선하고, parser 처리량은 JavaScript tokenizer와 string materialization 비용의 제한을 받습니다.

RuntimeWorkloadThroughputAveragePeak heapPeak RSS
Node 26.5.0stream-sync-full61.8 MiB/s258.74 ms21.2 MiB155.7 MiB
Node 26.5.0event-sync-full53.4 MiB/s299.57 ms11.8 MiB156.0 MiB
Bun 1.3.14stream-sync-full99.0 MiB/s161.56 ms33.1 MiB223.8 MiB
Bun 1.3.14event-sync-full100.6 MiB/s159.05 ms17.0 MiB264.0 MiB
Deno 2.9.3 (v8 14.9.207.2-rusty)stream-sync-full62.5 MiB/s255.87 ms29.0 MiB138.4 MiB
Deno 2.9.3 (v8 14.9.207.2-rusty)event-sync-full57.5 MiB/s278.38 ms34.3 MiB138.7 MiB

과거 4 GiB generated stream fixture는 Iterable<Uint8Array[]> 위의 StreamReaderSync를 fragment mode로 사용하고 current-token accessor로 소비합니다.

WorkloadAverageThroughputEventsHeap deltaRSS delta
StreamReaderSync current-token, 4 GiB48.21 s84.97 MiB/s572,662,31427.8 MiB78.1 MiB

릴리스 matrix는 전체 benchmark suite의 일부입니다. Parser, stream reader, converter, builder, writer, async writer, large writer sink benchmark는 release-facing 측정 surface이며 선택적인 부록이 아닙니다.

항목 명령 측정 내용
Parser fixture series pnpm --dir packages/benchmark bench:sync 2 KiB, 4 KiB, 13 MiB, 98 MiB parser fixture와 sync builder 결과.
Builder small/large pnpm --dir packages/benchmark bench:builder:small, bench:builder:big Builder에 맞춘 중간 데이터에서 XML을 생성하는 비용.
Converter schema pnpm --dir packages/benchmark bench:converter:compiled-batch Manual StreamReaderSync projection과 public schema.parseSync(bytes) 경로 비교.
Async parser/writer pnpm --dir packages/benchmark bench:async Async parser와 async-vs-sync writer API overhead.
Writer core pnpm --dir packages/benchmark bench:writer:core Compact/pretty output에 대한 direct writer method throughput.
1 GiB writer pnpm --dir packages/benchmark bench:writer:1gb:json Memory/temp-file target 위의 async writer와 sync sink writer.

대용량 XML 출력에서 전체 XML 문자열을 보관하지 않고 조금씩 쓰려면 WriterSyncSink가 계속 권장되는 동기 경로입니다. 생성된 release data에는 1 GiB writer 결과도 포함됩니다.

Converter 비교는 같은 byte fixture를 다음 경로로 파싱합니다.

  • schema.parseSync(bytes): 지원되는 streaming dispatch plan을 자동 compile하고 cache합니다.

두 converter 행은 측정 전에 수동 기준값과 동일한 결과를 만드는지 검증합니다.

벤치마크 소스: converter-compiled-batch-plan.mjs, release aggregation

항목처리량평균Heap deltaRSS deltaBooksChecksum
Manual StreamReaderSync projection102.43 MiB/s156.21 ms57.8 MiB3.8 MiB139,458-1845341048
Converter schema.parseSync(bytes)82.49 MiB/s193.96 ms61.0 MiB3.5 MiB139,458-1845341048

Writer benchmark는 builder-friendly input normalization을 timed region 밖에 둡니다. 따라서 행들은 반복적인 JSON shape 변환이 아니라 XML emission throughput을 측정합니다.

벤치마크 소스: writer release aggregation

라이브러리평균 시간초당 작업 수Heap비고
fast-xml-parser builder112.06 us8,924 ops/sec80.9 KiBfast-xml-parser builder
stax-xml writer146.35 us6,833 ops/sec274.4 KiBWriter API
stax-xml writer sync8.05 us124,170 ops/sec1.9 KiBSync writer API
stax-xml writer sync sink9.91 us100,922 ops/sec3.5 KiBSync streaming sink API
xml2js builder170.51 us5,865 ops/sec126.0 KiBxml2js builder

벤치마크 소스: writer release aggregation

라이브러리평균 시간초당 작업 수Heap비고
fast-xml-parser builder (big.json)18.53 ms53.97 ops/sec25.4 MiBfast-xml-parser builder
stax-xml writer sync (big.json)13.89 ms72.01 ops/sec8.9 MiBSync writer API
stax-xml writer sync sink (big.json)10.44 ms95.79 ops/sec4.1 MiBSync streaming sink API
stax-xml writer (big.json)30.42 ms32.88 ops/sec8.5 MiBWriter API

벤치마크 소스: writer release aggregation

Element CountAsync WriterSync WriterSync Sink성능 비율
1K elements4.26 ms1.98 ms2.18 ms1.95x 빠름 (sink)
5K elements16.69 ms6.05 ms7.22 ms2.31x 빠름 (sink)
10K elements30.13 ms14.84 ms12.33 ms2.44x 빠름 (sink)

벤치마크 소스: writer-1gb.mjs, release aggregation

대상평균 시간처리량HeapPeak RSS기록량Records
Async writer + memory WritableStream12.64 s81.03 MiB/s59.5 MiB170.4 MiB1.00 GiB1,164,225
Sync sink writer + memory sink4.83 s212.17 MiB/s69.6 MiB179.0 MiB1.00 GiB1,164,225
Async writer + temp file12.91 s79.31 MiB/s35.5 MiB179.5 MiB1.00 GiB1,164,225
Sync sink writer + temp file5.35 s191.45 MiB/s68.6 MiB179.7 MiB1.00 GiB1,164,225

각 공개 writer API가 같은 compact XML을 생성해 실제 파일 sink에 기록합니다. Node 전용 1 GiB 측정과는 별도의 suite입니다.

동일한 generator로 12,798개 레코드를 compact XML로 만들고 실제 파일에 기록했습니다. 각 행은 3회 실행의 중앙값입니다.

Writer중앙 처리량
stax-xml WriterSyncSink (v26.5.0)187.1 MiB/s
Woodstox 6.7.0 (Java 25.0.2)125.6 MiB/s
quick-xml 0.40.1 (Rust 1.95.0)528.4 MiB/s

입력 크기, warm-up, sink와 fixture가 다른 1 GiB Node 전용 측정과 직접 숫자를 섞어 비교하지 않습니다.

대용량 byte input에서는 public current-token API를 사용합니다.

import { StreamReaderSync, XmlEventType } from 'stax-xml';
const reader = new StreamReaderSync(byteChunks);
while (reader.next() !== null) {
if (reader.eventType() === XmlEventType.START_ELEMENT) {
const name = reader.name();
const attributeCount = reader.attributeCount();
for (let attribute = 0; attribute < attributeCount; attribute++) {
const attributeName = reader.attributeName(attribute);
const attributeValue = reader.attributeValue(attribute);
consume(name, attributeName, attributeValue);
}
}
}

for (const event of batch)도 계속 지원하지만 wrapper event object를 할당합니다. 98 MiB 표준 fixture에서 index-first 소비는 730.56 ms, heap delta 약 39.8 MiB였고, wrapper iteration은 1.28 s, heap delta 약 482.7 MiB였습니다.