# Performance

This page benchmarks multimark against other popular Python Markdown parsers and explains why multimark is fast.


# Benchmark: Markdown to HTML

We render a ~4 KB Markdown document (with headings, lists, code blocks, inline formatting, and a table) **1,000 times** and measure the average time per call. All parsers produce HTML output. The benchmark document is repeated 10 times to reach a realistic size, and each library is configured with GFM table and strikethrough support where available.


<style>
#gdxrblefma table {
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Fira Sans', 'Droid Sans', Arial, sans-serif;
          -webkit-font-smoothing: antialiased;
          -moz-osx-font-smoothing: grayscale;
        }

#gdxrblefma thead, tbody, tfoot, tr, td, th { border-style: none; }
 tr { background-color: transparent; }
#gdxrblefma p { margin: 0; padding: 0; }
 #gdxrblefma .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: 100%; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; }
 #gdxrblefma .gt_caption { padding-top: 4px; padding-bottom: 4px; }
 #gdxrblefma .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; }
 #gdxrblefma .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; }
 #gdxrblefma .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #gdxrblefma .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; }
 #gdxrblefma .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #gdxrblefma .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; }
 #gdxrblefma .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; }
 #gdxrblefma .gt_column_spanner_outer:first-child { padding-left: 0; }
 #gdxrblefma .gt_column_spanner_outer:last-child { padding-right: 0; }
 #gdxrblefma .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; }
 #gdxrblefma .gt_spanner_row { border-bottom-style: hidden; }
 #gdxrblefma .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; }
 #gdxrblefma .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; }
 #gdxrblefma .gt_from_md> :first-child { margin-top: 0; }
 #gdxrblefma .gt_from_md> :last-child { margin-bottom: 0; }
 #gdxrblefma .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; }
 #gdxrblefma .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; }
 #gdxrblefma .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; }
 #gdxrblefma .gt_row_group_first td { border-top-width: 2px; }
 #gdxrblefma .gt_row_group_first th { border-top-width: 2px; }
 #gdxrblefma .gt_striped { color: #333333; background-color: #F4F4F4; }
 #gdxrblefma .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; }
 #gdxrblefma .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; }
 #gdxrblefma .gt_first_grand_summary_row_bottom { border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; }
 #gdxrblefma .gt_last_grand_summary_row_top { border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; }
 #gdxrblefma .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; }
 #gdxrblefma .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; text-align: left; }
 #gdxrblefma .gt_left { text-align: left; }
 #gdxrblefma .gt_center { text-align: center; }
 #gdxrblefma .gt_right { text-align: right; font-variant-numeric: tabular-nums; }
 #gdxrblefma .gt_font_normal { font-weight: normal; }
 #gdxrblefma .gt_font_bold { font-weight: bold; }
 #gdxrblefma .gt_font_italic { font-style: italic; }
 #gdxrblefma .gt_super { font-size: 65%; }
 #gdxrblefma .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; }
 #gdxrblefma .gt_asterisk { font-size: 100%; vertical-align: 0; }
 
</style>

