<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Techstack by Aftershoot]]></title><description><![CDATA[Deep dive into Machine Learning, Rust and building performant cross platform desktop apps]]></description><link>https://aftershoot.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!pKbp!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F442a891b-99c4-451c-9da5-70841c4891e6_769x769.png</url><title>Techstack by Aftershoot</title><link>https://aftershoot.substack.com</link></image><generator>Substack</generator><lastBuildDate>Sun, 26 Apr 2026 08:07:21 GMT</lastBuildDate><atom:link href="https://aftershoot.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Aftershoot]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[aftershoot@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[aftershoot@substack.com]]></itunes:email><itunes:name><![CDATA[Harshit Dwivedi]]></itunes:name></itunes:owner><itunes:author><![CDATA[Harshit Dwivedi]]></itunes:author><googleplay:owner><![CDATA[aftershoot@substack.com]]></googleplay:owner><googleplay:email><![CDATA[aftershoot@substack.com]]></googleplay:email><googleplay:author><![CDATA[Harshit Dwivedi]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Supercharging Aftershoot with OpenVINO]]></title><description><![CDATA[At Aftershoot, speed isn&#8217;t just a nice-to-have, it&#8217;s part of the experience.]]></description><link>https://aftershoot.substack.com/p/supercharging-aftershoot-with-openvino</link><guid isPermaLink="false">https://aftershoot.substack.com/p/supercharging-aftershoot-with-openvino</guid><dc:creator><![CDATA[Karan]]></dc:creator><pubDate>Mon, 18 Aug 2025 20:48:20 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/cf4e2830-b7c2-440e-9f34-000f6a42deb0_1200x628.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>At Aftershoot, speed isn&#8217;t just a nice-to-have, it&#8217;s part of the experience. Our workflow&#8212;from ingesting to culling to editing and retouching&#8212; stands apart by how we inference our AI models.. When photographers are processing thousands of images in a single sitting, every saved minute translates directly into more time spent on creative work. That&#8217;s why improving inference speed has always been a priority for us.</p><div><hr></div><h3>Why OpenVINO?</h3><p>In our search for better performance, we started exploring <a href="https://github.com/openvinotoolkit/openvino">Intel&#8217;s OpenVINO</a> for AI model inferencing. Designed to maximize Intel hardware, whether that&#8217;s CPUs, integrated graphics, dedicated GPUs like Arc, or even the latest NPUs. It brings hardware-aware optimizations, better memory usage, and faster load times, all without sacrificing model accuracy.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://aftershoot.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Techstack by Aftershoot! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>We began experimenting with <a href="https://github.com/openvinotoolkit/openvino">OpenVINO</a> through <a href="https://github.com/microsoft/onnxruntime">ONNX Runtime</a>, and the results were encouraging. This led us to integrate it directly into our app, a process that involved close collaboration with Intel&#8217;s team. After months of work, we shipped a production-ready OpenVINO-enabled Aftershoot build by May 2025. Since then, we&#8217;ve taken it a step further&#8212;exploring <a href="https://github.com/intel/openvino-rs">native OpenVINO</a>(<em>Rust bindings for compatibility</em>) for additional benefits such as more efficient memory usage, faster model loading, and support for older Intel Mac architectures.</p><p><em>While OpenVINO is currently enabled only for our latest Retouching feature, we plan to extend support to Culling and Editing soon.</em></p><h4>Hurdles and Challenges</h4><p>The journey wasn&#8217;t without challenges. We had to account for older Intel CPUs with limited instruction set extensions, bifurcate OpenVINO&#8217;s enablement based on users&#8217; GPU drivers/VRAM, ensure compatibility for Intel Macs, deal with some of our models with custom ops, custom library compilations, and the list goes on. Integrating all of this into our non-traditional backend also required some work, but the payoff was undeniable once we started measuring results.</p><div><hr></div><h3>Time for some numbers!</h3><p>While all of this sounds great, but feels incomplete without some actual comparisons.</p><p>We have listed down some benchmarks for Windows and Intel MAC(<em>deployment WIP</em>)</p><p>To quantify the improvements, we benchmarked 200 single-subject portrait images using 11 retouching sliders, such as Acne Removal, Eye Bag Removal, Face Brightening, and Shine Removal, comparing our existing backend against OpenVINO-accelerated inference.</p><p><strong>Windows</strong><em>(MSI Prestige 13 Intel&#174; Core&#8482; Ultra 7 Processor 258V, 32 GB RAM)</em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-In6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441b1940-a8e3-41c5-a848-5a232637f640_1202x244.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-In6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441b1940-a8e3-41c5-a848-5a232637f640_1202x244.png 424w, https://substackcdn.com/image/fetch/$s_!-In6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441b1940-a8e3-41c5-a848-5a232637f640_1202x244.png 848w, https://substackcdn.com/image/fetch/$s_!-In6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441b1940-a8e3-41c5-a848-5a232637f640_1202x244.png 1272w, https://substackcdn.com/image/fetch/$s_!-In6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441b1940-a8e3-41c5-a848-5a232637f640_1202x244.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-In6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441b1940-a8e3-41c5-a848-5a232637f640_1202x244.png" width="1202" height="244" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/441b1940-a8e3-41c5-a848-5a232637f640_1202x244.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:244,&quot;width&quot;:1202,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:44298,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://aftershoot.substack.com/i/171308180?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441b1940-a8e3-41c5-a848-5a232637f640_1202x244.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-In6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441b1940-a8e3-41c5-a848-5a232637f640_1202x244.png 424w, https://substackcdn.com/image/fetch/$s_!-In6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441b1940-a8e3-41c5-a848-5a232637f640_1202x244.png 848w, https://substackcdn.com/image/fetch/$s_!-In6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441b1940-a8e3-41c5-a848-5a232637f640_1202x244.png 1272w, https://substackcdn.com/image/fetch/$s_!-In6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F441b1940-a8e3-41c5-a848-5a232637f640_1202x244.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><strong>Intel MAC</strong><em>(MacBook Pro 16 Intel(R) Core(TM) i7-9750H CPU, 32 GB RAM)</em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yHhB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda0f83c4-086d-43f8-b3c0-5fe1de8c18e4_1200x248.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yHhB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda0f83c4-086d-43f8-b3c0-5fe1de8c18e4_1200x248.png 424w, https://substackcdn.com/image/fetch/$s_!yHhB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda0f83c4-086d-43f8-b3c0-5fe1de8c18e4_1200x248.png 848w, https://substackcdn.com/image/fetch/$s_!yHhB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda0f83c4-086d-43f8-b3c0-5fe1de8c18e4_1200x248.png 1272w, https://substackcdn.com/image/fetch/$s_!yHhB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda0f83c4-086d-43f8-b3c0-5fe1de8c18e4_1200x248.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yHhB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda0f83c4-086d-43f8-b3c0-5fe1de8c18e4_1200x248.png" width="1200" height="248" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/da0f83c4-086d-43f8-b3c0-5fe1de8c18e4_1200x248.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:248,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:43923,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://aftershoot.substack.com/i/171308180?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda0f83c4-086d-43f8-b3c0-5fe1de8c18e4_1200x248.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yHhB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda0f83c4-086d-43f8-b3c0-5fe1de8c18e4_1200x248.png 424w, https://substackcdn.com/image/fetch/$s_!yHhB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda0f83c4-086d-43f8-b3c0-5fe1de8c18e4_1200x248.png 848w, https://substackcdn.com/image/fetch/$s_!yHhB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda0f83c4-086d-43f8-b3c0-5fe1de8c18e4_1200x248.png 1272w, https://substackcdn.com/image/fetch/$s_!yHhB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda0f83c4-086d-43f8-b3c0-5fe1de8c18e4_1200x248.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h4><strong>The BIG gains!</strong></h4><p>The benchmarks speak for themselves. On the latest Intel processors (Lunar Lake), OpenVINO delivered <em>~10x</em> faster inference. Even on older Intel Mac hardware, performance improved <em>~1.8x</em>. Beyond raw speed, we also observed gains in memory efficiency and model loading times&#8212;making our systems more resource- and hardware-efficient.</p><div><hr></div><h3><strong>BIG! Thanks to the Intel Crew</strong></h3><p>We&#8217;re grateful to the OpenVINO team and the Intel engineers who supported us throughout this journey. They were incredibly helpful in guiding us through integration challenges and solving the issues we faced along the way.</p><div><hr></div><h3><strong>Why We Do It at Aftershoot</strong></h3><p>At Aftershoot, we&#8217;ve always believed in staying ahead by adopting state-of-the-art technology that truly benefits our users. Performance improvements aren&#8217;t just about numbers on a benchmark&#8212;they&#8217;re about making sure photographers can trust us to deliver the fastest, smoothest, and most reliable experience possible. We take pride in building with the best, because our customers deserve nothing less.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rsHw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdac1f71-2a33-4fb3-a07a-c5e8ff798b7a_1418x326.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rsHw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdac1f71-2a33-4fb3-a07a-c5e8ff798b7a_1418x326.png 424w, https://substackcdn.com/image/fetch/$s_!rsHw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdac1f71-2a33-4fb3-a07a-c5e8ff798b7a_1418x326.png 848w, https://substackcdn.com/image/fetch/$s_!rsHw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdac1f71-2a33-4fb3-a07a-c5e8ff798b7a_1418x326.png 1272w, https://substackcdn.com/image/fetch/$s_!rsHw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdac1f71-2a33-4fb3-a07a-c5e8ff798b7a_1418x326.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rsHw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdac1f71-2a33-4fb3-a07a-c5e8ff798b7a_1418x326.png" width="1418" height="326" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fdac1f71-2a33-4fb3-a07a-c5e8ff798b7a_1418x326.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:326,&quot;width&quot;:1418,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:114805,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://aftershoot.substack.com/i/171308180?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdac1f71-2a33-4fb3-a07a-c5e8ff798b7a_1418x326.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rsHw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdac1f71-2a33-4fb3-a07a-c5e8ff798b7a_1418x326.png 424w, https://substackcdn.com/image/fetch/$s_!rsHw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdac1f71-2a33-4fb3-a07a-c5e8ff798b7a_1418x326.png 848w, https://substackcdn.com/image/fetch/$s_!rsHw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdac1f71-2a33-4fb3-a07a-c5e8ff798b7a_1418x326.png 1272w, https://substackcdn.com/image/fetch/$s_!rsHw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdac1f71-2a33-4fb3-a07a-c5e8ff798b7a_1418x326.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://aftershoot.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Techstack by Aftershoot! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How Web Workers can Supercharge your Electron App (and save your UI from lag death)]]></title><description><![CDATA[Are WebSocket Calls in Your Electron Render Thread Silently Wrecking Performance? Here&#8217;s How to Offload the Damage.]]></description><link>https://aftershoot.substack.com/p/how-web-workers-can-supercharge-your</link><guid isPermaLink="false">https://aftershoot.substack.com/p/how-web-workers-can-supercharge-your</guid><dc:creator><![CDATA[Vibhor Gupta]]></dc:creator><pubDate>Sat, 02 Aug 2025 08:19:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!n08E!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e611d5f-1f1a-49ca-bdf1-ed27e69ddad0_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!n08E!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e611d5f-1f1a-49ca-bdf1-ed27e69ddad0_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!n08E!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e611d5f-1f1a-49ca-bdf1-ed27e69ddad0_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!n08E!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e611d5f-1f1a-49ca-bdf1-ed27e69ddad0_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!n08E!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e611d5f-1f1a-49ca-bdf1-ed27e69ddad0_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!n08E!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e611d5f-1f1a-49ca-bdf1-ed27e69ddad0_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!n08E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e611d5f-1f1a-49ca-bdf1-ed27e69ddad0_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5e611d5f-1f1a-49ca-bdf1-ed27e69ddad0_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2504202,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://aftershoot.substack.com/i/164879176?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e611d5f-1f1a-49ca-bdf1-ed27e69ddad0_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!n08E!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e611d5f-1f1a-49ca-bdf1-ed27e69ddad0_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!n08E!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e611d5f-1f1a-49ca-bdf1-ed27e69ddad0_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!n08E!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e611d5f-1f1a-49ca-bdf1-ed27e69ddad0_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!n08E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e611d5f-1f1a-49ca-bdf1-ed27e69ddad0_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The good part about frontend performance issues is that they almost always manifest as janky animations and non responsive UI which make it obvious that something&#8217;s off. The bad part about frontend performance issues is that they almost always manifest as janky animations and non responsive UI which make it hard to figure out where they&#8217;re coming from - they could be a direct result of something you wrote or they could be related to something much deeper.</p><h2>The symptom</h2><p>Let&#8217;s start with what we noticed. We have a web socket connection setup that sends through binary data from the backend which can be large in size (in order of 10s, or even a couple 100 of MBs) depending on how big the images that are displayed on the app are, and this data is sent through the wire while loading images. We noticed that during the time an image was loading, the UI was freezing up completely, users couldn&#8217;t scroll through the page, no clicks and hovers were being registered. The app flow was such that the UI freeze didn&#8217;t hamper the UX too much, but performance bugs are like dead pixels on a screen. Once you spot them, they&#8217;re impossible to ignore.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://aftershoot.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Techstack by Aftershoot! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>The problem</h2><p>The chrome profiler (Our app is built on Electron which uses chromium underneath) is your best friend in situations like these when you need to find out what&#8217;s going on under the hood.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HQgQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5778cfba-181c-44de-a163-3fd829d0e707_2742x1964.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HQgQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5778cfba-181c-44de-a163-3fd829d0e707_2742x1964.png 424w, https://substackcdn.com/image/fetch/$s_!HQgQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5778cfba-181c-44de-a163-3fd829d0e707_2742x1964.png 848w, https://substackcdn.com/image/fetch/$s_!HQgQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5778cfba-181c-44de-a163-3fd829d0e707_2742x1964.png 1272w, https://substackcdn.com/image/fetch/$s_!HQgQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5778cfba-181c-44de-a163-3fd829d0e707_2742x1964.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HQgQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5778cfba-181c-44de-a163-3fd829d0e707_2742x1964.png" width="2742" height="1964" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5778cfba-181c-44de-a163-3fd829d0e707_2742x1964.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1964,&quot;width&quot;:2742,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:432207,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://aftershoot.substack.com/i/164879176?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2430c70f-d43e-4a54-90ba-6e40badb3082_3024x1964.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HQgQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5778cfba-181c-44de-a163-3fd829d0e707_2742x1964.png 424w, https://substackcdn.com/image/fetch/$s_!HQgQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5778cfba-181c-44de-a163-3fd829d0e707_2742x1964.png 848w, https://substackcdn.com/image/fetch/$s_!HQgQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5778cfba-181c-44de-a163-3fd829d0e707_2742x1964.png 1272w, https://substackcdn.com/image/fetch/$s_!HQgQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5778cfba-181c-44de-a163-3fd829d0e707_2742x1964.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Profiler outputs can be a bit overwhelming with their information, but the two main things to notice in this snapshot are:</p><ol><li><p>The large highlighted bar in the timeline: The long red coloured section in this flame graph is the largest section which has blocked the execution of every other task until it is done. During this time, no user interaction or browser tasks are happening.</p></li><li><p>The 627ms time block: The &#8220;System&#8221; is taking up a large chunk in the pie chart at the bottom left and the actual code execution marked by yellow (the Javascript code that is running), is very small. This indicates that the slowdown is not originating directly from some slow, blocking Javascript that we might have written, rather from something that chrome classifies as &#8220;System&#8221; and we have no control over.</p></li></ol><p>So, what&#8217;s going on? It&#8217;s great that we&#8217;ve narrowed down the problem, but what can we do if it&#8217;s the system that&#8217;s taking up all the time and not our code directly?</p><h4>Blink: Chromium&#8217;s rendering engine</h4><p>Blink is the browser engine (or the rendering engine) that powers all chromium based browsers. Being a rendering engine, it&#8217;s responsible for handling layout and painting pipelines and parsing the HTML/CSS, JS, images and other resources and turning them into interactive web pages. It is also responsible for orchestrating networking in the browser, which is what interests us.</p><p>Before trying to guess what might be happening, let&#8217;s look into Blink&#8217;s WebSocket implementation since that&#8217;s where our problem lies. A couple of code snippets are of interest:</p><ol><li><p><strong>Receiving each frame:</strong></p><pre><code>void WebSocketChannelImpl::OnDataFrame(
    bool fin,
    network::mojom::blink::WebSocketMessageType type,
    uint64_t data_length) {
  pending_data_frames_.push_back(
      DataFrame(fin, type, static_cast&lt;uint32_t&gt;(data_length)));
  ConsumePendingDataFrames();
}</code></pre></li><li><p><strong>Coalescing frames:</strong></p><pre><code>void WebSocketChannelImpl::ConsumePendingDataFrames() {
  while (!pending_data_frames_.empty() &amp;&amp; !backpressure_) {
    auto&amp; frame = pending_data_frames_.front();
    ConsumeDataFrame(frame.fin,
                     frame.type,
                     &#8230;,
                     frame.data_length);
    pending_data_frames_.pop_front();
  }
}</code></pre></li><li><p><strong>Buffer&#8209;until&#8209;FIN and dispatch</strong>:</p><pre><code>void WebSocketChannelImpl::ConsumeDataFrame(
    bool fin,
    network::mojom::blink::WebSocketMessageType type,
    const char* data,
    size_t size) {

  if (type == WebSocketMessageType::TEXT)
    receiving_message_type_is_text_ = true;
  else if (type == WebSocketMessageType::BINARY)
    receiving_message_type_is_text_ = false;

  message_chunks_.Append(data, size);

  if (fin) {
    auto full = message_chunks_.Take();
    if (receiving_message_type_is_text_)
      client_-&gt;DidReceiveTextMessage(WTFMove(full), {});
    else
      client_-&gt;DidReceiveBinaryMessage(WTFMove(full), {});

    message_chunks_.ResetMemory();
  }
}</code></pre></li><li><p><strong>Wrap bytes in JS buffer:</strong></p><pre><code>void DOMWebSocket::DidReceiveBinaryMessage(
    const Vector&lt;base::span&lt;const char&gt;&gt;&amp; data) {

  /* some code */

  switch (binary_type_) {

    /* some code */

    case V8BinaryType::Enum::kArraybuffer:
      DOMArrayBuffer* array_buffer = DOMArrayBuffer::Create(data);
      event_queue_-&gt;Dispatch(
          MessageEvent::Create(array_buffer, origin_string_));
      break;
  }

  NotifyWebSocketActivity();
}</code></pre></li></ol><p></p><p>These (incredibly cleanly written, succinct) functions tell us the full story clearly. There are two layers at play here:</p><ol><li><p><strong>WebSocketChannelImpl</strong><code>:</code> Orchestrates the network layer to Blink handoff. It accepts raw frames sent from the backend via the upgraded socket connection and buffers them into C++  byte chunks until it finds the FIN bit in one of the incoming frames, after which it stitches the frames together into a message chunk and passes it off to the DOM client. &#8220;FIN&#8221; is a bit that is present in a websocket frame header that denotes if the current frame is the last logical incoming frame (FIN = 1) or not (FIN = 0), which indicates whether the network client needs to stitch all the frames till this one into a chunk and pass on for further handling.</p></li><li><p><strong>DOMWebSocket</strong><code>: </code>Implements the W3C standard of WebSocket interface which has listeners like <strong>onmessage</strong>, <strong>onopen</strong>, <strong>onerror</strong> and properties like <strong>readyState</strong>. The <em>DidReceiveBinaryMessage </em>method is handed the buffered frames from the previous step where it creates a JS array buffer to be dispatched as a <em>MessageEvent </em>to the <em>onmessage</em> event listener. This creation of the array buffer part is where C++ land interacts with JS land inside V8, where the byte chunks are allocated on the heap and made into a JS <em>ArrayBuffer </em>and passed along to event listeners.</p></li></ol><p>With this behind the scenes knowledge, we can safely conclude that most of the &#8220;System&#8221; time being spent in the browser is of the byte chunks buffering inside Blink until the handoff to V8 takes place (since JS allocation and execution in V8 is always captured in profiles).</p><h2>The solution</h2><p>Before arriving at the solution, here are some constraints of the system we&#8217;re dealing with - the buffers in question can be of multiple 10s or even a couple 100s of MBs in size, and the rendering module on the client side that&#8217;s supposed to utilise these buffers also can&#8217;t render a stream of incoming bytes, and needs the data upfront.</p><h4>Approach 1: Sending multiple frames</h4><p>The data that we&#8217;re sending over the wire is in a single frame (with the FIN bit set), irrespective of how big the payload is - and since Blink buffers all the frames till it hits a frame where FIN is set, we&#8217;re forcing the browser to buffer the entire payload at once. This will cause performance and memory pressure in the main execution thread, trying to deserialise, allocate, and buffer this much data. One solution to this problem is to break this data up into multiple frames of reasonable payload sizes that won&#8217;t hurt the browser too much, and will allow it to interleave the data buffering with other browser interactions; given each frame is small enough, so that the user doesn&#8217;t experience a frozen UI and the profiler doesn&#8217;t show big grey bars of  &#8220;System&#8221; work.</p><p>Since the rendering module on the client can&#8217;t handle incoming byte streams, this approach warrants pre allocation of a JS ArrayBuffer of the appropriate length (an ArrayBuffer is how we store and move around bytes to and from the rendering module) depending on the size of the byte stream that&#8217;s going to arrive, and holding on to that memory until all the chunks of data are received. This would require sending the dimensions of data through in a special kind of starting frame to communicate size constraints so the client can allocate memory accordingly, sending all the data through in multiple frames after that so the browser can chunk up FIN adjacent frames for the DOM layer where these chunk slices can be appended to the pre allocated ArrayBuffer, and a special ending frame to allow cleanup and releasing memory.</p><p>This seems like a viable solution, but we didn&#8217;t go with it. Why? Well firstly, time was of the essence, and this approach required backend to cut up FIN and non-FIN frames of the payload and in frontend to implement the buffering logic which is non trivial and time taking. Even if we had the time (and whatever the opposite of laziness is), there was another consideration - things would still happen on the main thread, albeit in very small time slices if we used small enough frames (which we would have to arrive at with some experimentation, to keep chunk buffering within 50-100ms max). Since ours is a desktop app, the user device this program would run on would introduce another variable in terms of system capabilities - we wouldn&#8217;t be able to deterministically predict whether what seems a small enough chunk size for us to occupy a small enough time slice, would be the same on the user&#8217;s machine (spoiler: it wouldn&#8217;t be). And so we moved on.</p><h4>Approach 2: Web workers</h4><p>Another approach to this would be to bypass the main thread entirely and let web workers do the heavy lifting. They allow us to offload CPU intensive tasks like image decoding, heavy I/O or other long blocking stuff on a separate thread, thus freeing up the main execution thread for user interactions and smooth rendering.</p><p>Web workers run on separate OS threads in isolation from the main execution thread and any interaction happens via message passing. They have their own V8 isolate spawned - which means their own memory (JS heap), their own event loop, their own garbage collection state, and their own V8 context with a global scope (without the <em>Window</em> object).</p><p>Opening the web socket connection in the web worker, completely skipping the main execution thread, would offload all our woes to a separate isolate. All the long times of buffering in Blink&#8217;s websocket and the handoff to JS land happens in parallel in the worker, while the main execution thread remains free for UI interactions. When the byte array is ready and available in JS, it is passed on to the main thread from the worker and that&#8217;s the only bit of blocking work that gets done, which is deterministically a lot quicker than the entire buffering that was happening before. </p><p>Let&#8217;s see the performance profiles of what happens in the worker and what happens in the main execution thread with this approach:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8SGh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f637ba1-3091-458c-9c0f-b8e3709af44a_3024x1964.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8SGh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f637ba1-3091-458c-9c0f-b8e3709af44a_3024x1964.png 424w, https://substackcdn.com/image/fetch/$s_!8SGh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f637ba1-3091-458c-9c0f-b8e3709af44a_3024x1964.png 848w, https://substackcdn.com/image/fetch/$s_!8SGh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f637ba1-3091-458c-9c0f-b8e3709af44a_3024x1964.png 1272w, https://substackcdn.com/image/fetch/$s_!8SGh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f637ba1-3091-458c-9c0f-b8e3709af44a_3024x1964.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8SGh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f637ba1-3091-458c-9c0f-b8e3709af44a_3024x1964.png" width="1456" height="946" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8f637ba1-3091-458c-9c0f-b8e3709af44a_3024x1964.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:946,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:654341,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://aftershoot.substack.com/i/164879176?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f637ba1-3091-458c-9c0f-b8e3709af44a_3024x1964.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8SGh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f637ba1-3091-458c-9c0f-b8e3709af44a_3024x1964.png 424w, https://substackcdn.com/image/fetch/$s_!8SGh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f637ba1-3091-458c-9c0f-b8e3709af44a_3024x1964.png 848w, https://substackcdn.com/image/fetch/$s_!8SGh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f637ba1-3091-458c-9c0f-b8e3709af44a_3024x1964.png 1272w, https://substackcdn.com/image/fetch/$s_!8SGh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8f637ba1-3091-458c-9c0f-b8e3709af44a_3024x1964.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Web worker does the heavy lifting of web socket buffering and deserialising</figcaption></figure></div><p>The web worker (socket.worker in the above snapshot, the expanded one) has the large red chunk of death which takes up more than 600ms to do what the main execution thread was doing earlier - accepting the byte chunks from websocket and buffering them up, all inside &#8220;System&#8221;. The function <em>openConnection </em>also makes an appearance here because it houses the deserialising logic inside JS land once the app receives the buffer.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hf8F!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33134de9-e092-4b2b-99d2-6c088d16b9f8_2280x1964.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hf8F!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33134de9-e092-4b2b-99d2-6c088d16b9f8_2280x1964.png 424w, https://substackcdn.com/image/fetch/$s_!hf8F!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33134de9-e092-4b2b-99d2-6c088d16b9f8_2280x1964.png 848w, https://substackcdn.com/image/fetch/$s_!hf8F!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33134de9-e092-4b2b-99d2-6c088d16b9f8_2280x1964.png 1272w, https://substackcdn.com/image/fetch/$s_!hf8F!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33134de9-e092-4b2b-99d2-6c088d16b9f8_2280x1964.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hf8F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33134de9-e092-4b2b-99d2-6c088d16b9f8_2280x1964.png" width="2280" height="1964" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33134de9-e092-4b2b-99d2-6c088d16b9f8_2280x1964.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1964,&quot;width&quot;:2280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:379576,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://aftershoot.substack.com/i/164879176?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1fc9a7b-a00a-4727-a883-8eca73212a4c_3024x1964.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hf8F!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33134de9-e092-4b2b-99d2-6c088d16b9f8_2280x1964.png 424w, https://substackcdn.com/image/fetch/$s_!hf8F!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33134de9-e092-4b2b-99d2-6c088d16b9f8_2280x1964.png 848w, https://substackcdn.com/image/fetch/$s_!hf8F!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33134de9-e092-4b2b-99d2-6c088d16b9f8_2280x1964.png 1272w, https://substackcdn.com/image/fetch/$s_!hf8F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33134de9-e092-4b2b-99d2-6c088d16b9f8_2280x1964.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Browser thread stays interactive and only receives the deserialised output</figcaption></figure></div><p>The browser thread now shows us a task of 100ms duration, give or take. This is a <strong>~80% improvement</strong> from what we had in the beginning of this article. The only thing that happens in this thread is the handoff of the buffer from the web worker, which involves some Javascript execution and memory allocations in the innards of web workers. In fact, the reason that this task is also being marked as red is not because of the actual handoff, but because of more functions being called right after which are responsible for some business logic (as seen from subsequent stacks in the flame graph after the <em>onmessage</em> stack, and now that I think about it, maybe I should look into optimising those as well). As depicted by the flat parts of the inverted steps, passing data from the worker to this thread takes a fraction of the total time, around 10ms to 12ms - and that is for a very large byte array in our particular use case that we&#8217;re measuring for.</p><p>We&#8217;ll look at some code that implements this. The codebase we&#8217;re dealing with is written in React (TypeScript) bundled with Webpack v4.</p><p>We want to use and import our workers as <code>.ts</code> files, let&#8217;s declare a TS module:</p><pre><code>/* worker.d.ts */

