MarkItDown
Measure MarkItDown performance without hiding extraction failures
Build a per-format conversion benchmark, account for buffering and optional services, and report accepted documents instead of raw conversion throughput.
What you will learn
- Report accepted documents and extraction quality together.
- Measure non-seekable buffering and worker concurrency explicitly.
- Separate external-service costs from local parsing.
Before you start
- Basic Python and command-line usage
- A non-sensitive document whose contents you can verify
Use the chapter checklist to explain and verify this part of a document ingestion workflow.
Key takeaways
- Report accepted documents and extraction quality together.
- Measure non-seekable buffering and worker concurrency explicitly.
- Separate external-service costs from local parsing.
Count documents that remain useful
A fast converter that drops the decisive table is not fast at your ingestion task. Build a corpus with expected facts and report both elapsed time and the proportion of outputs that preserve them. Group by format and complexity; averaging a short text file with a large scanned document hides the actual work.
The initial report should record input bytes, pages or slides where applicable, selected extras, plugin settings, package versions and failures. Preserve cold-start and warmed-process measurements separately. Reusing an initialized converter measures something different from spawning a CLI process for each file.
Locate time and memory costs in the real path
The inspected convert_stream implementation copies non-seekable input into memory before conversion. Format-specific libraries may add their own buffers. Measure peak resident memory as well as elapsed time, especially when several conversion jobs share one worker; an upload stream alone is not evidence of constant memory use.
Optional image-description models and cloud document services add separate network, service and billing components. Measure those calls independently of local extraction. Do not attribute an external service delay to the local format parser or compare a local run with a cloud-assisted run as if their output semantics were identical.
Publish measurements readers can reproduce
Use repeated runs, state the hardware and report a distribution rather than a single best duration. Count failed conversions and rejected outputs in the denominator. A useful operating metric is total processing and review cost divided by the number of accepted documents; it connects performance to the work a reader actually needs.
The timer below measures one local call only. It neither captures peak memory nor proves output quality. Extend it with a fixture evaluator and process-level resource sampling before making performance claims. This article supplies a measurement design and does not invent benchmark numbers or keyword-demand statistics.
Implementation steps
- 1
Create labeled fixtures grouped by file type and complexity.
- 2
Record environment, plugins and conversion options.
- 3
Measure repeated cold and warm runs with failures included.
- 4
Evaluate preserved facts before computing cost per accepted document.
Copy-ready example
from time import perf_counter
from markitdown import MarkItDown
converter = MarkItDown(enable_plugins=False)
start = perf_counter()
result = converter.convert_local("example.docx")
elapsed = perf_counter() - start
print({"seconds": elapsed, "characters": len(result.markdown)})Frequently asked questions
Are output character counts a quality score?
No. They can expose empty or unexpectedly short output but cannot verify that the correct facts survived.
Does this article claim measured throughput?
No. It defines a reproducible measurement approach and an example timer, without presenting unperformed benchmarks as results.
Sources
- Pinned sourceSource checked 2026-09-07
- Core dispatcherSource checked 2026-09-07
- Plain-text converterSource checked 2026-09-07
- Public exportsSource checked 2026-09-07