<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Performance |</title><link>https://mikelayuso.com/tags/performance/</link><atom:link href="https://mikelayuso.com/tags/performance/index.xml" rel="self" type="application/rss+xml"/><description>Performance</description><generator>HugoBlox Kit (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Sun, 19 Apr 2026 00:00:00 +0000</lastBuildDate><image><url>https://mikelayuso.com/media/icon_hu_b0970716f810afd6.png</url><title>Performance</title><link>https://mikelayuso.com/tags/performance/</link></image><item><title>From a Forgotten Python Script to a 2500x Faster Rust Tool: The Evolution of PNGToSVG</title><link>https://mikelayuso.com/blog/png-to-svg-performance/</link><pubDate>Sun, 19 Apr 2026 00:00:00 +0000</pubDate><guid>https://mikelayuso.com/blog/png-to-svg-performance/</guid><description>&lt;p&gt;I just wanted to share the story behind a tool I’ve been working on called
. As you can probably guess, it converts PNG images into SVG vectors.&lt;/p&gt;
&lt;p&gt;The project started back in 2019 as a small Python script I wrote for a frontend job. Sometimes we needed to work with SVGs, and I just wanted a quick way to convert images locally without uploading them to some random remote service. When I switched jobs, I took that code, tossed it onto GitHub, and completely forgot about it.&lt;/p&gt;
&lt;p&gt;Fast forward five years. Out of nowhere, a contributor (&amp;ldquo;Kartik Nayak&amp;rdquo; on GitHub) made the first Rust implementation of the tool. A few months later, someone else (&amp;ldquo;Salman Sali&amp;rdquo;) jumped in and improved it by adding parallelization.&lt;/p&gt;
&lt;p&gt;This was late 2024. Seeing the community breathe new life into my old script blew my mind and gave me the perfect excuse to dive in and learn Rust myself. Since then, I’ve been rewriting, polishing, and tweaking the tool, focusing heavily on ease of use and performance.&lt;/p&gt;
&lt;p&gt;What used to be a sluggish script that took a couple of seconds to process a tiny 64x64 icon can now chew through 8000x8000 images in a fraction of that time. To put this performance leap into perspective, I ran a benchmark using a relatively complex ~900KB image.&lt;/p&gt;
&lt;p&gt;Here is how the evolution of the project looks:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Time (Seconds)&lt;/th&gt;
&lt;th&gt;Relative Performance&lt;/th&gt;
&lt;th&gt;Speedup Strategy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Python (Legacy)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,124.14s&lt;/td&gt;
&lt;td&gt;100% (Baseline)&lt;/td&gt;
&lt;td&gt;Original implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v0.4.0 / v0.4.1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~24.50s&lt;/td&gt;
&lt;td&gt;~2.18%&lt;/td&gt;
&lt;td&gt;Initial Rust Port (O(n) lookups)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v0.5.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.15s&lt;/td&gt;
&lt;td&gt;0.10%&lt;/td&gt;
&lt;td&gt;O(1) Hash-Set Optimization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;v0.6.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.43s&lt;/td&gt;
&lt;td&gt;0.04%&lt;/td&gt;
&lt;td&gt;Memory Reuse &amp;amp; Direct IO&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Going from over 18 minutes in the original Python implementation to under half a second in Rust represents a massive &lt;strong&gt;~2580x speedup&lt;/strong&gt;. But the numbers only tell half the story. Translating this project into Rust became an amazing learning exercise, and the technical journey went roughly like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Phase 1: The Rust Port (v0.4.x):&lt;/strong&gt; Moving to Rust gave us a huge baseline boost (about 45x faster than Python), but the code still relied on linear searches for neighbor lookups, which struggled under the heavy weight of complex paths.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phase 2: The Breakthrough (v0.5.0):&lt;/strong&gt; As I learned more, I realized we could replace vector-based neighbor checks with O(1) Hash-Set lookups. This single change killed the quadratic scaling bottleneck and dropped processing time from 24 seconds to just 1.1 seconds—a 20x jump just within the Rust codebase.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phase 3: Total Efficiency (v0.6.0):&lt;/strong&gt; Finally, I focused on memory efficiency. Instead of constantly allocating new structures for every shape, we now reuse memory buffers, read pixel data directly via raw buffers, and use sorted edge lookups to trace shapes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Recently, I published the latest release (v0.6.1), which polishes a few rough edges and includes a shiny new (and very &amp;ldquo;programmer art&amp;rdquo;) icon, hahaha. Since then, I&amp;rsquo;ve received plenty of feedback from the community. Based on that, I&amp;rsquo;m already planning the improvements for v0.6.2, as well as a v0.7.0 release (which will include some breaking API changes, hence the version bump).&lt;/p&gt;
&lt;p&gt;Honestly, the best part of this whole experience hasn&amp;rsquo;t been the crazy performance gains. It’s the fact that randomly deciding to share a quick script allowed me to receive very useful community contributions, learn a completely new language, and actually build a fast, easy-to-use tool that actually helps me without interrupting my workflow with wait times in my own projects.&lt;/p&gt;</description></item></channel></rss>