벤치마크
StAX-XML 벤치마크는 순수 JavaScript 파서를 기준으로 합니다. latest 문서와
v1.0.0-rc3 릴리스 결과는 같은 측정 기준을 사용합니다. 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:expanded: 릴리스용 runtime matrix, 4 GiBStreamReaderSyncindex-first benchmark, converter compiled batch-plan benchmark, 1 GiB writer benchmark 결과 파일을 다시 생성.
측정 기준
섹션 제목: “측정 기준”Parser benchmark는 JavaScript 문자열과 객체를 반환하는 경로를 측정합니다. raw byte만 훑는 작업은 완료된 parse로 보지 않습니다. 대용량 XML 측정은 낮은 RSS, JavaScript 문자열 크기 제한 회피, stream/event/converter API의 pull 방식 소비를 기준으로 봅니다.
릴리스 결과
섹션 제목: “릴리스 결과”현재 릴리스 결과는 Node 24.15.0, Bun 1.3.13, Deno 2.7.13에서 다시 측정했습니다. Runtime 행은 생성된 16 MiB fixture, warmup 1회, 측정 3회를 기준으로 합니다.
| Runtime | Workload | Throughput | Average | Heap delta |
|---|---|---|---|---|
| Node 24.15.0 | public-sync-full-string | 96.1 MiB/s | 166.50 ms | 24.6 MiB |
| Node 24.15.0 | stream-sync-index-full-string | 105.5 MiB/s | 151.66 ms | 23.3 MiB |
| Bun 1.3.13 | public-sync-full-string | 69.2 MiB/s | 231.12 ms | 16.8 MiB |
| Bun 1.3.13 | stream-sync-index-full-string | 138.7 MiB/s | 115.36 ms | 67.2 MiB |
| Deno 2.7.13 | public-sync-full-string | 93.3 MiB/s | 171.51 ms | 27.5 MiB |
| Deno 2.7.13 | stream-sync-index-full-string | 111.2 MiB/s | 143.94 ms | 27.8 MiB |
4 GiB stream fixture는 Iterable<Uint8Array[]> 위의 StreamReaderSync를
사용합니다. 각 batch는 eventCount와 index accessor로 소비하고, warmup 없이
3회 측정합니다.
| Workload | Average | Throughput | Events | Average RSS delta |
|---|---|---|---|---|
StreamReaderSync index-first, 4 GiB | 43.44 s | 94.29 MiB/s | 572,662,314 | 4.0 MiB |
기타 벤치마크 항목
섹션 제목: “기타 벤치마크 항목”릴리스 matrix는 전체 benchmark suite의 일부입니다. Parser, stream reader, converter, builder, writer, async writer, large writer sink benchmark는 계속 별도로 측정합니다.
| 항목 | 명령 | 측정 내용 |
|---|---|---|
| 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 compiled batch plan | pnpm --dir packages/benchmark bench:converter:compiled-batch | 수동 StreamReaderSync 변환과 converter의 자동/명시적 .compile() batch dispatch plan 비교. |
| 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가 계속 권장되는 동기 경로입니다. 1 GiB writer 결과는
packages/benchmark/results/release/raw/writer-1gb.json에 보존합니다.
Converter API 비교
섹션 제목: “Converter API 비교”Converter 비교는 순수 JavaScript 릴리스 측정에 포함됩니다. Native reader를
호출해 값을 받아오는 경로는 사용하지 않습니다. 수동 기준값은 StreamReaderSync
batch를 eventCount와 index accessor로 소비해 만듭니다. Converter 행은 같은 byte fixture를
다음 경로로 파싱합니다.
schema.parseSync(bytes): 지원 가능한 schema를 compiled dispatch batch plan으로 자동 변환합니다.schema.compile().parseSync(bytes): 같은 compiled batch plan을 명시적으로 만들고 재사용합니다.
두 converter 행은 측정 전에 수동 기준값과 동일한 결과를 만드는지 검증합니다.
결과 파일은
packages/benchmark/results/release/converter-compiled-batch-plan.json입니다.
| Case | Throughput | Average | Books | Checksum |
|---|---|---|---|---|
수동 StreamReaderSync 변환 | 80.17 MiB/s | 199.58 ms | 139,458 | -1845341048 |
Converter schema.parseSync(bytes) | 47.11 MiB/s | 339.63 ms | 139,458 | -1845341048 |
Converter schema.compile().parseSync(bytes) | 46.03 MiB/s | 347.61 ms | 139,458 | -1845341048 |
StreamReaderSync 소비 방식
섹션 제목: “StreamReaderSync 소비 방식”대용량 byte input에서는 batch 안에서 index로 접근하는 방식을 권장합니다.
import { StreamEventType, StreamReaderSync } from 'stax-xml';
const reader = new StreamReaderSync(byteBatches);
for (const batch of reader) { for (let index = 0; index < batch.eventCount; index++) { if (batch.typeAt(index) === StreamEventType.START_ELEMENT) { const name = batch.nameAt(index); const attributeCount = batch.attributeCountAt(index);
for (let attribute = 0; attribute < attributeCount; attribute++) { const attributeName = batch.attributeNameAt(index, attribute); const attributeValue = batch.attributeValueAt(index, 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였습니다.