declare module '*.worker.ts' {
    class WebpackWorker extends Worker {
&#9;constructor()
    }

    export default WebpackWorker
}
</code></pre><p>To allow the bundler to understand the above, let&#8217;s add the relevant loader to our Webpack config</p><pre><code>/* webpack.config.ts */

modules: {
    rules: [
        ...,
&#9;{
            test: /\.worker\.ts$/,
&#9;    use: [
&#9;&#9;    {
&#9;&#9;&#9;loader: 'worker-loader',
&#9;            },
&#9;&#9;    'ts-loader',
&#9;    ],
&#9;},
    ]
}</code></pre><p>Now for the actual worker file which houses our logic. Here we&#8217;re establishing a connection to the web socket server and accepting a stream of bytes. Once the entire buffer lands, we deserialise it and the post a message called <em><strong>received</strong></em> to the main thread with the data</p><pre><code>/* workers/socket.worker.ts */

/// &lt;reference lib="webworker" /&gt;

self.onmessage = function (event) {
    switch (event.data.type) {
        case 'connect':
&#9;    connectSocket(event.data.options)
&#9;    break
&#9;case 'close':
&#9;    disconnectSocket()
&#9;    break
&#9;default:
&#9;    break
    }
}

let socket: WorkerSocket

function connectSocket(options: ISocketOpts) {
     socket = WorkerSocket.getSocketInstance(options)
}

