# Purifai v3 category benchmark

Purifai is benchmarked for its deliberately narrow category: bounded streaming conversion of untrusted HTML into readable plain text. This report does not claim that Purifai is the fastest HTML tool for every job.

Raw measurements: [benchmark/results/v3.json](../../benchmark/results/v3.json)

## Reproduce

```sh
pnpm run bench
pnpm run bench:check
```

- Run date: 2026-08-30T19:22:56.884Z
- Runtime: v24.16.0
- Platform: darwin arm64
- CPU: Apple M1 (8 logical cores)
- Memory: 8192.00 MiB
- Packages: purifai 3.0.2, striptags 3.2.0, html-to-text 10.0.0
- Corpus SHA-256: `fe131490fda5dfb290654d3ff7f6f126471362e6debe47e2f0aced0dbad7b4d5`

The throughput path uses already-materialized strings for every implementation. Each worker calibrates a batch to at least 100 ms, performs 10 warmup batches, then records 40 batches with `process.hrtime.bigint()`. Median and p95 are nearest-rank values per conversion. Each package/corpus pair runs in a fresh process.

The memory path is different by design: Purifai streaming receives lazily generated chunks of at most 16,384 code units, while one-shot tools receive the same logical document after materialization. Each mode runs alone under `node --expose-gc` in 7 fresh processes. The table gates the nearest-rank median of those process peak-RSS values and retains every raw sample in JSON. Purifai one-shot memory is reported separately and is not presented as streaming memory.

Artifact: 23,689 bytes for the complete minified ESM runtime at gzip level 9; 69,819 bytes for the npm tarball.

## Release gates

| Gate | Result | Evidence |
| --- | --- | --- |
| reviewed readability fixtures | PASS | Purifai 8/8; striptags 0/8 |
| non-reader body removal | PASS | Purifai 5/5; striptags script calibration leak observed |
| hostile-tags hostile p95 | PASS | Purifai 2.751 ms; html-to-text 3.130 ms |
| hostile-comments hostile p95 | PASS | Purifai 1.032 ms; html-to-text 1.614 ms |
| hostile-entities hostile p95 | PASS | Purifai 2.513 ms; html-to-text 3.226 ms |
| hostile-raw hostile p95 | PASS | Purifai 2.249 ms; html-to-text 2.465 ms |
| readable-large streaming RSS | PASS | Purifai 66.00 MiB; html-to-text 82.83 MiB |
| hostile-tags streaming RSS | PASS | Purifai 60.94 MiB; html-to-text 62.55 MiB |
| hostile-comments streaming RSS | PASS | Purifai 57.98 MiB; html-to-text 63.91 MiB |
| hostile-entities streaming RSS | PASS | Purifai 59.75 MiB; html-to-text 66.31 MiB |
| hostile-raw streaming RSS | PASS | Purifai 60.06 MiB; html-to-text 62.55 MiB |

## Throughput

| Corpus | Package | Median ms | p95 ms | Conversions/sample |
| --- | --- | ---: | ---: | ---: |
| readable-small | purifai | 0.019 | 0.019 | 16384–16384 |
| readable-small | html-to-text | 0.188 | 0.196 | 1024–1024 |
| readable-small | striptags | 0.009 | 0.009 | 32768–32768 |
| readable-large | purifai | 8.934 | 9.093 | 32–32 |
| readable-large | html-to-text | 13.807 | 15.767 | 16–16 |
| readable-large | striptags | 4.354 | 4.625 | 64–64 |
| hostile-tags | purifai | 2.714 | 2.751 | 128–128 |
| hostile-tags | html-to-text | 3.044 | 3.130 | 128–128 |
| hostile-tags | striptags | 2.361 | 2.822 | 128–128 |
| hostile-comments | purifai | 0.976 | 1.032 | 256–256 |
| hostile-comments | html-to-text | 1.539 | 1.614 | 256–256 |
| hostile-comments | striptags | 2.421 | 3.117 | 128–128 |
| hostile-entities | purifai | 2.363 | 2.513 | 128–128 |
| hostile-entities | html-to-text | 3.041 | 3.226 | 128–128 |
| hostile-entities | striptags | 2.655 | 2.878 | 128–128 |
| hostile-raw | purifai | 2.143 | 2.249 | 128–128 |
| hostile-raw | html-to-text | 2.327 | 2.465 | 128–128 |
| hostile-raw | striptags | 1.869 | 2.024 | 128–128 |

## Peak memory

| Corpus | Mode | Max RSS MiB | Retained heap MiB |
| --- | --- | ---: | ---: |
| readable-large | purifai-stream | 66.00 | 1.05 |
| readable-large | purifai-one-shot | 67.83 | 1.87 |
| readable-large | html-to-text | 82.83 | 2.46 |
| hostile-tags | purifai-stream | 60.94 | 0.93 |
| hostile-tags | purifai-one-shot | 60.72 | 0.88 |
| hostile-tags | html-to-text | 62.55 | 2.22 |
| hostile-comments | purifai-stream | 57.98 | 0.91 |
| hostile-comments | purifai-one-shot | 58.77 | 0.99 |
| hostile-comments | html-to-text | 63.91 | 2.24 |
| hostile-entities | purifai-stream | 59.75 | 0.96 |
| hostile-entities | purifai-one-shot | 61.13 | 1.99 |
| hostile-entities | html-to-text | 66.31 | 2.69 |
| hostile-raw | purifai-stream | 60.06 | 0.95 |
| hostile-raw | purifai-one-shot | 60.77 | 0.92 |
| hostile-raw | html-to-text | 62.55 | 2.22 |

## Interpretation and limits

- `striptags` is a minimal tag remover. Its inclusion calibrates readability and dropped-body behavior; it is not the hostile-throughput gate.
- `html-to-text` offers a broader formatting feature set. Purifai intentionally does less, which is why hostile one-shot latency and streaming memory are compared directly while advanced table fidelity is not claimed.
- Purifai preserves simple rows and cells, but it does not attempt rowspan/colspan layout, visual CSS reconstruction, or browser-equivalent error recovery.
- Results are machine-, runtime-, and corpus-specific. The checked claim is the set of release gates above, not an uncategorized “fastest” claim.