<table class="gt_table" data-quarto-disable-processing="true" data-quarto-bootstrap="false">
<thead>
<tr class="gt_heading">
<th colspan="4" class="gt_heading gt_title gt_font_normal">Markdown → HTML Benchmark</th>
</tr>
<tr class="gt_heading">
<th colspan="4" class="gt_heading gt_subtitle gt_font_normal gt_bottom_border">4,450-byte document, 1,000 iterations, best-of-average</th>
</tr>
<tr class="gt_col_headings">
<th id="Package" class="gt_col_heading gt_columns_bottom_border gt_left" scope="col">Package</th>
<th id="Engine" class="gt_col_heading gt_columns_bottom_border gt_left" scope="col">Engine</th>
<th id="Time-(µs)" class="gt_col_heading gt_columns_bottom_border gt_right" scope="col">Time (µs)</th>
<th id="Relative" class="gt_col_heading gt_columns_bottom_border gt_right" scope="col">Relative</th>
</tr>
</thead>
<tbody class="gt_table_body">
<tr>
<td class="gt_row gt_left" style="font-weight: bold">multimark</td>
<td class="gt_row gt_left" style="font-weight: bold">cmark-gfm (C)</td>
<td class="gt_row gt_right" style="font-weight: bold">98.6</td>
<td class="gt_row gt_right" style="font-weight: bold">1.0×</td>
</tr>
<tr>
<td class="gt_row gt_left">cmarkgfm</td>
<td class="gt_row gt_left">cmark-gfm (C)</td>
<td class="gt_row gt_right">114.3</td>
<td class="gt_row gt_right">1.2×</td>
</tr>
<tr>
<td class="gt_row gt_left">mistune</td>
<td class="gt_row gt_left">Pure Python</td>
<td class="gt_row gt_right">1,773.0</td>
<td class="gt_row gt_right">18.0×</td>
</tr>
<tr>
<td class="gt_row gt_left">commonmark.py</td>
<td class="gt_row gt_left">Pure Python</td>
<td class="gt_row gt_right">3,016.9</td>
<td class="gt_row gt_right">30.6×</td>
</tr>
<tr>
<td class="gt_row gt_left">markdown-it-py</td>
<td class="gt_row gt_left">Pure Python</td>
<td class="gt_row gt_right">3,031.3</td>
<td class="gt_row gt_right">30.7×</td>
</tr>
</tbody><tfoot class="gt_sourcenotes">
<tr>
<td colspan="4" class="gt_sourcenote">Lower is better. 'Relative' is normalized to multimark = 1.0×.</td>
</tr>
</tfoot>

</table>


# Why multimark Is Fast

multimark wraps a compiled C library via CFFI. The actual parsing and rendering happens entirely in C, with Python involved only for the function call overhead and string encoding/decoding. This architecture gives you the ergonomics of Python with the speed of native code.

For typical Markdown documents (a few kilobytes), rendering completes in microseconds. The overhead of the Python-to-C boundary is negligible compared to what a pure-Python parser would spend on the same input.


# Memory Efficiency

Each render call allocates memory for the AST and output buffer in C, then frees it before returning. There is no persistent state between calls, which means memory usage stays constant regardless of how many documents you process.


``` python
import sys
from multimark import markdown_to_html

result = markdown_to_html("# Hello\n\nWorld\n")
print(f"Result type: {type(result).__name__}")
print(f"Result size: {sys.getsizeof(result)} bytes")
```


    Result type: str
    Result size: 69 bytes


The C library manages its own memory internally. Python only holds the final output string, so you do not need to worry about manual memory management or cleanup.


# Tips for Maximum Throughput

<span class="gd-details-chevron" aria-hidden="true"></span><img src="data:image/svg+xml;base64,PHN2ZyBzdHlsZT0iaGVpZ2h0OjFlbTt3aWR0aDoxZW07dmVydGljYWwtYWxpZ246LTAuMTI1ZW07Zm9udC1zaXplOmluaGVyaXQ7b3ZlcmZsb3c6dmlzaWJsZTtwb3NpdGlvbjpyZWxhdGl2ZTsiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld2JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLSBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJnZC1kZXRhaWxzLWljb24iIGFyaWEtaGlkZGVuPSJ0cnVlIj48cGF0aCBkPSJNNCAxNGExIDEgMCAwIDEtLjc4LTEuNjNsOS45LTEwLjJhLjUuNSAwIDAgMSAuODYuNDZsLTEuOTIgNi4wMkExIDEgMCAwIDAgMTMgMTBoN2ExIDEgMCAwIDEgLjc4IDEuNjNsLTkuOSAxMC4yYS41LjUgMCAwIDEtLjg2LS40NmwxLjkyLTYuMDJBMSAxIDAgMCAwIDExIDE0eiIgLz48L3N2Zz4=" class="gd-details-icon" />Performance tips


**Minimize extension overhead.** Each enabled extension adds a small amount of parsing work. If you do not need tables or autolinks for a particular batch, omit them.

**Reuse option values.** Building the options bitmask is cheap, but defining it once outside a loop avoids redundant work.

**Process in batches, not one-at-a-time.** If you are reading files from disk, batch the I/O and render calls together rather than interleaving with other heavy operations.

**Avoid unnecessary encoding.** multimark accepts `str` and handles UTF-8 encoding internally. Do not pre-encode to bytes and decode back; just pass the string directly.