function disconnectSocket() {
     socket.close()
     socket = null
}

class WorkerSocket {
    private static instance: WorkerSocket | null = null
    private wsStream: WebSocketStream | null
    private reader: ReadableStreamDefaultReader&lt;Uint8Array&gt; | null
    private controller: AbortController | null
    private retryCount: number
    private maxRetries: number
    private closed: boolean

    constructor(options: ISocketOpts) {
&#9;this.wsStream = null
&#9;this.reader = null
&#9;this.controller = new AbortController()
&#9;this.retryCount = 0
&#9;this.maxRetries = 3
&#9;this.closed = true
&#9;if (!this.wsStream || options.forceReconnect) {
&#9;    this.connect(options)
&#9;}
    }

    /**
     * Connect to the websocket server.
     */
    private connect(options: ISocketOpts): void {
&#9;try {
&#9;    this.openConnection(options)
&#9;} catch (error) {
&#9;    this.handleConnectionError(error)
&#9;}
    }

    /**
     * Open a new WebSocket connection and stream buffers.
     */
    private async openConnection(options: ISocketOpts): Promise&lt;void&gt;{
        try {
&#9;    await this.cleanup()

&#9;    this.wsStream = new WebSocketStream(options.url, {
&#9;&#9;                signal: this.controller.signal,
&#9;&#9;            })
&#9;    const { readable } = await this.wsStream.opened
&#9;    this.reader = readable.getReader()

&#9;    this.closed = false
&#9;    this.retryCount = 0

&#9;    self.postMessage({
                type: 'connected',
                message: 'Successfully connected to stream'
            })

&#9;    while (true) {
&#9;&#9;const { value, done } = await this.reader.read()
&#9;&#9;if (done) {
&#9;            self.postMessage({
                        type: 'closed',
                        message: 'Successfully closed stream'
                    })
