
<?xml version='1.0' encoding='utf-8'?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Bhargav Kulkarni</title>
    <link>https://bhargavkulk.github.io</link>
    <description>Blog and research from Bhargav Kulkarni</description>
    <item>
      <title>Speeding up Skia using E-graphs: A Retrospective</title>
      <link>https://bhargavkulk.github.io/blog/skia.html</link>
      <pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="true">https://bhargavkulk.github.io/blog/skia.html</guid>
      <content:encoded>&lt;div id="preamble"&gt;
&lt;div class="sectionbody"&gt;
&lt;p &gt;For the past year I have been working on a research project to try and speed up the rendering of
webpages in Google Chrome.
There are many ways in which one could achieve this, but as PL researchers, we&amp;#8201;&amp;#8212;&amp;#8201;my advisor Pavel, an
undergrad I mentored Henry, and I&amp;#8201;&amp;#8212;&amp;#8201;focused on the graphics API of Skia, which is Chrome&amp;#8217;s
rendering engine.
The crux of this project was to treat Skia&amp;#8217;s graphics API as a programming language and optimize it
like one.
This was actually Pavel&amp;#8217;s second go at this project; he and a previous student already tried
optimizing Skia with E-graphs, but that did not go anywhere and the project was dormant.
I found the idea to be very interesting, and decided to have a go at it.
A year later, we ended up with an optimizer that nets on average a 19% speedup on the most visited
websites in the world.&lt;/p&gt;
&lt;p &gt;I wanted to write something about my effort, and that would mean I have to explain a lot of
background about Skia.
Turns out Pavel already wrote a blog post titled &lt;a href="https://pavpanchekha.com/blog/skia-egraph.html"&gt;"Speeding up Skia using E-graphs"&lt;/a&gt; a while back.
So instead of repeating Pavel&amp;#8217;s words, I decided that this blog post would be a retrospective of
Pavel&amp;#8217;s blog post.
I&amp;#8217;ll quote relevant parts of his blog post, while chiming in with new information we discovered on
our second attempt.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="_retrospective"&gt;Retrospective&lt;/h2&gt;&lt;blockquote&gt;
&lt;p &gt;Skia has three main types of structured data in its API.
The first is &lt;strong&gt;shape&lt;/strong&gt;.
These are things you can draw, stuff like circles, rounded rectangles, and text (a very important
type of shape).
The second is &lt;strong&gt;surfaces&lt;/strong&gt;.
A surface is a 2D array of pixels, which is where you draw shapes.
Note that Skia itself talks about surfaces using several different surface-level API calls, like
SaveLayer and Surface, but internally at the end of the day Skia is either creating a 2D array of
pixels or not.
Finally, there are &lt;strong&gt;paints&lt;/strong&gt;, which are ways of drawing a shape to a surface.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p &gt;In broad strokes this describes a basic working model for Skia.
But in the course of trying to come up with &lt;em&gt;correct&lt;/em&gt; rewrites, we refined this model a bit more.
Instead of &lt;strong&gt;shapes&lt;/strong&gt; and &lt;strong&gt;surfaces&lt;/strong&gt; you just have an &lt;strong&gt;image&lt;/strong&gt;.
An &lt;strong&gt;image&lt;/strong&gt; is simply a 2D buffer of pixels.
Skia API calls simply consume image(s) and return a modified image.
For example, the &lt;code&gt;DrawRect&lt;/code&gt; command consumes an image of a rectangle, the image we are drawing on,
and returns a new image which is the old image with a rectangle drawn on top of it.
Paints are just a collection of operators &lt;em&gt;on&lt;/em&gt; images.
They describe how an image gets filled, filtered, or blended.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p &gt;The sequence of shapes, surfaces, and paints is like a program.
It&amp;#8217;s not written as source code—it is expressed temporally through Skia API calls—but nonetheless it
can be executed, it produces an output (typically a collection of output surfaces), and it has a
clear semantics.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p &gt;I am not sure why Pavel says that a Skia program has "clear semantics".
It really does not, as a large part of this project was trying to clarify the semantics of Skia.
Understanding the semantics of Skia is &lt;em&gt;very important&lt;/em&gt; to ensure we derive correct optimizations.
By correct I mean optimizations that do not change the picture being drawn.
While the model I described is very simple, we still have to reason about the various ways in which
different parts of the model interact.
This is hard to do without writing down the semantics of Skia (which we do in the project, in fact
we mechanize it in Lean).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p &gt;E-graphs seem like a good match for this kind of optimization.
E-graphs are good at equational reasoning with purely-functional ASTs, and Skia is kind of like
this.
Many of the equivalence relationships seem kind of equational-ish, and while the API is not purely
functional (since painting to a surface modifies the surface) it&amp;#8217;s potentially possible to treat
those surfaces as linear data structures and to enforce linearity when we extract from the e-graph
instead of when synthesizing alternative implementations inside the e-graph itself.
Also, while Skia programs are relatively large, they are still much smaller than, say, LLVM IR, and
should fit into an e-graph comfortably.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p &gt;An E-graph is a data structure that represents equivalent terms in a succinct manner.
To use an E-graph, you would apply rewrites on an initial term, and once it saturates you can
extract the least costly equivalent term out of the E-graph.
E-graphs are cool, but it turns out that they are very slow for doing optimizations in the browser.
Rendering only has a budget of a few microseconds, and E-graphs cannot optimize terms that
quickly.&lt;sup class="footnote"&gt;[&lt;a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote."&gt;1&lt;/a&gt;]&lt;/sup&gt;
So to ensure performing the optimization pays for itself, we instead chose to write a linear-time
rewriter from scratch.
It's really fast!
For some large websites, our optimizer only takes 32μs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p &gt;Skia programs can be extracted from actual applications like Chrome.
I am told that code for doing this and generating "SkPicture" files already exists.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p &gt;Extracting Skia programs (as &lt;code&gt;SkPictures&lt;/code&gt;) is very easy!
You just call &lt;code&gt;chrome.gpuBenchmarking.printToSkPicture(&amp;lt;path&amp;gt;)&lt;/code&gt; from Chrome&amp;#8217;s debug console.
Skia also has utilities to convert the binary SKP format to human-readable JSON.
Also, the Skia codebase was a treat to work on!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p &gt;I think typically the goal should be optimizing for space, meaning number of surfaces.
You could care about total surface memory or peak surface memory at any one time, but the reason
this is the most important optimization goal is that surfaces typically live on the GPU, and GPU
memory is limited.
Moreover, at a higher level Chrome, the Skia client, will trade space for time (by caching more
surfaces), so saving memory will turn into saving time as well.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p &gt;We were never able to measure memory wins from our optimizer, but using Skia&amp;#8217;s &lt;code&gt;nanobench&lt;/code&gt;
benchmarking harness, we learned that our optimizer is actually very good at optimizing rendering time!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p &gt;One tricky bit to optimizing well is going to be handling error.
When a shape is drawn to a surface, there is going to be some error due to rasterization.
Error here means images don&amp;#8217;t look as sharp as they could.
It&amp;#8217;s best to ignore this at least in the first attempt at this problem because my intuition is that
programs that use fewer surfaces are also going to have less error (they rasterize less often).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p &gt;This phenomenon is called anti-aliasing and is caused by the engine trying to figure out how to
draw curves and other weird stuff that cross pixel boundaries.
We ignore this completely in our work 🙂.
This was in fact not a big deal, because we did not find too many anti-aliasing differences that were
very visible to the human eye after running websites through our optimizer.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p &gt;I think writing down a semantics for Skia programs, proving some equivalences about it, and building
the infrastructure to extract Skia programs from Chrome are all interesting research tasks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p &gt;We did all of this!
Pavel does not give much importance to the semantics aspect of this project in his blog post, but in
our attempt, the majority of the project was about the semantics.
We mechanize both the abstract model of Skia I described in the beginning and semantics that lower
Skia&amp;#8217;s API to this abstract model.
We then stared at websites for a long time and tried to come up with general rewrites that we proved
correct w.r.t. our semantics in Lean.
We then use these proven correct rewrites in an actual Skia optimizer that performs really well!
As a cherry on top, we convert both unoptimized and optimized Skia programs back into Lean terms, and
translation-validate them against our rewrites using our Lean semantics!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p &gt;If there are any interesting missing optimizations (probably?) they could be communicated to either
the Skia or Chrome folks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p &gt;We did try to do this, but the Chrome and Skia team did not come to a consensus on who should
implement the rewrites. We are still talking to them, and hopefully we can get our research
upstreamed in some way.&lt;/p&gt;
&lt;h2 id="_conclusion"&gt;Conclusion&lt;/h2&gt;&lt;p &gt;This was a fun project to work on, and the numbers we get seem to show it was also a successful one.
I am excited for follow-up research, because we realized near the end of this project that most graphics APIs
are very similar to Skia.&lt;sup class="footnote"&gt;[&lt;a id="_footnoteref_2" class="footnote" href="#_footnotedef_2" title="View footnote."&gt;2&lt;/a&gt;]&lt;/sup&gt;
It would be very exciting to extend our semantics work to other APIs and see what we can do with
them.
Be on the lookout for our paper on this project!&lt;/p&gt;
&lt;section id="footnotes"&gt;&lt;ul&gt;

&lt;li class="footnote" id="_footnotedef_1"&gt;
[&lt;a href="#_footnoteref_1"&gt;1&lt;/a&gt;] I don't have concrete numbers to list here, but it's pretty slow!
&lt;/li&gt;
&lt;li class="footnote" id="_footnotedef_2"&gt;
[&lt;a href="#_footnoteref_2"&gt;2&lt;/a&gt;] In some sense all of these 2D graphics APIs relate to PostScript in some manner.
&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;</content:encoded>
    </item>
    <item>
      <title>Website Extras</title>
      <link>https://bhargavkulk.github.io/blog/website-extras.html</link>
      <pubDate>Tue, 22 Jul 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="true">https://bhargavkulk.github.io/blog/website-extras.html</guid>
      <content:encoded>&lt;div id="preamble"&gt;
&lt;div class="sectionbody"&gt;
&lt;p &gt;Here are few extra things I have access to in my website if I need them.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id="_details"&gt;Details&lt;/h2&gt;&lt;p &gt;&lt;code&gt;asciidoctor&lt;/code&gt; has native support for &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; HTML tag. If I write this
in asciidoc:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code class="adoc language-adoc"&gt;.Click to reveal the answer
[%collapsible]
====
This is the answer.
====&lt;/code&gt;&lt;/pre&gt;
&lt;p &gt;I get this:&lt;/p&gt;
&lt;details&gt;
&lt;summary class="title"&gt;Click to reveal the answer&lt;/summary&gt;
&lt;div class="content"&gt;
&lt;p &gt;This is the answer.&lt;/p&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;h2 id="_admonitions"&gt;Admonitions&lt;/h2&gt;&lt;p &gt;Admonitions are a neat way to highlight specific information. They are also
supported in &lt;code&gt;asciidoctor&lt;/code&gt;.&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code class="adoc language-adoc"&gt;[IMPORTANT]
====
While werewolves are hardy community members, keep in mind the following dietary
concerns:

. They are allergic to cinnamon.
. More than two glasses of orange juice in 24 hours makes them howl in harmony with alarms and sirens.
. Celery makes them sad.

====&lt;/code&gt;&lt;/pre&gt;
&lt;p &gt;Which gets rendered as:&lt;/p&gt;
&lt;div class="admonitionblock important"&gt;
&lt;div class="title"&gt;Important!&lt;/div&gt;

&lt;p &gt;While werewolves are hardy community members, keep in mind the following dietary
concerns:&lt;/p&gt;
&lt;ol  class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;They are allergic to cinnamon.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;More than two glasses of orange juice in 24 hours makes them howl in harmony with alarms and sirens.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Celery makes them sad.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p &gt;I am still unsure how to style this. I might also have to change the HTML generated.&lt;/p&gt;
&lt;h2 id="_quotes"&gt;Quotes&lt;/h2&gt;&lt;p &gt;Quotes are pretty useful in a website; my main page uses them!&lt;/p&gt;
&lt;p &gt;Here is how you write a quote in asciidoc:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code class="adoc language-adoc"&gt;[quote,Monty Python and the Holy Grail]
____
Dennis: Come and see the violence inherent in the system. Help! Help! I'm being
repressed!

King Arthur: Bloody peasant!

Dennis: Oh, what a giveaway! Did you hear that? Did you hear that, eh? That's
what I'm on about! Did you see him repressing me? You saw him, Didn't you?
____&lt;/code&gt;&lt;/pre&gt;
&lt;p &gt;Which we see as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p &gt;Dennis: Come and see the violence inherent in the system. Help! Help! I&amp;#8217;m being
repressed!&lt;/p&gt;
&lt;p &gt;King Arthur: Bloody peasant!&lt;/p&gt;
&lt;p &gt;Dennis: Oh, what a giveaway! Did you hear that? Did you hear that, eh? That&amp;#8217;s
what I&amp;#8217;m on about! Did you see him repressing me? You saw him, Didn&amp;#8217;t you?&lt;/p&gt;
&lt;br&gt;
&amp;#8212; &lt;cite&gt;Monty Python and the Holy Grail&lt;/cite&gt;
&lt;/blockquote&gt;
&lt;h2 id="_source_code"&gt;Source Code&lt;/h2&gt;&lt;p &gt;As a computer scientist, my blog for sure will have some code. Source code
listings in asciidoc are simple enough:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code class="adoc language-adoc"&gt;[source,python]
----
print('Hello, World!')
----&lt;/code&gt;&lt;/pre&gt;
&lt;p &gt;Becomes:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code class="python language-python"&gt;print('Hello, World!')&lt;/code&gt;&lt;/pre&gt;
&lt;p &gt;I am still on the fence on whether I should have syntax highlighting. I don&amp;#8217;t
want to fill my blog with code! &lt;code&gt;asciidoctor&lt;/code&gt; has some neat features, like
adding callouts for specific lines. When I see the need to use those features, I
will add styling for them.&lt;/p&gt;
&lt;h2 id="_math"&gt;Math&lt;/h2&gt;&lt;p &gt;&lt;code&gt;asciidoctor&lt;/code&gt; did not have any satisfactory way to render math. The only HTML
output they had used MathJax (that too an older version). I know MathJax to be
slow and the older version is not accessible by screen readers. I could have
done something hacky with Katex but now that most popular browsers support
MathML, I&amp;#8217;d like to use MathML to typeset math in my blog. But, I did not find
any MathML renderers for &lt;code&gt;asciidoctor&lt;/code&gt;. So I wrote one myself. It just shells
out to &lt;code&gt;latexmlmath&lt;/code&gt;, which supports packages like &lt;code&gt;amsmath&lt;/code&gt; and
&lt;code&gt;stmaryrd&lt;/code&gt;(indispensable for wacky computer science theory notation). It was
&lt;em&gt;very easy&lt;/em&gt; to add a new block in &lt;code&gt;asciidoctor&lt;/code&gt;. Here is what my MathML block
looks like:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code class="adoc language-adoc"&gt;[mathml]
++++
\vec{v} \in \mathbb{R}^3 \quad \text{for } x \in \llbracket 0, 1 \rrbracket
++++&lt;/code&gt;&lt;/pre&gt;
&lt;p &gt;which looks like:&lt;/p&gt;
&lt;math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\vec{v}\in\mathbb{R}^{3}\quad\text{for }x\in\llbracket 0,1\rrbracket" display="block"&gt;
  &lt;mrow&gt;
    &lt;mrow&gt;
      &lt;mover accent="true"&gt;
        &lt;mi&gt;v&lt;/mi&gt;
        &lt;mo stretchy="false"&gt;→&lt;/mo&gt;
      &lt;/mover&gt;
      &lt;mo&gt;∈&lt;/mo&gt;
      &lt;msup&gt;
        &lt;mi&gt;ℝ&lt;/mi&gt;
        &lt;mn&gt;3&lt;/mn&gt;
      &lt;/msup&gt;
    &lt;/mrow&gt;
    &lt;mspace width="1em"/&gt;
    &lt;mrow&gt;
      &lt;mrow&gt;
        &lt;mtext&gt;for &lt;/mtext&gt;
        &lt;mo&gt;⁢&lt;/mo&gt;
        &lt;mi&gt;x&lt;/mi&gt;
      &lt;/mrow&gt;
      &lt;mo&gt;∈&lt;/mo&gt;
      &lt;mrow&gt;
        &lt;mo stretchy="false"&gt;⟦&lt;/mo&gt;
        &lt;mn&gt;0&lt;/mn&gt;
        &lt;mo&gt;,&lt;/mo&gt;
        &lt;mn&gt;1&lt;/mn&gt;
        &lt;mo stretchy="false"&gt;⟧&lt;/mo&gt;
      &lt;/mrow&gt;
    &lt;/mrow&gt;
  &lt;/mrow&gt;
&lt;/math&gt;
&lt;p &gt;Pretty neat!&lt;/p&gt;</content:encoded>
    </item>
    <item>
      <title>New Look!</title>
      <link>https://bhargavkulk.github.io/blog/new-website.html</link>
      <pubDate>Tue, 06 Jan 2026 00:00:00 +0000</pubDate>
      <guid isPermaLink="true">https://bhargavkulk.github.io/blog/new-website.html</guid>
      <content:encoded>&lt;p &gt;Inspired by my friend &lt;a href="https://andrewriachi.com/"&gt;Andrew's retro website&lt;/a&gt;, I&amp;#8217;ve
decided to turn back the clock on my website. I got rid of the old CSS entirely.
&lt;sup class="footnote"&gt;[&lt;a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote."&gt;1&lt;/a&gt;]&lt;/sup&gt;
I also tweaked some of the generated code. For example, the old blog/research
index code was a rats nest of &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;'s and CSS flexbox code that Claude spat out
a long time ago. Turns out a structured list of things separated by columns and
rows can be represented by a &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;! Who would have thought! There is still
more surgical cleaning up to do. &lt;code&gt;asciidoctor&lt;/code&gt; generates less than desirable
HTML for many of its elements. Fortunately we can change what HTML the scripts
writes, so it should not be &lt;em&gt;too&lt;/em&gt; bad. A weird thing I realized after removing
all the CSS is that no CSS webpages seem to be insanely responsive on small
screens like mobile phones! Which means the very little styling I intend to do
in the future should be fine on all devices. Finally I found these cute little
button/banner images to put in the footer:&lt;/p&gt;
&lt;img loading="lazy" width="88" height="31" fetchpriority="low" src="https://bhargavkulk.github.io/images/emacs.gif"/&gt;
&lt;img loading="lazy" width="88" height="31" fetchpriority="low" src="https://bhargavkulk.github.io/images/sanehtml.gif"/&gt;
&lt;img loading="lazy" width="88" height="31" fetchpriority="low" src="https://bhargavkulk.github.io/images/firefox3.gif"/&gt;
&lt;p &gt;They look so cool! But I could not find banners for Python (which also powers
this site). Now the only thing this site is missing is a &lt;code&gt;&amp;lt;marquee&amp;gt;&lt;/code&gt;. If only
it was not deprecated&amp;#8230;&amp;#8203;&lt;/p&gt;
&lt;section id="footnotes"&gt;&lt;ul&gt;

&lt;li class="footnote" id="_footnotedef_1"&gt;
[&lt;a href="#_footnoteref_1"&gt;1&lt;/a&gt;] There is still &lt;em&gt;some&lt;/em&gt; CSS remaining
&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;</content:encoded>
    </item>
    <item>
      <title>My Website</title>
      <link>https://bhargavkulk.github.io/blog/my-website.html</link>
      <pubDate>Fri, 18 Jul 2025 00:00:00 +0000</pubDate>
      <guid isPermaLink="true">https://bhargavkulk.github.io/blog/my-website.html</guid>
      <content:encoded>&lt;p &gt;I have redon my website. Again. The markup is now in
&lt;a href="https://asciidoc.org/"&gt;asciidoc&lt;/a&gt;. I have moved lot of the file wrangling and
renaming code to a makefile. The makefile orchestrates &lt;code&gt;asciidoctor&lt;/code&gt; and
various Python scripts, each of which performs a specific task, such as
extracting metadata, generating blog indexes, or filling in templates. The
templating engine continues to be &lt;a href="https://www.makotemplates.org/"&gt;mako&lt;/a&gt;. I also
took the chance to redo the website styling a bit. I took a lot of inspiration
from &lt;a href="https://perfectmotherfuckingwebsite.com/"&gt;Perfect M- Website&lt;/a&gt;.&lt;/p&gt;</content:encoded>
    </item>
  </channel>
</rss>