<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Pgo on Bernát Gábor — Python packaging, tox, virtualenv &amp; open source</title><link>https://bernat.tech/tags/pgo/</link><description>Engineering notes from Bernát Gábor — PyPA maintainer of tox, virtualenv, pipx, filelock, and platformdirs. Python packaging, type hints, and supply chain security.</description><generator>Hugo 0.163.3</generator><language>en-US</language><lastBuildDate>Thu, 18 Jun 2026 09:00:00 +0000</lastBuildDate><atom:link href="https://bernat.tech/tags/pgo/index.xml" rel="self" type="application/rss+xml"/><item><title>Building a fast HTML toolkit in C for Python</title><link>https://bernat.tech/posts/blazing-fast-html-parser/</link><pubDate>Thu, 18 Jun 2026 09:00:00 +0000</pubDate><author>Bernát Gábor</author><guid>https://bernat.tech/posts/blazing-fast-html-parser/</guid><description>&amp;lt;div class=&amp;#34;alert alert-info&amp;#34;&amp;gt;
&amp;lt;p class=&amp;#34;alert-title&amp;#34;&amp;gt;&amp;lt;i class=&amp;#34;fa-solid fa-circle-info&amp;#34; aria-hidden=&amp;#34;true&amp;#34;&amp;gt;&amp;lt;/i&amp;gt; TLDR: turbohtml does HTML escape, unescape, tokenize, query, serialize, and URL work in C, 3-22x faster than Python&amp;amp;#39;s standard library. The recurring trick is skipping work:&amp;lt;/p&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Scan in blocks, not characters.&amp;lt;/strong&amp;gt; &amp;lt;a href=&amp;#34;#the-swar-trick-checking-eight-bytes-with-one-subtraction&amp;#34;&amp;gt;SWAR&amp;lt;/a&amp;gt;
clears eight
bytes with a subtraction, &amp;lt;a href=&amp;#34;#sixteen-bytes-with-one-shuffle&amp;#34;&amp;gt;SIMD&amp;lt;/a&amp;gt;
sixteen with one shuffle; a clean block costs almost
nothing.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Measure, then write.&amp;lt;/strong&amp;gt; &amp;lt;a href=&amp;#34;#two-passes-measure-then-write&amp;#34;&amp;gt;One pass sizes the output exactly&amp;lt;/a&amp;gt;
, so the second allocates
once and bulk-copies the clean stretches.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Keep text at its native width and copy it rarely.&amp;lt;/strong&amp;gt; The tokenizer stamps its state machine
&amp;lt;a href=&amp;#34;#stamping-the-machine-once-per-width&amp;#34;&amp;gt;once per width&amp;lt;/a&amp;gt;
and hands back
&amp;lt;a href=&amp;#34;#never-copy-text-you-dont-have-to&amp;#34;&amp;gt;zero-copy slices&amp;lt;/a&amp;gt;
into the input.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;The same instincts across the toolkit.&amp;lt;/strong&amp;gt; Tag names &amp;lt;a href=&amp;#34;#interning-names-to-integers&amp;#34;&amp;gt;interned to integers&amp;lt;/a&amp;gt;
, an
&amp;lt;a href=&amp;#34;#building-the-index-once-instead-of-every-time&amp;#34;&amp;gt;id index built once&amp;lt;/a&amp;gt;
that turns an O(N²) path walk linear, and
&amp;lt;a href=&amp;#34;#recycling-the-wrapper-objects&amp;#34;&amp;gt;wrapper objects recycled&amp;lt;/a&amp;gt;
on a free list.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;When the work is a standard, not a scan.&amp;lt;/strong&amp;gt; Host encoding needs
&amp;lt;a href=&amp;#34;#when-the-work-is-a-standard-not-a-scan&amp;#34;&amp;gt;Punycode, normalization, and Hangul by arithmetic&amp;lt;/a&amp;gt;
, with the Unicode tables
generated at build time.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Down to the build and the benchmark.&amp;lt;/strong&amp;gt; &amp;lt;a href=&amp;#34;#teaching-the-compiler-what-is-hot&amp;#34;&amp;gt;LTO and PGO&amp;lt;/a&amp;gt;
squeeze the machine code;
the CI gate &amp;lt;a href=&amp;#34;#measuring-without-lying-to-yourself&amp;#34;&amp;gt;counts instructions under Callgrind&amp;lt;/a&amp;gt;
so a regression cannot hide in
the noise.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Free-threaded.&amp;lt;/strong&amp;gt; No shared mutable state, so it declares &amp;lt;code&amp;gt;Py_MOD_GIL_NOT_USED&amp;lt;/code&amp;gt; and runs on the
&amp;lt;a href=&amp;#34;https://peps.python.org/pep-0703/&amp;#34; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noopener noreferrer&amp;#34;&amp;gt;no-GIL build&amp;lt;/a&amp;gt;
without forcing the lock back on.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Safe under hostile input.&amp;lt;/strong&amp;gt; It
&amp;lt;a href=&amp;#34;#when-the-input-is-trying-to-hurt-you&amp;#34;&amp;gt;caps nesting, dedups attributes in linear time, and overflow-checks every buffer&amp;lt;/a&amp;gt;
,
and the sanitizer is validated against DOMPurify&amp;amp;rsquo;s own XSS corpus.&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;turbohtml was built with Claude (Opus 4.8), not by hand, over a month and close to 300 iterations. I review the code
and own its correctness; &amp;lt;a href=&amp;#34;#how-this-was-built&amp;#34;&amp;gt;more on how, and my thanks, at the end&amp;lt;/a&amp;gt;
.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;</description><category>python</category><category>c</category><category>performance</category><category>simd</category><category>html</category><category>parser</category><category>tokenizer</category><category>unicode</category><category>turbohtml</category><category>idna</category><category>pgo</category><category>lto</category><category>benchmarking</category></item></channel></rss>