&#9;&#9;    break
&#9;&#9;}

&#9;        const deserialisedBuf = await deserialiseBuffer(value)
&#9;        self.postMessage(
                   {
                       type: 'received',
                       ...deserialisedBuf
                   } as WorkerBufferEvent,
                   [
                       deserialisedBuf.data.buffer,
                       deserialisedBuf.downscaledData.buffer,
&#9;           ]
                )
&#9;    }
&#9;} catch (error) {
&#9;    this.retryConnection(options)
&#9;}
    }
}
</code></pre><p>Let&#8217;s export this <code>.ts</code> file as a module from the index file</p><pre><code>/* workers/index.ts */

import SocketWorker from '@workers/socket.worker.ts'
const socketWorker = new SocketWorker()

export { socketWorker }
</code></pre><p>In the main thread, we&#8217;ll listen for the <em><strong>received</strong></em> message and process the incoming buffer accordingly inside the <em><strong>onBufferResponse </strong></em>function</p><pre><code>/* App.tsx */

import { socketWorker } from '@workers'

// Create the web socket connection
socketWorker.postMessage({
    type: 'connect',
    options: {
&#9;url: 'ws://localhost:43938/websocket_server',
&#9;forceReconnect: false,
        ...,
    },
})

// register listeners for worker messages
socketWorker.onmessage = async function (event) {
    switch (event.data.type) {
&#9;case 'received':
&#9;&#9;await onBufferResponse(event.data as WorkerBufferEvent)
&#9;&#9;break
&#9;default:
&#9;&#9;break
    }
}


</code></pre><p>This pretty straightforward setup is enough to make our approach work. Not too verbose, not too complex.</p><p></p><h4>Is there a cost to all this?</h4><p>This is a pretty acceptable solution to the performance bottleneck we faced, but as with most optimisations in software, there&#8217;s a cost attached - in optimising for one dimension, you often have to let go of another. In this case, it would be the app&#8217;s overall memory consumption. Spawning a web worker bears a natural memory space overhead for it&#8217;s entire lifetime since it involves spawning an OS thread and a full V8 isolate within it - it&#8217;s difficult to deterministically say how much (it would change from system to system), but 10-20 MB of increased memory footprint would be a safe estimate. The program that you&#8217;ve offloaded to the worker will consume memory on top of this, so make sure to keep an eye on the Activity Monitor/Task Manager and the heap snapshots of your application to make sure things aren&#8217;t going out of control.</p><p>The tradeoff of this increase vs the improved UI interaction experience seemed acceptable to us, but it might not be for everyone else&#8217;s use case, so these tools should be used with a grain of salt. Workers can be terminated programatically quite easily and it is encouraged to keep closing them as and when their work is done, unless the problem at hand requires them to be kept alive the entire time (which was the case for us).</p><h3>What else with web workers?</h3><p>This approach is also really efficient in offloading the browser image decoding of large images from the main execution thread, whether you want the decoding to happen directly in the <code>&lt;img/&gt; </code>tag or you want to pass an image as bytes and convert it to an <code>ImageData </code>object using an <code>OffscreenCanvas</code> and use that for rendering. These are both pretty CPU intensive operations and block a chunk of the main execution thread, and can be trivially shifted to web workers.</p><h2>Conclusion</h2><p>Web workers are a great tool to distribute the stress on your main execution thread, and are really handy for handling blocking tasks that might be degrading user experience. However, they are hardly the hammer to use on every performance-degradation nail since there are real costs attached to this hammer in terms of performance and memory overhead for spawning and maintenance in your app. Your decision of performance enhancement tools should be informed by proper profiling and measurements of problematic areas of your software.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://aftershoot.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Techstack by Aftershoot! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Closest Pair Algorithm: A Faster and Comprehensible Approach Using Bit Shift]]></title><description><![CDATA[As the world gets their ass beaten by AI there is hardly any word about fundamental algorithms of Computer Science.]]></description><link>https://aftershoot.substack.com/p/closest-pair-algorithm-a-faster-and</link><guid isPermaLink="false">https://aftershoot.substack.com/p/closest-pair-algorithm-a-faster-and</guid><dc:creator><![CDATA[Karan]]></dc:creator><pubDate>Mon, 07 Apr 2025 18:22:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!8qlP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda7476a3-2590-4549-8d34-a340f0ce4e66_433x365.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As the world gets their ass beaten by AI there is hardly any word about fundamental algorithms of Computer Science. Ironically, these algorithms rose the applications of Computer Science, making it a fundamental thing in the modern era&#8230;</p><p>The Closest Pair Problem is one such problem, though it seems straightforward to solve and also has countless applications like radar systems, collision detection in video games, and air traffic control etc. At its core, the challenge is simply to find the two closest points in a set(2D plane), yet the most optimized solution that exists seems ugly ( &#10012;&#65077;&#10012; )</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://aftershoot.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Techstack by Aftershoot! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h4><strong>Existing Solution</strong></h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8qlP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda7476a3-2590-4549-8d34-a340f0ce4e66_433x365.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8qlP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda7476a3-2590-4549-8d34-a340f0ce4e66_433x365.webp 424w, https://substackcdn.com/image/fetch/$s_!8qlP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda7476a3-2590-4549-8d34-a340f0ce4e66_433x365.webp 848w, https://substackcdn.com/image/fetch/$s_!8qlP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda7476a3-2590-4549-8d34-a340f0ce4e66_433x365.webp 1272w, https://substackcdn.com/image/fetch/$s_!8qlP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda7476a3-2590-4549-8d34-a340f0ce4e66_433x365.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8qlP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda7476a3-2590-4549-8d34-a340f0ce4e66_433x365.webp" width="433" height="365" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/da7476a3-2590-4549-8d34-a340f0ce4e66_433x365.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:365,&quot;width&quot;:433,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:6250,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://aftershoot.substack.com/i/160788781?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda7476a3-2590-4549-8d34-a340f0ce4e66_433x365.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8qlP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda7476a3-2590-4549-8d34-a340f0ce4e66_433x365.webp 424w, https://substackcdn.com/image/fetch/$s_!8qlP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda7476a3-2590-4549-8d34-a340f0ce4e66_433x365.webp 848w, https://substackcdn.com/image/fetch/$s_!8qlP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda7476a3-2590-4549-8d34-a340f0ce4e66_433x365.webp 1272w, https://substackcdn.com/image/fetch/$s_!8qlP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda7476a3-2590-4549-8d34-a340f0ce4e66_433x365.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This algorithm employs a divide-and-conquer strategy with a band technique. First, it splits the point set along a vertical line into roughly equal halves and recursively solves each half to find their minimum distances<em>(fig. 1)</em>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_QUs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8d55e6f-b029-4bdc-824d-310eff5d914e_480x354.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_QUs!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8d55e6f-b029-4bdc-824d-310eff5d914e_480x354.png 424w, https://substackcdn.com/image/fetch/$s_!_QUs!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8d55e6f-b029-4bdc-824d-310eff5d914e_480x354.png 848w, https://substackcdn.com/image/fetch/$s_!_QUs!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8d55e6f-b029-4bdc-824d-310eff5d914e_480x354.png 1272w, https://substackcdn.com/image/fetch/$s_!_QUs!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8d55e6f-b029-4bdc-824d-310eff5d914e_480x354.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_QUs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8d55e6f-b029-4bdc-824d-310eff5d914e_480x354.png" width="480" height="354" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c8d55e6f-b029-4bdc-824d-310eff5d914e_480x354.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:354,&quot;width&quot;:480,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!_QUs!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8d55e6f-b029-4bdc-824d-310eff5d914e_480x354.png 424w, https://substackcdn.com/image/fetch/$s_!_QUs!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8d55e6f-b029-4bdc-824d-310eff5d914e_480x354.png 848w, https://substackcdn.com/image/fetch/$s_!_QUs!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8d55e6f-b029-4bdc-824d-310eff5d914e_480x354.png 1272w, https://substackcdn.com/image/fetch/$s_!_QUs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8d55e6f-b029-4bdc-824d-310eff5d914e_480x354.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">fig. 2</figcaption></figure></div><p>We might miss pairs across the two halves then ;-;</p><p>But rather than checking all cross-pairs, we create a narrow vertical band of width 2&#948; centered at the division line, where &#948; is the minimum distance of the two halves. Within this band, points are sorted by y-coordinate, and for each point, only the next 6 points in this sorted order need to be checked, as geometric packing constraints ensure no more than 6 relevant points can exist within a &#948; &#215; 2&#948; rectangle extending from any point in the band<em>(fig. 2)</em>.</p><blockquote><p><em><strong>Time Complexity</strong></em></p></blockquote><p><code>nlog(n)+2T(n/2)+n+6n -&gt; O(nlogn)<br></code><em>Includes: Initial sorting, recursive calls on the left and right halves, dividing the points and creating the band, band processing (for each point, check up to 6 neighbors)</em></p><p>As complicated it is, to me, it feels there must be a simpler solution that exists. Why not?</p><div><hr></div><h4><strong>Proposed Solution</strong></h4><p>The solution begins with an elegant intuition: why can't we somehow pack each 2D point in a single value to reduce the spatial dimension and then sort them? This linearization might reveal closest pairs at adjacent indices, potentially being a <strong>&#8220;simpler solution&#8221;</strong></p><p><code>Consider an Array A={(x&#7522;, y&#7522;) &#8712; [0 ,2&#179;&#178;-1]}</code></p><p>We start by packing each 2D point <em>a</em> from the original array <em>A</em> into a single packed value <em>a&#7522;&#8203; </em>in a new array <em>A&#8242;</em>, We construct each <em>a&#7522; </em>as a 64-bit integer where the higher-order 32 bits store <em>x&#7522;</em> and the lower-order 32 bits store<em> y&#7522;</em>.</p><p><code>Array A'={a&#7522; &#8712; [0 ,2&#8310;&#8308;-1]}<br>here a&#7522; = (x&#7522; &lt;&lt; 32)| y&#7522; (&lt;&lt; denotes left shift operation, | bitwise OR)</code></p><p>After packing, we apply any sorting technique(can use unstable sort as ordering does not matter) on <em>A&#8217; </em>and start traversing the array, we might think that the solution lies in the adjacent indices, but upon experimentation, this packing introduces discontinuities where close points end up far apart in the <em>A&#8217;.<br></em>But the probability of finding the closest pair increases as you increase the search window size and becomes 100% when the window size is till the number of bits shifted, i.e., 32<em>(fig. 3).</em></p><p><code>Now, &#8704;a&#7522; search space &#8712; [a&#7522;+1, a&#7522;+32]</code></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!x2jF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be0a97a-a7fa-4864-ac96-d6fc59908ce3_700x296.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!x2jF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be0a97a-a7fa-4864-ac96-d6fc59908ce3_700x296.png 424w, https://substackcdn.com/image/fetch/$s_!x2jF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be0a97a-a7fa-4864-ac96-d6fc59908ce3_700x296.png 848w, https://substackcdn.com/image/fetch/$s_!x2jF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be0a97a-a7fa-4864-ac96-d6fc59908ce3_700x296.png 1272w, https://substackcdn.com/image/fetch/$s_!x2jF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be0a97a-a7fa-4864-ac96-d6fc59908ce3_700x296.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!x2jF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be0a97a-a7fa-4864-ac96-d6fc59908ce3_700x296.png" width="700" height="296" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9be0a97a-a7fa-4864-ac96-d6fc59908ce3_700x296.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:296,&quot;width&quot;:700,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!x2jF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be0a97a-a7fa-4864-ac96-d6fc59908ce3_700x296.png 424w, https://substackcdn.com/image/fetch/$s_!x2jF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be0a97a-a7fa-4864-ac96-d6fc59908ce3_700x296.png 848w, https://substackcdn.com/image/fetch/$s_!x2jF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be0a97a-a7fa-4864-ac96-d6fc59908ce3_700x296.png 1272w, https://substackcdn.com/image/fetch/$s_!x2jF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be0a97a-a7fa-4864-ac96-d6fc59908ce3_700x296.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">fig. 3</figcaption></figure></div><p>Visual Representation of the algorithm</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;5f3450c9-ce19-4717-bb9d-0b3a0723ce7e&quot;,&quot;duration&quot;:null}"></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VBbV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb31b8-6826-4cff-aff6-3c83200cba28_1920x1440.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VBbV!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb31b8-6826-4cff-aff6-3c83200cba28_1920x1440.gif 424w, https://substackcdn.com/image/fetch/$s_!VBbV!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb31b8-6826-4cff-aff6-3c83200cba28_1920x1440.gif 848w, https://substackcdn.com/image/fetch/$s_!VBbV!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb31b8-6826-4cff-aff6-3c83200cba28_1920x1440.gif 1272w, https://substackcdn.com/image/fetch/$s_!VBbV!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb31b8-6826-4cff-aff6-3c83200cba28_1920x1440.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VBbV!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb31b8-6826-4cff-aff6-3c83200cba28_1920x1440.gif" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/87eb31b8-6826-4cff-aff6-3c83200cba28_1920x1440.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:5205343,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://aftershoot.substack.com/i/160788781?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb31b8-6826-4cff-aff6-3c83200cba28_1920x1440.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VBbV!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb31b8-6826-4cff-aff6-3c83200cba28_1920x1440.gif 424w, https://substackcdn.com/image/fetch/$s_!VBbV!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb31b8-6826-4cff-aff6-3c83200cba28_1920x1440.gif 848w, https://substackcdn.com/image/fetch/$s_!VBbV!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb31b8-6826-4cff-aff6-3c83200cba28_1920x1440.gif 1272w, https://substackcdn.com/image/fetch/$s_!VBbV!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb31b8-6826-4cff-aff6-3c83200cba28_1920x1440.gif 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><blockquote><p><em><strong>Time Complexity</strong></em></p></blockquote><p><code>nlog(n)+(bits*n) -&gt; O(nlogn)</code><br><em>Includes: Sorting of the packed array, checking in the window size limited by the bit shift number</em></p><blockquote><p><em><strong>Limitations</strong></em></p></blockquote><ul><li><p>Currently, it just works with positive points, i.e., any set of points that lie in the 1st Quadrant of the 2D Cartesian Plane. A workaround would be to process all points to fall in the 1st quadrant by adding some constant <em>c </em>to both x and y coordinates</p></li><li><p>The algorithm works by combining x and y values in bits, so you can only process numbers that are within half the number of max bits supported by your compiler.</p></li><li><p>The algorithm does not work with floating point values(for now)</p></li></ul><p><em>Note: The proof behind how the closest pair lies within the window of number of bits shifted &#8704;a&#7522; is yet to be discovered ;-;</em></p><div><hr></div><p>To me, this seems to be an elegant yet comprehensible solution, but the fun part is that it&#8217;s<br><strong>~4.45x faster than the traditional algorithm (&#9583;&#176;&#9633;&#176;)&#9583;</strong></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1awy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3880072-ff50-4e2d-888e-b9da04dc61fe_700x216.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1awy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3880072-ff50-4e2d-888e-b9da04dc61fe_700x216.png 424w, https://substackcdn.com/image/fetch/$s_!1awy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3880072-ff50-4e2d-888e-b9da04dc61fe_700x216.png 848w, https://substackcdn.com/image/fetch/$s_!1awy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3880072-ff50-4e2d-888e-b9da04dc61fe_700x216.png 1272w, https://substackcdn.com/image/fetch/$s_!1awy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3880072-ff50-4e2d-888e-b9da04dc61fe_700x216.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1awy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3880072-ff50-4e2d-888e-b9da04dc61fe_700x216.png" width="700" height="216" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a3880072-ff50-4e2d-888e-b9da04dc61fe_700x216.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:216,&quot;width&quot;:700,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!1awy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3880072-ff50-4e2d-888e-b9da04dc61fe_700x216.png 424w, https://substackcdn.com/image/fetch/$s_!1awy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3880072-ff50-4e2d-888e-b9da04dc61fe_700x216.png 848w, https://substackcdn.com/image/fetch/$s_!1awy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3880072-ff50-4e2d-888e-b9da04dc61fe_700x216.png 1272w, https://substackcdn.com/image/fetch/$s_!1awy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3880072-ff50-4e2d-888e-b9da04dc61fe_700x216.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">fig.5 benchmark generated by Rust</figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xSd_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd444e617-3fcc-44a2-8686-ee5344c0dfef_700x214.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xSd_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd444e617-3fcc-44a2-8686-ee5344c0dfef_700x214.png 424w, https://substackcdn.com/image/fetch/$s_!xSd_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd444e617-3fcc-44a2-8686-ee5344c0dfef_700x214.png 848w, https://substackcdn.com/image/fetch/$s_!xSd_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd444e617-3fcc-44a2-8686-ee5344c0dfef_700x214.png 1272w, https://substackcdn.com/image/fetch/$s_!xSd_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd444e617-3fcc-44a2-8686-ee5344c0dfef_700x214.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xSd_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd444e617-3fcc-44a2-8686-ee5344c0dfef_700x214.png" width="700" height="214" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d444e617-3fcc-44a2-8686-ee5344c0dfef_700x214.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:214,&quot;width&quot;:700,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!xSd_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd444e617-3fcc-44a2-8686-ee5344c0dfef_700x214.png 424w, https://substackcdn.com/image/fetch/$s_!xSd_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd444e617-3fcc-44a2-8686-ee5344c0dfef_700x214.png 848w, https://substackcdn.com/image/fetch/$s_!xSd_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd444e617-3fcc-44a2-8686-ee5344c0dfef_700x214.png 1272w, https://substackcdn.com/image/fetch/$s_!xSd_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd444e617-3fcc-44a2-8686-ee5344c0dfef_700x214.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">fig.6 Execution time plot against the mean values</figcaption></figure></div><p>The above has been neatly implemented in Rust, can be accessed using this Repository, and is open to criticism :p</p><p><a href="https://github.com/K-prog/closest-pair-rs">https://github.com/K-prog/closest-pair-rs</a></p><p>Thanks for joining in on this journey of annoying points (^o^)&#12494;</p><div><hr></div><p>[1]: Subhash Suri. <em>Closest Pair Algorithms in 2D plane<br></em><a href="https://sites.cs.ucsb.edu/~suri/cs235/ClosestPair.pdf">https://sites.cs.ucsb.edu/~suri/cs235/ClosestPair.pdf</a></p><p>[2]: Wikipedia. <em>Closest Z-order Curve to pack numbers<br></em><a href="https://en.wikipedia.org/wiki/Z-order_curve">https://en.wikipedia.org/wiki/Z-order_curve</a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://aftershoot.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Techstack by Aftershoot! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Mastering Binary Data in JavaScript: ArrayBuffer, TypedArray, and DataView Explained]]></title><description><![CDATA[A Deep Dive into JavaScript&#8217;s Powerful Binary Data Structures: ArrayBuffer, TypedArray, and DataView]]></description><link>https://aftershoot.substack.com/p/mastering-binary-data-in-javascript</link><guid isPermaLink="false">https://aftershoot.substack.com/p/mastering-binary-data-in-javascript</guid><dc:creator><![CDATA[Vibhor Gupta]]></dc:creator><pubDate>Mon, 03 Feb 2025 18:27:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!cM72!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cb8ea2-bd4e-4198-b840-895ea7d49eef_1792x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Often our simple, weakly typed, DOM manipulating JavaScript applications built primarily for smooth user interfaces find themselves in the middle of use cases where they need to handle binary data in some capacity. But isn't that mostly the job of the backend or the middle end, one might wonder - for decently complex software dealing directly with blobs or files, HTTP octet responses, web socket messages from other services, image processing use cases, or transferring data to the GPU, the answer to that question is likely a no. To support these use cases, JavaScript exposes some pretty performant and powerful data structures to handle incoming streams of binary data and model them to fit any application - namely <strong>ArrayBuffer</strong>, <strong>TypedArray</strong>, and <strong>DataView</strong>.</p><p>We'll try to understand more about them by drawing comparisons with the real world, so please bear with my spectacularly unimaginative metaphors.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cM72!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cb8ea2-bd4e-4198-b840-895ea7d49eef_1792x1024.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cM72!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cb8ea2-bd4e-4198-b840-895ea7d49eef_1792x1024.webp 424w, https://substackcdn.com/image/fetch/$s_!cM72!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cb8ea2-bd4e-4198-b840-895ea7d49eef_1792x1024.webp 848w, https://substackcdn.com/image/fetch/$s_!cM72!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cb8ea2-bd4e-4198-b840-895ea7d49eef_1792x1024.webp 1272w, https://substackcdn.com/image/fetch/$s_!cM72!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cb8ea2-bd4e-4198-b840-895ea7d49eef_1792x1024.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cM72!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cb8ea2-bd4e-4198-b840-895ea7d49eef_1792x1024.webp" width="1456" height="832" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/61cb8ea2-bd4e-4198-b840-895ea7d49eef_1792x1024.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:832,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:690030,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cM72!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cb8ea2-bd4e-4198-b840-895ea7d49eef_1792x1024.webp 424w, https://substackcdn.com/image/fetch/$s_!cM72!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cb8ea2-bd4e-4198-b840-895ea7d49eef_1792x1024.webp 848w, https://substackcdn.com/image/fetch/$s_!cM72!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cb8ea2-bd4e-4198-b840-895ea7d49eef_1792x1024.webp 1272w, https://substackcdn.com/image/fetch/$s_!cM72!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61cb8ea2-bd4e-4198-b840-895ea7d49eef_1792x1024.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2><code>ArrayBuffer</code></h2><p>We can think of our raw binary data to be a massive amount of all kinds of building material. To store this amount of material for any sort of further usage, we'd need a warehouse of the appropriate size. This warehouse is our <strong>ArrayBuffer</strong>.</p><p><strong>ArrayBuffer</strong> is a representation of <strong>generic</strong>, <strong>raw</strong> binary data. It's a fundamental data structure for working with binary data in JavaScript (the term "<em>byte array</em>" is used in some other languages for similar data structures). An <strong>ArrayBuffer</strong> object is a reference to a fixed length, contiguous memory allocated in the heap. The binary data is generic and raw because -</p><ul><li><p>Generic: It doesn't intrinsically have a particular data type. It just has a sequence of bytes and can store binary data corresponding to integers, unsigned integers, floats, strings etc.</p></li><li><p>Raw: The representation is similar to that in physical memory as there are no transformations internally</p></li></ul><h4><code>Array</code><strong>  vs  </strong><code>ArrayBuffer</code></h4><p><strong>ArrayBuffer</strong> is significantly different from <strong>Array</strong> in JavaScript (the <strong>Array.isArray()</strong> method returns false for buffers). It lacks the traditional <strong>.pop()</strong> and <strong>.push()</strong> methods because a buffer is initialised with a fixed length and is allocated a contiguous memory block which can't be pushed to or popped from - it can only be resized, but that too requires a deterministic max resize length on initialisation.</p><pre><code>// buffer of length 16, can hold 16 bytes
const buffer = new ArrayBuffer(16)

console.log(buffer.byteLength) // 16</code></pre><p>Traditional arrays can hold any combination of different types of objects in them - string and number elements can exist in the same array, which is why often you won't get contiguous blocks of memory, unlike <strong>ArrayBuffer</strong>, where you do. Because the compiler/interpreter is getting information on exactly how much memory is being allocated and exactly how it's going to be viewed, it can make optimisations while working on the buffer. When iterating through that data, the compiler doesn't have to make calculated leaps through memory. Instead, it knows&nbsp;<em>exactly</em>&nbsp;how far to move ahead in memory to find the next data point. This sort of memory allocation and movement makes <strong>ArrayBuffer</strong> quite fast and optimised compared to traditional arrays.</p><div><hr></div><h2><code>TypedArray</code></h2><p>Taking the previously drawn comparison further, if <strong>ArrayBuffer</strong> is the warehouse, then <strong>TypedArray</strong> are the series of carton boxes in which the building material is arranged and packed. These boxes could be of varying sizes - e.g. 100 boxes of 2 feet<sup>3</sup>, or 800 boxes of 1 feet<sup>3</sup> and so on - but always of the same size at a time. The same building material in the warehouse can thus be arranged and moved in a series boxes of the same sizes. The underlying material remains the same, it's just viewed differently, in packing boxes.</p><p>In short, the two types of <strong>TypedArray</strong> are the 1 feet and 2 feet boxes, and we have access to a series of only one kind of box at a time.</p><h4>Encoding and representation</h4><p>Coming back to JavaScript land, <strong>TypedArray</strong> is a set of Array-like objects that allow you to interpret the underlying array buffer and work with binary data in a specific format. They don't actually store any data, they're a "lens" to make sense of the raw binary data, and these lenses comes in various shapes and sizes -</p><ul><li><p><strong>Int8Array</strong> - 8 bit signed integers, where every byte is an integer.</p></li><li><p><strong>Uint8Array</strong> - 8 bit unsigned integers, where every byte is an integer.</p></li><li><p><strong>Int16Array</strong> - 16 bit signed integers, where every 2 bytes is an integer.</p></li><li><p><strong>Uint16Array</strong> - 16 bit unsigned integers, where every 2 bytes is an integer.</p></li><li><p><strong>Int32Array</strong> - 32 bit signed integers, where every 4 bytes is an integer.</p></li><li><p><strong>Uint32Array</strong> - 32 bit unsigned integers, where every 4 bytes is an integer.</p></li><li><p><strong>BigInt64Array</strong> - 64 bit signed integers, where every 8 bytes is an integer.</p></li><li><p><strong>BigUint64Array</strong> - 64 bit unsigned integers, where every 8 bytes is an integer.</p></li><li><p><strong>Float16Array</strong> - 16 bit floating point numbers, where every 2 bytes is a floating point representation.</p></li><li><p><strong>Float32Array</strong> - 32 bit floating point numbers, where every 4 bytes is a floating point representation.</p></li><li><p><strong>Float64Array</strong> - 64 bit floating point numbers, where every 8 bytes is a floating point representation.</p></li></ul><p>We needn't worry though, this isn't supposed to be memorised - let's understand what all this actually means. Consider <strong>Uint8Array</strong> and <strong>Int8Array</strong>:</p><ol><li><p><strong>Uint8Array</strong> - The U  stands for unsigned, so this representation will have non-negative values only. The 8 means that the numbers will be stored in 8 bits (1 byte) and in the binary number system that means the range is from 0 to 2<sup>8</sup> -1 or 0 to 255.</p></li><li><p><strong>Int8Array</strong> - This representation will have signed integers. The 8 means that the numbers will be stored in 8 bits (1 byte), but since this is a signed representation, the most significant bit (MSB) will be utilised in denoting a negative/non-negative value. Since one bit is used up, only 7 bits remain for the binary number system, which means the range is from -2<sup>7</sup> to 2<sup>7</sup> -1 or -128 to 127.</p><p></p></li></ol><p>Other integer arrays behave in the exact same manner with increasing bytes needed to store the representation. Floating point arrays arrange the numbers according to the IEEE floating point format:</p><ol><li><p><strong>Float16Array</strong> uses 10 bits for the mantissa, 5 bits for the exponent and one bit for sign.</p></li><li><p><strong>Float32Array</strong> uses 23 bits for mantissa, 8 bits for the exponent and one bit for sign, and so on.</p></li></ol><pre><code>// buffer of length 16 - holds 16 bytes
const buffer = new ArrayBuffer(16)

// unsigned int32 view over the buffer
const view = new Uint32Array(buffer)

console.log(Uint32Array.BYTES_PER_ELEMENT) // 4, each number takes up 4 bytes

console.log(view.length) // 4, count of numbers/elements
console.log(byteLength) // 16, size in bytes of the underlying buffer</code></pre><p>We can also create views over a section of the <strong>ArrayBuffer</strong> instead of the whole thing by optionally specifying an offset and a length. No offset means the view covers the buffer from the beginning, and no length means the view covers till the end of the buffer. </p><pre><code><code>const buffer = new ArrayBuffer(16)
const view = new Uint32Array(buffer, 4, 3)

// the offset is 4 - view starts from the 4th byte
// the length is 3 - length of the underlying buffer equivalent to 3 elements, which makes it (3 * Uint32Array.BYTES_PER_ELELEMT) bytes</code></code></pre><h4>Playing with binary numbers<br></h4><p>Let's try to play around with different <strong>TypedArray</strong> views with the same <strong>ArrayBuffer</strong> data underneath to see what different representations look like for the same binary data.</p><pre><code>const uint8View = new Uint8Array([250, 250, 250, 250, 250, 250, 250, 250])
const buffer = uint8View.buffer
console.log(buffer.byteLength) // this outputs 8, same as the Uint8Array length</code></pre><p>The above snippet automagically creates a buffer underneath filled with the raw binary data represented by the typed array. </p><pre><code>console.log(uint8View[0]) // this outputs 250
console.log(uint8View.length) // this outputs 8</code></pre><p>Here, each element (unsigned integer) of the view is 250, taking up 8 bits or a single byte. This view has 8 elements, 1 byte each.</p><pre><code>const uint16View = new Uint16Array(buffer)
console.log(uint16View[0]) // this outputs 64250
console.log(uint16View.length) // this outputs 4</code></pre><p>Here, each element (unsigned integer) of the view is expected to be 64250, taking up 16 bits or 2 bytes. This view has 4 elements, 2 bytes each.<br><br>To visualise how exactly this difference arises between the two representations over the same byte data, let's convert the base 10 numbers in both typed arrays to binary.</p><ul><li><p>250 = 11111010 (8 bits)</p></li><li><p>64250 = 1111101011111010 (16 bits)</p></li></ul><p>The <strong>Uint8Array</strong> treats elements as 8 bit numbers occupying 1 byte, and <strong>Uint16Array</strong> treats elements as 16 bit numbers occupying 2 bytes. So the arrays would look something like this in respective views (for visualisation purposes only):</p><ul><li><p><strong>uint8View = </strong>[ 11111010, 11111010, 11111010, 11111010, 11111010, 11111010, 11111010, 11111010 ]</p></li><li><p><strong>uint16View = </strong>[ 1111101011111010, 1111101011111010, 1111101011111010, 1111101011111010 ]</p></li></ul><p>The pattern seems obvious now. Appending the binary elements and their neighbours in the <strong>Uint8Array</strong> into a single 2 byte number yields us the element for <strong>Uint16Array</strong></p><p>11111010 ++ 11111010 = 1111101011111010 (appending binary numbers)</p><p>which, converted to base 10, yields the result 64250.</p><p>So we see how the same underlying buffer can be represented in multiple ways depending which type we're using to view the data. It could be a representation of many small integers or a fewer count of larger integers. Or it could be a floating point number representation entirely. There's a really nice illustration on MDN that puts this into perspective.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2wyk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8682795-8b8a-4a87-9966-32a0c0612afa_1270x558.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2wyk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8682795-8b8a-4a87-9966-32a0c0612afa_1270x558.png 424w, https://substackcdn.com/image/fetch/$s_!2wyk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8682795-8b8a-4a87-9966-32a0c0612afa_1270x558.png 848w, https://substackcdn.com/image/fetch/$s_!2wyk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8682795-8b8a-4a87-9966-32a0c0612afa_1270x558.png 1272w, https://substackcdn.com/image/fetch/$s_!2wyk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8682795-8b8a-4a87-9966-32a0c0612afa_1270x558.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2wyk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8682795-8b8a-4a87-9966-32a0c0612afa_1270x558.png" width="1270" height="558" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f8682795-8b8a-4a87-9966-32a0c0612afa_1270x558.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:558,&quot;width&quot;:1270,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:64432,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2wyk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8682795-8b8a-4a87-9966-32a0c0612afa_1270x558.png 424w, https://substackcdn.com/image/fetch/$s_!2wyk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8682795-8b8a-4a87-9966-32a0c0612afa_1270x558.png 848w, https://substackcdn.com/image/fetch/$s_!2wyk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8682795-8b8a-4a87-9966-32a0c0612afa_1270x558.png 1272w, https://substackcdn.com/image/fetch/$s_!2wyk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8682795-8b8a-4a87-9966-32a0c0612afa_1270x558.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4>Handling overflows</h4><p>What would happen if we try to write a value into the typed arrays that is larger than its supported range? Let's try to put 256 and 260 as values in the <strong>Uint8Array</strong> view, which are out of bounds values since the supported range is 0-255.</p><pre><code>const view = new Uint8Array(8)

const oobNum1 = 256
const oobNum2 = 260

console.log(oobNum1.toString(2)) /// this outputs 100000000, binary number
console.log(oobNum2.toString(2)) /// this outputs 100000100, binary number

view[0] = oobNum1
view[1] = oobNum2

console.log(view[0]) /// 0
console.log(view[1]) /// 4</code></pre><p>Nothing breaks, but extra bits seem to be cut off. For instance, the binary for 256 is 100000000 which is 9 bits but <strong>Uint8Array</strong> only allows for 8 bit per integer. For out of bounds numbers the less significant bits, the rightmost bits (this depends on the endianness of the system), are retained and the remaining are dropped. </p><p>So for 256, the 8 less significant bits are kept and the MSB is dropped, which leaves the value to be 00000000 which is 0. Similarly, the binary for 260 is 100000100. Again, the 8 less significant bits are kept and rest are dropped, so the remaining binary number is 00000100 which is 4.</p><p>Mathematically, the number's modulo 2<sup>8</sup> is preserved and the rest is dropped. We can think of this as the numbers wrapping around to the beginning of the range once they go beyond 255, and start from 0 again.</p><div><hr></div><h2><code>DataView</code></h2><p>The warehouse metaphor might be going a bit too far at this point, but we&#8217;ll roll with it. We can think of a <strong>DataView</strong> as us having access to a series of all different kinds of boxes at the same time. The material in the warehouse can be packaged on demand in boxes of any size at the same time. For instance, 50 boxes of 2 feet<sup>3</sup> and 400 boxes of 1 feet<sup>3</sup> at the same time to pack the warehouse material instead of having access to a sequence of just a single type of box size.</p><p><strong>DataView</strong> is a special type of "untyped" view over <strong>ArrayBuffer</strong> that gives more flexibility to work with binary data than a <strong>TypedArray</strong> by allowing data access at any offset, in any format, and in a specifiable byte order.</p><p>In case of <strong>TypedArray</strong> the constructor dictates the representation of the underlying binary data, whether its signed, unsigned, 8 bit, or 16 bit etc. <strong>DataView</strong> is more flexible and exposes methods that allow for choosing the representation format at runtime. e.g. <strong>getUint8()</strong> and <strong>getUint16()</strong>.</p><p><strong>DataView</strong> is great for use cases where the same buffer has mixed format data stored in it. This is a common occurrence in network programming where data passing in protocols happens in buffers where, for instance, n bytes in a buffer specifies the length of the entity for the next m bytes, and both n and m are read in different representations.</p><p>For instance:</p><pre><code>// this creates a buffer with the specified binary numbers
const buffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]).buffer

// syntax for initialising data view
const view = new DataView(buffer)

console.log(view.getUint32(0, true)) // 67305985 -&gt; 32-bit number, first 4 bytes
console.log(view.getUint8(4, true)) // 5 -&gt; 8-bit number, 1 byte</code></pre><p>Another way to do this with <strong>TypedArray</strong> instead:</p><pre><code>// this creates a buffer with the specified binary numbers
const buffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]).buffer

const uint32View = new Uint32Array(buffer, 0, 1)
console.log(uint32View) // this logs -&gt; Uint32Array(1) [ 67305985 ]

const uint8View = new Uint8Array(buffer, 4, 1)
console.log(uint8View) // this logs -&gt; Uint8Array(1) [ 5 ]</code></pre><p>This is of course a very simplistic example of binary data manipulation but the point here is that <strong>DataView</strong> has powerful methods exposed that allow accessing and manipulating binary data in different formats in a single view. This can be very useful while passing binary streams across systems and decoding them on the application side if the data is composed of different sections which hold information like content length and offsets about other sections in the same stream. Network protocols often use this methodology to encode information in packets and headers - we too use a similar strategy in our app to receive and decode binary information for image processing use cases.</p><h4>Endianness</h4><p>Endianness refers to the order in which a computer reads bytes. Like how English is read from left to right and Urdu is read from right to left, computers read bytes either from left to right or from right to left. The two most common orderings are -</p><ul><li><p>Little endian: This order stores the MSB (most significant bit) last. In other words, the bytes are stored in least-to-most significant order, with LSB occupying the smallest memory address.</p></li><li><p>Big endian: This order stores the MSB first. In other words, the bytes are stored in most-to-least significant order, with MSB occupying the smallest memory address.</p></li></ul><p>There was an example of this a few sections above where we discussed how the same binary data was expressed as a <strong>Uint8Array</strong> with 8 numbers being 250  vs a <strong>Uint16Array</strong> with 4 being 64250. In that example, we were representing 250 as 11111010 and 64250 as 1111101011111010 - this representation has the MSB first ("first" essentially means occupying the lowest address) while reading them, and such byte ordering is big endian. Conversely, if we were to represent 250 as 01011111 and 64250 as 0101111101011111, the byte ordering would have been little endian since the MSB is read and stored last ("last" essentially means occupying the highest address).</p><p>The ordering here doesn't affect the value of the element itself in any way, it just affects the order of storing and reading the byte data by the processor from the registers. So for all intents and purposes, the binary output that you will get in your system after binary manipulation will be unchanged, but the ordering of bits and their arrangement in registers will differ for different endianness.</p><p>The systems that we use on a daily basis to write programs are mostly little endian. x86 architecture is little endian, ARM architecture supports both orderings but is predominantly used as and defaults to little endian. Big endian ordering is mostly used in network protocols - TCP is big endian according to its spec.</p><p>Byte ordering doesn't concern us much as long as the application is dealing with binary data within the system - every system is a black box that is self sufficient in handling its respective endianness. Byte ordering starts mattering when systems with different endianness start communicating or when systems have to deal with bytes coming over the network. <strong>DataView</strong> gives us a convenient way to control the endianness while reading/writing bytes to a buffer. <strong>TypedArray</strong> views are in the native byte order always but <strong>DataView</strong> getter/setters allow us to change that. In the examples above -</p><pre><code>console.log(view.getUint8(4, true))</code></pre><p>The true parameter tells the <strong>DataView</strong> to read the data in little endian order, otherwise it defaults to big endian.</p><div><hr></div><h2>Conclusion</h2><p>This article was an attempt at explaining and differentiating between different constructs that JavaScript exposes to allow us to read and manipulate binary data efficiently (albeit "efficiently" is a loaded word when dealing with JavaScript) for practical use cases in applications dealing with image processing, graphics, networks, communicating data to the GPU and many more. </p><p>Thanks for tuning in!</p><div><hr></div><h2>Bonus read</h2><p>There's a great article on how the v8 team at Google improved the underlying performance of <strong>DataView</strong> to match with <strong>TypedArray</strong>. While these abstractions are provided by JavaScript, we often take for granted the work that goes into making these performant in JS engines, and  by extension our browsers and Node environments. <br>Give it a read! <a href="https://v8.dev/blog/dataview">https://v8.dev/blog/dataview</a></p>]]></content:encoded></item><item><title><![CDATA[Compressing Random Forest Models with over 90% Reduction in Size]]></title><description><![CDATA[With the latest advancements in the field of AI, new techniques and algorithms like transformers, GANs, etc.]]></description><link>https://aftershoot.substack.com/p/compressing-random-forest-models</link><guid isPermaLink="false">https://aftershoot.substack.com/p/compressing-random-forest-models</guid><dc:creator><![CDATA[Karan]]></dc:creator><pubDate>Fri, 03 Jan 2025 09:23:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!R2Nt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53fde64-aa56-405d-8944-a225f7ea556c_700x873.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>With the latest advancements in the field of AI, new techniques and algorithms like transformers, GANs, etc. have emerged stealing the limelight from traditional techniques(knock knock GenZ incoming &#172;&#8255;&#172;). While there have been significant advancements with these algorithms in specific domains that provide state-of-the-art performance, let&#8217;s not overlook traditional algorithms' enduring power and reliability. They may seem rusty, but their proven track record and versatility continue to make them invaluable in various domains.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!R2Nt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53fde64-aa56-405d-8944-a225f7ea556c_700x873.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!R2Nt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53fde64-aa56-405d-8944-a225f7ea556c_700x873.jpeg 424w, https://substackcdn.com/image/fetch/$s_!R2Nt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53fde64-aa56-405d-8944-a225f7ea556c_700x873.jpeg 848w, https://substackcdn.com/image/fetch/$s_!R2Nt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53fde64-aa56-405d-8944-a225f7ea556c_700x873.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!R2Nt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53fde64-aa56-405d-8944-a225f7ea556c_700x873.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!R2Nt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53fde64-aa56-405d-8944-a225f7ea556c_700x873.jpeg" width="700" height="873" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c53fde64-aa56-405d-8944-a225f7ea556c_700x873.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:873,&quot;width&quot;:700,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!R2Nt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53fde64-aa56-405d-8944-a225f7ea556c_700x873.jpeg 424w, https://substackcdn.com/image/fetch/$s_!R2Nt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53fde64-aa56-405d-8944-a225f7ea556c_700x873.jpeg 848w, https://substackcdn.com/image/fetch/$s_!R2Nt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53fde64-aa56-405d-8944-a225f7ea556c_700x873.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!R2Nt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc53fde64-aa56-405d-8944-a225f7ea556c_700x873.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Random Forest Models, which may seem like your grandfather&#8217;s futile old stick(&#12484;) is still a popular technique in the realm of ensemble learning that offers a compelling edge over typical artificial neural networks (ANNs) when it comes to handling numerical data. Despite their simplicity and intuitive nature, random forests are beneficial in several areas. Their robustness to overfitting ensures better predictions, especially in noisy or complex datasets. Additionally, their ability to capture non-linear relationships within the data, makes them versatile and effective in a wide range of real-world applications.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://aftershoot.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Techstack by Aftershoot! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>However, these models can sometimes be costly in terms of storage. As the dimensionality of the input features grows, the size of the model can significantly increase, occasionally reaching gigabytes just to model a couple of thousand entries of the provided input features and sadly there are no intuitive strategies to compress or quantize these models ;-;</p><p>Therefore, to relieve such a stressful situation, I&#8217;ve formulated a novel solution to compress such models, slashing their size by over 90% while still preserving the original model&#8217;s accuracy. The approach is pretty intuitive and straightforward and works with all kinds of Random Forest models(regression, classification, multi-input/output, etc.).</p><p>As Random Forest models are binary trees in nature and can be stored as a dictionary in Python if you&#8217;re brave enough(&#8226;&#768;&#7447;&#8226;&#769; ), so we start by extracting the tree&#8217;s content(features, nodes, thresholds, values). We can then reduce the decimal precision of the tree's thresholds and leaf node values to trim the non-required trailing decimal digits.</p><pre><code>from sklearn.tree import _tree
import numpy as np
import json
import gzip
from joblib import load

def extract_tree_structure(decision_tree, feature_names, precision=4):
    """
    Extracts and returns the structure of a decision tree in a recursive dictionary format.
    
    This function traverses a decision tree and extracts information about each node, including
    the feature used for splitting (if any), the threshold for splitting, and the values at the leaf nodes.
    The structure is returned as a nested dictionary, where decision nodes contain information about
    the feature name ('n'), threshold ('t'), and recursive structures for the left ('l') and right ('r') branches.
    Leaf nodes contain the values ('v'). All numeric values are rounded to the specified precision.

    Parameters:
    - decision_tree (DecisionTreeClassifier or DecisionTreeRegressor): A trained decision tree.
    - feature_names (list of str): A list containing the names of the features used in the decision tree.
    - precision (int, optional): The number of decimal places to round numeric values to. Default is 4.

    Returns:
    - dict: A nested dictionary representing the structure of the decision tree.
    """
    tree_ = decision_tree.tree_
    feature_name = [
        feature_names[i] if i != _tree.TREE_UNDEFINED else "undefined!"
        for i in tree_.feature
    ]
    
    thresholds = np.around(tree_.threshold, decimals=precision)
    values = np.around(tree_.value, decimals=precision)

    def recurse(node, depth):
        if tree_.feature[node] != _tree.TREE_UNDEFINED:
            name = feature_name[node]
            threshold = thresholds[node]
            left = recurse(tree_.children_left[node], depth + 1)
            right = recurse(tree_.children_right[node], depth + 1)
            return {'n': name, 't': np.around(threshold, decimals=precision), 'l': left, 'r': right}
        else:
            temp = np.around(values[node], decimals=precision)
            return {'v': temp.tolist()}

    return recurse(0, 1)</code></pre><pre><code>model_path = "path/to/random/forest/model.joblib"
model = load(model_path)
feature_names = [i for i in range(model.n_features_in_)]
model_dict = [extract_tree_structure(estimator, feature_names, precision=precision) for estimator in model.estimators_]</code></pre><p>The resulting dictionary can then be saved in JSON format and this can be further zipped with best-in-class algorithms.</p><pre><code>with open("some/path.json", 'w') as f:
        json.dump(model_dict, f)

with open("some/path.json", 'r') as f:
    json_data = f.read()

with gzip.open("some/path.json.gz", 'wt', encoding='UTF-8') as zipfile:
    zipfile.write(json_data)</code></pre><p>For obtaining the predictions from the JSON a.k.a the Random Forest Model, we can utilize a simple recursive tree traversal logic &#3589;/&#5152;. &#811; .&#5151;\&#3589;</p><p>&#9598;&#9473;&#9572;&#12487;&#9574;&#65083;<br>My most civil threat for you is to head over to the GitHub Repo and enjoy the entire code and hands-on comparison of the original and JSON model outputs.<br><a href="https://github.com/K-prog/Compressing-Random-Forest-Models">https://github.com/K-prog/Compressing-Random-Forest-Models</a></p><p>Curious to know how it works&#8230;</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eOwD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9017f51c-815a-4711-83c1-ee0f6ef792a9_700x202.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eOwD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9017f51c-815a-4711-83c1-ee0f6ef792a9_700x202.png 424w, https://substackcdn.com/image/fetch/$s_!eOwD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9017f51c-815a-4711-83c1-ee0f6ef792a9_700x202.png 848w, https://substackcdn.com/image/fetch/$s_!eOwD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9017f51c-815a-4711-83c1-ee0f6ef792a9_700x202.png 1272w, https://substackcdn.com/image/fetch/$s_!eOwD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9017f51c-815a-4711-83c1-ee0f6ef792a9_700x202.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eOwD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9017f51c-815a-4711-83c1-ee0f6ef792a9_700x202.png" width="700" height="202" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9017f51c-815a-4711-83c1-ee0f6ef792a9_700x202.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:202,&quot;width&quot;:700,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!eOwD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9017f51c-815a-4711-83c1-ee0f6ef792a9_700x202.png 424w, https://substackcdn.com/image/fetch/$s_!eOwD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9017f51c-815a-4711-83c1-ee0f6ef792a9_700x202.png 848w, https://substackcdn.com/image/fetch/$s_!eOwD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9017f51c-815a-4711-83c1-ee0f6ef792a9_700x202.png 1272w, https://substackcdn.com/image/fetch/$s_!eOwD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9017f51c-815a-4711-83c1-ee0f6ef792a9_700x202.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">For a single output regression model with precision set to 2</figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3_gH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a74cb-831a-4d5e-b70f-1004927c92e9_700x283.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3_gH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a74cb-831a-4d5e-b70f-1004927c92e9_700x283.png 424w, https://substackcdn.com/image/fetch/$s_!3_gH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a74cb-831a-4d5e-b70f-1004927c92e9_700x283.png 848w, https://substackcdn.com/image/fetch/$s_!3_gH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a74cb-831a-4d5e-b70f-1004927c92e9_700x283.png 1272w, https://substackcdn.com/image/fetch/$s_!3_gH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a74cb-831a-4d5e-b70f-1004927c92e9_700x283.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3_gH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a74cb-831a-4d5e-b70f-1004927c92e9_700x283.png" width="700" height="283" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/306a74cb-831a-4d5e-b70f-1004927c92e9_700x283.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:283,&quot;width&quot;:700,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!3_gH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a74cb-831a-4d5e-b70f-1004927c92e9_700x283.png 424w, https://substackcdn.com/image/fetch/$s_!3_gH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a74cb-831a-4d5e-b70f-1004927c92e9_700x283.png 848w, https://substackcdn.com/image/fetch/$s_!3_gH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a74cb-831a-4d5e-b70f-1004927c92e9_700x283.png 1272w, https://substackcdn.com/image/fetch/$s_!3_gH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a74cb-831a-4d5e-b70f-1004927c92e9_700x283.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">For multiple output regression model with precision set to 1</figcaption></figure></div><p>Thanks for joining in on this journey of pounding Random Forests (^o^)&#12494;<br></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://aftershoot.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Techstack by Aftershoot! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Welcome to Techstack by Aftershoot]]></title><description><![CDATA[Make yourself comfortable!]]></description><link>https://aftershoot.substack.com/p/coming-soon</link><guid isPermaLink="false">https://aftershoot.substack.com/p/coming-soon</guid><dc:creator><![CDATA[Harshit Dwivedi]]></dc:creator><pubDate>Sun, 08 Sep 2024 11:47:23 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/180d1d20-e70f-49cc-8da9-369724645dd9_3012x2789.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://aftershoot.com/">Aftershoot</a> is an Ai powered Image Selection and Editing application which is used by Photographers to cut down the time they spend on post-production after the shoot. </p><p>Our desktop app (yes!) is built on a lot of bleeding edge technologies (including Ai compute on the edge) and we figured this blog would serve as a place to share our learnings of building Aftershoot with the world. </p><p>Future blogs on this substack will include things like: </p><ul><li><p>Creating high performant client-server interactions on the edge</p></li><li><p>Utilising local compute (CPU and GPU) for running ML models</p></li><li><p>Shipping and maintaining a cross platform desktop app </p></li><li><p>and many more</p></li></ul><p>If you&#8217;ve made it this far (of this short blog), do subscribe to be notified about our next post as soon as its live! &#128075;&#127996;</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://aftershoot.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://aftershoot.substack.com/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item></channel></rss>