<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Fenils Blog</title><description>What does it mean to be strong Takamura-san?</description><link>https://fknil.com/</link><item><title>Weeknote 25 2026</title><link>https://fknil.com/weeknote/2026-week-25/</link><guid isPermaLink="true">https://fknil.com/weeknote/2026-week-25/</guid><pubDate>Mon, 22 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Tech&lt;/h2&gt;
&lt;p&gt;This week feels like a super major one. My decision to leave tech forever got vetoed, and for some reason I feel super relieved. I can, for the near future, throw out my worries about AI, and focus back on my work plus study of computer science. I am fully motivated to start a project, it has been a while since I last built one end-to-end. Last completed one was &lt;a href=&quot;https://github.com/feniljain/brenphuk&quot;&gt;JIT interpreter for brainfuck language&lt;/a&gt;. Maybe working on a join operator, just join operator with arrow batches as vector format and benchmarking it against datafusion&apos;s implementation would be an interesting thing to do. I can start with a basic nested loop join implementation, move to hash join, adding partitioning and spilling and vectorization as we move forward. AHHH this excites me so much!&lt;/p&gt;
&lt;p&gt;I also finally got around to integrating &lt;a href=&quot;https://codeberg.org/susam/wander&quot;&gt;wander console&lt;/a&gt; in &lt;a href=&quot;https://fknil.com/wander/&quot;&gt;my website&lt;/a&gt;. It is available as a top level link in my header. I visit it from time to time to find interesting pages from people. If you are reading this note, give wander console a try, it really is something which can be cherised by IndieWeb peeps!&lt;/p&gt;
&lt;p&gt;Also, if you are actually integrating it, do take care of wander.js file&apos;s &lt;code&gt;Content-type&lt;/code&gt;, I had messed it up but Susam helpfully &lt;a href=&quot;https://hachyderm.io/@susam@mastodon.social/116777757231268292&quot;&gt;pointed it&lt;/a&gt; out which I &lt;a href=&quot;https://github.com/feniljain/fknil/pull/11&quot;&gt;promptly fixed&lt;/a&gt; with some help from &lt;a href=&quot;https://yashgarg.dev/&quot;&gt;Yash&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Non tech&lt;/h2&gt;
&lt;p&gt;This week me and &lt;a href=&quot;https://wantguns.dev/&quot;&gt;Gunwant&lt;/a&gt;, went to &lt;a href=&quot;https://share.google/n6PKxmAbs3yTQr4WP&quot;&gt;Pizza 4Ps&lt;/a&gt;. This time we ordered Garlic bread as starters and for the main pizza, half was the tried and tested &lt;code&gt;Burrata Salad&lt;/code&gt; Pizza and other half was feeling lucky &lt;code&gt;Honey Chilli&lt;/code&gt; pizza. Well I can say for sure I didn&apos;t feel lucky after having the second half :(&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://fknil.com/rss-images/weeknotes/2026/25/garlic-bread.jpeg&quot;&gt;Image: Garlic Bread Sticks&lt;/a&gt;
&lt;a href=&quot;https://fknil.com/rss-images/weeknotes/2026/25/pizza.jpeg&quot;&gt;Image: Burrata and Honey Chilli Pizza&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;After that me, &lt;a href=&quot;https://wantguns.dev/&quot;&gt;Gunwant&lt;/a&gt;, &lt;a href=&quot;https://msfjarvis.dev/&quot;&gt;Harsh&lt;/a&gt; and &lt;a href=&quot;https://yashgarg.dev/&quot;&gt;Yash&lt;/a&gt; went to watch &lt;code&gt;Backrooms&lt;/code&gt;. Having watched the original &lt;a href=&quot;https://youtube.com/playlist?list=PLVAh-MgDVqvDUEq6qDXqORBioE4Yhol_z&amp;amp;si=0QBiL9Ksw77x7cBl&quot;&gt;backrooms series&lt;/a&gt; on youtube and falling in love with it at the very second, I was pretty stoked when it got picked up by &lt;code&gt;a24&lt;/code&gt; , a studio I have grown to absolutely love. They also recruited Kane to make the movie, he finally could use real people and real set instead of blender-ing it all out. Suffice to say, it is a must watch if you are fan of horror genre or backrooms in general.&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://fknil.com/rss-images/weeknotes/2026/25/backrooms.jpeg&quot;&gt;Image: Backrooms movie title screen&lt;/a&gt;
&lt;/p&gt;
&lt;h2&gt;Sports&lt;/h2&gt;
&lt;p&gt;My climbing friends are discussing a Hampi trip. AND I LOVE HAMPI! My last experience there in the end of previous year was so good, I could&apos;t think about missing it this time! And hence I ditched my &quot;dont climb, get strong&quot; plans and finally went to the climbing gym this Thursday. It was still going to be a test of how I feel when using my right foot, jumping on different holds or from high above. Luckily all felt good, so this means I am going to continue climbing and training my fingers for the Hampi trip next week! Enjoy video of a climb I completed off-camera, but couldn&apos;t repeat on-camera 😭&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://www.youtube.com/shorts/1NZMz0H1obs?mute=1&quot;&gt;https://www.youtube.com/shorts/1NZMz0H1obs?mute=1&lt;/a&gt;
&lt;/p&gt;
&lt;h2&gt;Food explore&lt;/h2&gt;
&lt;p&gt;I tried out Bun Maska at two new places:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://maps.app.goo.gl/jn2oRimMFwfUtgfMA&quot;&gt;Pure Coffee&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://maps.app.goo.gl/us47jiw6qLKUT5mK7&quot;&gt;BrewMash&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For me a good Bun Maska is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Super soft bun&lt;/li&gt;
&lt;li&gt;Proper maska, not just butter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pure Coffee has the best Bun Maska near Jayanagar metro station at the very least!&lt;/p&gt;
&lt;p&gt;As for Poha exploration, this time I tried &lt;a href=&quot;https://maps.app.goo.gl/6EXLdg8KWzBDdKQW6&quot;&gt;Maharaja Wada&lt;/a&gt;, it was 4/5. Not perfect, but good enough.&lt;/p&gt;
&lt;h2&gt;Interesting links from around internet&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://www.jvm-weekly.com/p/project-valhalla-explained-how-a&lt;/li&gt;
&lt;li&gt;https://www.linkedin.com/posts/andygrove_nice-to-see-aws-labs-publish-a-comparison-share-7472505161546911745-ksQ9/&lt;/li&gt;
&lt;li&gt;https://medium.com/@kimth0312/computer-architecture-block-i-o-optimization-18a3f64458e1&lt;/li&gt;
&lt;li&gt;https://xkcd.com/3261/&lt;/li&gt;
&lt;li&gt;https://xkcd.com/3260/&lt;/li&gt;
&lt;li&gt;https://thedailywtf.com/articles/required-fields&lt;/li&gt;
&lt;li&gt;https://thedailywtf.com/articles/microbits&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Minimal CSS for everything</title><link>https://fknil.com/blog/minimal-css-for-everything/</link><guid isPermaLink="true">https://fknil.com/blog/minimal-css-for-everything/</guid><pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today I am going to talk about minimal CSS to make responsive websites. This is the best time to write this down cause I have built &lt;a href=&quot;fknil.com&quot;&gt;my website&lt;/a&gt; recently, and I fought through a jungle of different loosely connected pieces of ideas I had in my brain to make this website responsive and with no horizontal scroll. Please please remove horizontal scrolls from your websites, it is not that hard. Personally if I come across one, it drives me nuts. And this is not just noobs writing CSS who introduce them, I just saw it yesterday on Github compare page!! A multi-million dollar company, with all the resources in the world, and they can introduce such bugs. Its still present as of writing this blog i.e. 10-06-2026-06-09 in DD-MM-YYYY, check it out &lt;a href=&quot;https://github.com/apache/datafusion/compare/main...feniljain:datafusion:feat-offset-pushdown&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I was doing a lot of web-dev during my college days. Being part of a super active club, we used to conduct a lot of events! And each one required at least a website, with android/iOS app being optional. We were always in build mode, one event goes, another one is knocking on the door. You would think with all this pressure, why not use something like Wordpress? Well there are two reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We had some of the best designers, they have wild imaginations and pushed us to our limits always&lt;/li&gt;
&lt;li&gt;We were students and we wanted to learn!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was pre-LLM era, so all we did was &lt;a href=&quot;https://www.youtube.com/watch?v=AOPZuXh-f5M&quot;&gt;Fuck Around Find Out&lt;/a&gt;. Ahh the golden days. But due to all this grinding, we had become super efficient at base responsiveness of any page. For each project, we would bang out responsive base layouts pretty fast, hard part was things like rendering a 3D globe (well this was a library so we escaped), or making CSS animations with canvas, etc. I was involved in almost all the projects either as a frontend guy or a backend guy, sometimes both :P&lt;/p&gt;
&lt;p&gt;Around when I took over most of the development, something flipped in development practices of our team. I had pushed a rule of not using any CSS frameworks. I had recently learnt about flexbox, and it felt magical! I wondered to myself: What have I been doing till this day. Trying to use loads of media queries, z-index, float, display and what not to make the website just barely responsive. Responsiveness was just a weird screen away from breaking. As soon as I learnt about it, and built enough intuition I realized: all these frameworks were helping with was hiding skill issue. There were zero reasons, for a complete learning project to use any frameworks. So they were banned, but this meant literally hand-rolling everything in the whole website, now this could be a CTF website or recruitment portal, anything which the next event and design curiosity led to.&lt;/p&gt;
&lt;p&gt;The reason I am talking about this today is I recently read an interesting article from &lt;a href=&quot;https://matklad.github.io/2026/06/04/css-unavoidable-bad-parts.html&quot;&gt;Matklad&lt;/a&gt;. While I follow his blog posts for systems engineering/low level/PL design, etc, this one stuck to me. No-one wants to learn oddities of CSS, this is especially true for people who want to control everything about their website but not be bothered with too much framework complexity, I am still that guy! So following his lead, what are my rules which I follow and want to remember to make this happen?&lt;/p&gt;
&lt;p&gt;With this motivation, I am going to give you a recipe which has worked for me over the years! Lets get started:&lt;/p&gt;
&lt;h2&gt;Box Sizing&lt;/h2&gt;
&lt;p&gt;First set:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;* { box-sizing: border-box; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make all the elements use &lt;code&gt;border-box&lt;/code&gt;. By default CSS does not include padding and border as part of the height and width calculation of the element, it just feels unintuitive to me, as well as to Matklad too xD&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/box-sizing&quot;&gt;MDN link&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Flexbox&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;display: flex;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anywhere where you want to arrange multiple elements in a way where they can wrap, you want them to be strictly stacked, strictly next to each other, strict space between or any such scenarios, use flexbox. I am not doing a proper justice to how important and life changing flexbox is. So, for this one thing, do sit and read full &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/CSS_layout/Flexbox&quot;&gt;MDN reference&lt;/a&gt;. It is really good! And you would definitely forget it, so next time you will have to come and see this again for few things xD&lt;/p&gt;
&lt;p&gt;Next is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;justify-content: center;
align-items: center;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How to center a child div? SOLVED!&lt;/p&gt;
&lt;p&gt;I am not even kidding, this is it. And its not just this, &lt;code&gt;justify-content&lt;/code&gt; has other values like &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;end&lt;/code&gt;, &lt;code&gt;space-between&lt;/code&gt;, &lt;code&gt;space-around&lt;/code&gt; and &lt;code&gt;space-evenly&lt;/code&gt;. Again &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/justify-content&quot;&gt;MDN page&lt;/a&gt; gives a good interactive example for these properties.&lt;/p&gt;
&lt;p&gt;and same for &lt;code&gt;align-items&lt;/code&gt;: &lt;code&gt;stretch&lt;/code&gt;/&lt;code&gt;center&lt;/code&gt;/&lt;code&gt;start&lt;/code&gt;/&lt;code&gt;end&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There is one question to ask though, why two properties? why not just one? Well for that we will have to understand flexbox a bit and this explanation will also double down as our introduction to next two flexbox properties. Again, I am only going to touch just enough to give an idea, for detailed documentation do refer to its official &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Flexible_box_layout/Basic_concepts&quot;&gt;MDN page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Flexbox has two directions in which it tries to reason about its child element. One is called the &lt;code&gt;main axis&lt;/code&gt; and other is called the &lt;code&gt;cross axis&lt;/code&gt;. By default, &lt;code&gt;main axis&lt;/code&gt; is horizontal and &lt;code&gt;cross axis&lt;/code&gt; is vertical. So, if you say:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;justify-content: start;
align-items: start;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can expect all children to be in the left hand side upper corner of your parent element. Or, if you set:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;justify-content: end;
align-items: end;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;they will be in the right hand side lower corner. There are loads of different combinations. You should personally play with these combinations to get the best feel! Once you understand these, it will start to feel like superpower :)&lt;/p&gt;
&lt;p&gt;There is one catch though, values of main and cross axis can change according to &lt;code&gt;flex-direction&lt;/code&gt;, which can be &lt;code&gt;row&lt;/code&gt;, &lt;code&gt;row-reverse&lt;/code&gt;, &lt;code&gt;column&lt;/code&gt; or &lt;code&gt;column-reverse&lt;/code&gt;. Main axis in each case is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;row&lt;/code&gt;: left-to-right&lt;/li&gt;
&lt;li&gt;&lt;code&gt;row-reverse&lt;/code&gt;: right-to-left&lt;/li&gt;
&lt;li&gt;&lt;code&gt;column&lt;/code&gt;: top-to-bottom&lt;/li&gt;
&lt;li&gt;&lt;code&gt;column-reverse&lt;/code&gt;: bottom-to-top&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One can easily infer about cross-axis. And finally there&apos;s &lt;code&gt;flex-grow&lt;/code&gt;. This is used to tell an element to take a stretch factor in free space. A simple example to understand it would be:&lt;/p&gt;
&lt;p&gt;Imagine there&apos;s a website with body set as 100% of screen space. At the top there&apos;s a header and at the bottom there&apos;s a footer, in the middle you have dynamically sized content. Header is of height 10% and footer, 5%. Now we can set &lt;code&gt;flex-grow&lt;/code&gt; on middle element to &lt;code&gt;1&lt;/code&gt;, which would make it automatically occupy all the remaining space between them.&lt;/p&gt;
&lt;p&gt;In this example one could compute the height of middle element as 85%, but think of multiple elements of varying size, some fixed, some dynamic, all of them interacting with each other and they are dynamically inserted in any order, having properties like &lt;code&gt;flex-grow&lt;/code&gt;, helps in that case!&lt;/p&gt;
&lt;p&gt;There are other properties on flexbox, but I don&apos;t remember now if I used any regularly in college, at least I didn&apos;t need any in my current website.&lt;/p&gt;
&lt;h2&gt;Single media query&lt;/h2&gt;
&lt;p&gt;Just use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@media (max-width: 480px) { /* all needed CSS */ }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;for any mobile phone specific stuff. Important to note point will be: it is only to be used for things like: image size is 30% on desktops, but on phone it needs to be 50%. DO NOT use it to get responsiveness. Flexbox should have everything you need to achieve that!&lt;/p&gt;
&lt;h2&gt;Margin on body&lt;/h2&gt;
&lt;p&gt;For some reason Firefox (not sure about other browsers), had a default margin of 8?? This caused so much confusion to me :(&lt;/p&gt;
&lt;p&gt;Setting it zero removed horizontal scroll and overflow I was debugging xD&lt;/p&gt;
&lt;p&gt;By default just remove it on &lt;code&gt;html&lt;/code&gt; and &lt;code&gt;body&lt;/code&gt; tag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;html, body {
    /* by default it has 8 margin 🤦 */
    margin: 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From Matklads blogpost I also learnt about other such non-intuitive things browsers set and hence every website should perform a so called &quot;CSS Reset&quot;, basically a small set of sane properties to keep at the the top of project and start building from that base. One linked by Matklad was &lt;a href=&quot;https://www.joshwcomeau.com/css/custom-css-reset/&quot;&gt;this&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Percent over pixels for margin and padding&lt;/h2&gt;
&lt;p&gt;Percentage scales better for different screen sizes by default, so stop hard-coding pixels! Btw these pixels do not map to actual pixels of your screen. Its just a logical construct which has a mapping decided by browser to the actual physical pixels!&lt;/p&gt;
&lt;p&gt;There could be a case where margin and padding looks off on different screen resolutions. I usually keep two sets, one for mobile and one for any other screens.&lt;/p&gt;
&lt;h2&gt;Rem over pixels for font size&lt;/h2&gt;
&lt;p&gt;Again don&apos;t hard code using pixels, just use rem. While its intuitive, a &lt;a href=&quot;https://matklad.github.io/2022/11/05/accessibility-px-or-rem.html&quot;&gt;good read&lt;/a&gt; on the same was linked in Matklads post.&lt;/p&gt;
&lt;h2&gt;CSS variables for light and dark mode&lt;/h2&gt;
&lt;p&gt;Use global CSS variables for setting light and dark mode colors and use them with system preferences like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:root {
    --bg: #fafafa;
    --fg: #212121;
    --muted: #5a5a5a;
    --logo-backdrop: #1565c0;
    --alt-bg: #e0e0e0;
    --border: #d0d0d0;
    --text-color: #212121;
}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: #212121;
        --fg: #dadada;
        --muted: #a0a0a0;
        --logo-backdrop: #42a5f5;
        --alt-bg: #424242;
        --border: #3a3a3a;
        --text-color: #dadada;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would change theme of the website according to system preferences by default. You can also force it using a button on your website :)&lt;/p&gt;
&lt;h2&gt;Don&apos;t forget LVHA&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Love/Hate&lt;/code&gt; relationship of CSS. Its basically a rule covering how CSS loads properties on anchor tag in a particular order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:hover&lt;/code&gt; must come after &lt;code&gt;:link&lt;/code&gt; and &lt;code&gt;:visited&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:active&lt;/code&gt; must come after &lt;code&gt;:hover&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you does not follow these, you might see properties being overridden randomly. So always remember these!&lt;/p&gt;
&lt;h2&gt;Semantic tags&lt;/h2&gt;
&lt;p&gt;Try to use HTML semantic tags like &lt;code&gt;&amp;lt;em&amp;gt;&lt;/code&gt; for &lt;code&gt;italic&lt;/code&gt;, &lt;code&gt;&amp;lt;strong&amp;gt;&lt;/code&gt; for &lt;code&gt;bold&lt;/code&gt; , etc. These are for text decoration, but there are tags for loads of other things like &lt;code&gt;lists&lt;/code&gt;: &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt;, navigation lists etc. There are a ton of semantic tags baked into HTML, try to make use of them as much as possible and leave the defaults as they are!&lt;/p&gt;
&lt;h2&gt;Divs&lt;/h2&gt;
&lt;p&gt;And finally, use divs liberally when trying to make sense of some responsive layout, I usually find giving borders of different colors to different divs a pretty good way to debug CSS issues. Now, I know we have a very powerful &lt;code&gt;Inspector&lt;/code&gt;, but you know to debug two very different elements together in a complex hierarchical top-down dependent system (flexbox all the way down), this trick is useful xD&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And that&apos;s it, a good example of all these tips in use is my website&apos;s &lt;a href=&quot;https://github.com/feniljain/fknil&quot;&gt;codebase&lt;/a&gt;. Not saying, these are the golden rules which are absolute perfect, there could be things wrong with this. Accessibility comes to mind as the major footgun when discussing webdev. So, would love to hear about things which can go wrong or are incorrect! Thanks for reading and until next time, ciao!&lt;/p&gt;
</content:encoded></item><item><title>My new small space in the vast web</title><link>https://fknil.com/blog/new-website-2026/</link><guid isPermaLink="true">https://fknil.com/blog/new-website-2026/</guid><pubDate>Sun, 24 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;I have been pilled by &quot;write more&quot; propaganda, and its not something new, I have written &lt;a href=&quot;https://fknil.com/blog/writing-more-2024/&quot;&gt;about it before&lt;/a&gt; but never actually made progress on it. Something has changed this year, I utilized my new year to go through the book of &lt;code&gt;Accidental Genius&lt;/code&gt;, it talks about freewriting, basically a concept about just typing out words till the editor in your brain takes a step back and you can get your original ideas out, a process of self discovery you can say. Well this is one benefit, mostly importantly I really liked writing as a means to gain clarity. This could work for anything life, work, problem solving, etc. To be frank, I hadn&apos;t written much after reading the book in January, but you know there are few things which grow over you? I think the idea has grown over me. So now, I just bang out lines, edit it once maybe and hit &lt;code&gt;push&lt;/code&gt;. I even migrated to using markdown files for this, lower the friction to actually publish a blog post, the better.&lt;/p&gt;
&lt;p&gt;This is one part of the equation, second is, I have grown to start liking &lt;a href=&quot;https://indieweb.org/&quot;&gt;IndieWeb&lt;/a&gt; concept. I first discovered the concept through &lt;a href=&quot;https://codeberg.org/susam/wander&quot;&gt;Susam Pal&apos;s Wander instances&lt;/a&gt;. When I first visited it, I felt like my child like curiosity came back again, it was so wonderful to visit people&apos;s websites, read what they have been up to in life/tech, etc. I wanted a little space of my own too, not as fancy as some of those neocities pages, but just my own little simple space where I can host my own interests.&lt;/p&gt;
&lt;p&gt;Thirdly, I have finally been able to get my RSS reader set up! I use &lt;a href=&quot;https://github.com/newsboat/newsboat&quot;&gt;newsboat&lt;/a&gt;, its simple and it works! This time I made sure to very slowly add URLs to my feed, and also do regular scrutiny of which feed I want to keep. This has allowed to sustain a habit of actually opening RSS reader everyday and looking forward to new content 😁. Btw one of the key to achieving this was also having a rather regular cadence feed, for me that is &lt;a href=&quot;https://thedailywtf.com/&quot;&gt;DailyWTF&lt;/a&gt; and &lt;a href=&quot;https://xkcd.com/&quot;&gt;xkcd&lt;/a&gt;. Its so fun to read them! With all this working out, of course I want a RSS feed of my own writing and we need a hosted space for that to happen, so why not build one!?&lt;/p&gt;
&lt;h2&gt;Execution&lt;/h2&gt;
&lt;p&gt;Now lets talk about details, how I achieved it, what I liked about the way I took and what I didn&apos;t.&lt;/p&gt;
&lt;h3&gt;Requirements&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Keep writing blog posts in markdown files&lt;/li&gt;
&lt;li&gt;Simple framework without any client-side javascript&lt;/li&gt;
&lt;li&gt;Still able to use &lt;code&gt;npm&lt;/code&gt; packages, basically easy to leverage community work&lt;/li&gt;
&lt;li&gt;Keep draft blogs private if possible&lt;/li&gt;
&lt;li&gt;Easy to deploy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I dont want to increase friction of me writing, a simple markdown file, edit in obsidian and that should do it!&lt;/p&gt;
&lt;p&gt;I want to have it load super fast!! No heavy frameworks like React, Vue, etc. I really don&apos;t want client side javascript. It should be build once, keep serving always after that! Basically a static site.&lt;/p&gt;
&lt;p&gt;There are a bunch of neat things which you can find in &lt;code&gt;npm&lt;/code&gt; packages like better markdown parsers, fancy wallpapers, dangerous malwares 💀 (:P). I definitely want to be able to use some of them if possible.&lt;/p&gt;
&lt;h3&gt;Ditching hugo&lt;/h3&gt;
&lt;p&gt;For my old website, which my old self made, I used a simple Hugo template. It might have been easy to get up and going at that time, but I am not a fan of these templating engines myself. When I decided to revamp the website, I realized I needed a hugo binary on a specific version, cause it was not building with latest! Some people push their binaries itself to the repo, but what about changing machines? Working across different architectures, etc. I decided I did not want to deal with this crap. So what to do now? Well recently, I migrated my resume from all these templating engines to a custom HTML/CSS file. It is so beautiful now, I can make any changes, add as many points as I want, lay it out as I want, this is what I was looking for! LaTex? Hold my gun, HTML/CSS are always the original king 👑.&lt;/p&gt;
&lt;p&gt;I wanted to replicate same success with my new setup too, so that&apos;s why I started writing my website in .... HTML and CSS. I could do all my pages with it, but what about markdown files, I would have to write scripts for rendering them? And then actually managing/using them in website is also a bit painful.&lt;/p&gt;
&lt;h3&gt;Enter Astro&lt;/h3&gt;
&lt;p&gt;While I was figuring all this out, I attended a IndieWebClubBengaluru meetup and there, some of my old colleagues showed me their websites which were built using Astro. I initially wasn&apos;t interested as I thought it would be another heavy client side javascript framework. But then they stared showing me their lighthouse score, how fast their sites loads and how it did not have &quot;client heavy javascript&quot;! Bingo, that was all I wanted to hear. That&apos;s when I started exploring Astro, and went through full &lt;code&gt;guide&lt;/code&gt; section in its &lt;a href=&quot;https://docs.astro.build/en/concepts/why-astro/&quot;&gt;documentation starting from here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And OH BOY!! I had hit jackpot, somehow these guys had exact thoughts like I had and had everything needed to fulfil my requirements. Markdown files to html? Content management from a third folder of all the markdown content? No client side javascript? RSS? EVERYTHING WAS PRESENT! And everything was supported first class!&lt;/p&gt;
&lt;p&gt;There is just one thing amongst my requirements, which you would be confused about, how do you &lt;code&gt;keep draft blogs private if possible&lt;/code&gt;, that&apos;s technically not something these frameworks can support. Well, to solve this I created a content collection, basically a folder which has all the blog posts. Astro would walk all the dirs/sub-dirs in this collection and make an array of blog posts. Main catch is, this folder is a &lt;a href=&quot;https://github.com/feniljain/blogs&quot;&gt;separate repo&lt;/a&gt; in my case. I added it as a submodule, and while it is public for now, I can convert it into a private one at any anytime. I can clone the submodule because I have access to it, but others can&apos;t. I can still keep the website code open though! Match made in heaven xD.&lt;/p&gt;
&lt;h3&gt;Not so good parts&lt;/h3&gt;
&lt;p&gt;So till now, we discussed how Astro is ticking all the boxes, but nothing is perfect, there were a few caveats. First is resizing images in markdown. Images can be of varied size but resizing them dynamically is a very common usecase. Well it seems like Astro does not have good support for the same with markdown files. I had to convert my blog of &lt;a href=&quot;https://fknil.com/blog/chinaga-betta-hike/&quot;&gt;chinaga betta hike&lt;/a&gt; to a MDX file and inject Astro specific JSX to get it working, this was a huge bummer for me. Before moving on to MDX I tried a bunch of things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;img&lt;/code&gt; tag in markdown file with local path&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;span&lt;/code&gt; and &lt;code&gt;div&lt;/code&gt; tag in markdown file with local path&lt;/li&gt;
&lt;li&gt;Try to use &lt;code&gt;githubusercontent&lt;/code&gt; URL of the image with height and width mentioned in URL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these worked 😓. At the end, I had to shift to MDX which obsidian does not support out of the box, it does not even show it in UI!! Well this was one grievance.&lt;/p&gt;
&lt;p&gt;Next was, &lt;code&gt;astro/@rss&lt;/code&gt; by default does not support linking to images. So if I convert my markdown files to HTML for RSS consumption it does not separate out images in those with some kinda permalink. I am not sure if this an Astro problem, but the OutOfTheBox experience kinda broke here. For now, I am sending MDX files directly in RSS feed without even linking to the images :(&lt;/p&gt;
&lt;p&gt;One last problem, was a surprising behaviour. When I first added frontmatter to all my blogs, registered it as content collection, Astro&apos;s hot refresh didn&apos;t pick them up at all. There were no errors in logs, browser console anywhere?? I had my &lt;code&gt;npm run dev&lt;/code&gt; running from the morning since I have been building website incrementally. But I for some reason decided to kill and restart the server, and low and behold it didn&apos;t start. It failed with an error. Frontmatter of one of the markdown files was not proper (it was reading root README.md too). Well well, I spent so much time, tweaking &lt;code&gt;content.config.ts&lt;/code&gt; and frontmatter for all the blogs, this was very frustrating.&lt;/p&gt;
&lt;h2&gt;Further plans&lt;/h2&gt;
&lt;p&gt;But you know, as they say, all&apos;s well that ends well. Except it hasn&apos;t ended, I will take a pause on development for now, but will come back to try to make fixes for the image handling flow for HTML rendering and RSS readers.&lt;/p&gt;
&lt;p&gt;I don&apos;t want this to be a passion project for now, it is more of a means to an end, to fulfil the core motivations I listed in the beginning of this blog. I want to take it slow, not try to go full speed once and then never come back to touch it. Core goal is writing more, so we will focus on that above all!&lt;/p&gt;
&lt;p&gt;There&apos;s also one thing I wanna do before taking on work of image flow fixing, that is adding wander instance to my webpage, that would be fun to have, a corner of mine in the small/indie web community :)&lt;/p&gt;
&lt;p&gt;For now, you can find &lt;a href=&quot;https://fknil.com/&quot;&gt;my website here&lt;/a&gt; and &lt;a href=&quot;https://fknil.com/rss.xml&quot;&gt;RSS feed here&lt;/a&gt;. Till next time, chao! 😺&lt;/p&gt;
</content:encoded></item><item><title>Upgrading to neovim 0.12</title><link>https://fknil.com/blog/neovim-v12/</link><guid isPermaLink="true">https://fknil.com/blog/neovim-v12/</guid><pubDate>Wed, 20 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently upgraded my neovim to 0.12.2, I have been chipping away at it slowly using &lt;code&gt;NVIM_APPNAME&lt;/code&gt; feature so that my day to day activities are not hindered. One of the things I found interesting in using &lt;code&gt;NVIM_APPNAME&lt;/code&gt; is, I saw a blog where author manually create all the &lt;code&gt;~/.local/share/nvim-next&lt;/code&gt; etc dirs, I did the same cause I was not aware, but later I realized that one can literally just say &lt;code&gt;NVIM_APPNAME=nvim-next ~/.local/share/bob/0.12/bin/nvim&lt;/code&gt; and neovim will automatically make all those dirs for you. Also yes, I use awesome &lt;a href=&quot;https://github.com/MordechaiHadad/bob&quot;&gt;bob-nvim&lt;/a&gt; for managing neovim versions. With that out of the way, lets get started!&lt;/p&gt;
&lt;h2&gt;Major changes&lt;/h2&gt;
&lt;p&gt;Cool, lets talk about major changes, listing them out first:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;vim.pack&lt;/code&gt;: Migrating to builtin package manager&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lsp&lt;/code&gt;: Ditching nvim-lspconfig etc for &lt;code&gt;vim.lsp.enable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ui2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;no vimscript in config&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Migrating to builtin package manager&lt;/h3&gt;
&lt;p&gt;Neovim now comes with an inbuilt package manager called &lt;code&gt;vim.pack&lt;/code&gt;, its currently experimental but is considered good enough for daily driving. I have used a bunch of package managers over time, vim-plugin, packer, lazy and now &lt;code&gt;vim.pack&lt;/code&gt;. While I don&apos;t care about them a lot, each migration has been inspired by reasons. vim-plug -&amp;gt; packer, lua shift of whole ecosystem. packer -&amp;gt; lazy.nvim, extra features like dependencies, clean config, etc and finally lazy -&amp;gt; vim.pack cause I want to reduce count of external dependencies. With all the changes happening upstream, I am really hopeful that some day my whole config will just fit in a small file!&lt;/p&gt;
&lt;p&gt;But I couldn&apos;t simply move to using vim.pack, I had to evaluate if it was strictly an upgrade. Factors I looked for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do I depend on dependencies feature of lazy?&lt;/li&gt;
&lt;li&gt;Does it slow down my startup times?&lt;/li&gt;
&lt;li&gt;Does separating config in &lt;code&gt;vim.pack&lt;/code&gt; make config structure worse?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For &lt;code&gt;dependencies&lt;/code&gt; section, I went through my plugin list and realized I had these dependencies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;aerial.nvim on nvim-treesitter and nvim-web-devicons&lt;/li&gt;
&lt;li&gt;telescope on telescope-fzy-native, telescope-live-grep-args and plenary.nvim&lt;/li&gt;
&lt;li&gt;nvim-treesitter-context on nvim-treesitter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These don&apos;t seem that bad, all of them are loaded much later in the neovim startup process and I could just keep them in a particular order to make the resolution pass. Well I tried it and that did work out, so ticked this off.&lt;/p&gt;
&lt;p&gt;For my startup times, I used &lt;code&gt;nvim --startuptime startuptime.log .&lt;/code&gt;. If you are not familiar with this command, it instructs neovim to write a log of its startup activities and to get startup time you would look for log &lt;code&gt;--- NVIM STARTED ---&lt;/code&gt;, first number in that row is the amount of seconds it took to startup your neovim. I measured this and realized I hadn&apos;t used &quot;lazy&quot; in lazy.nvim package manager 😭. But I never felt the need to make it go faster, cause I wasn&apos;t able to perceive a delay when starting it. Day I start noticing the delay is the day I bring down my hammer. I had done this earlier for my &lt;a href=&quot;https://github.com/feniljain/blogs/tree/main/2024/faster-shell-boot&quot;&gt;shell&lt;/a&gt; too. But after the migration, timings seemed the same, actually a bit better than lazy.nvim, so I was already happy :)&lt;/p&gt;
&lt;p&gt;For config structure, I really liked how lazy forced a dir structure of &lt;code&gt;plugins&lt;/code&gt; and encouraged keeping config along side plugin installation line itself. It was a clean way. With &lt;code&gt;vim.pack&lt;/code&gt; I had two ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;keep installation line with config in top level &lt;code&gt;plugin/&lt;/code&gt; dir&lt;/li&gt;
&lt;li&gt;keep all installation lines together and config separately&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I wanted to maintain order remember? That can easily be done with a single &lt;code&gt;vim.pack.add&lt;/code&gt; and listing down all the plugins together with their install order. But with &lt;code&gt;plugin/&lt;/code&gt; dir, I would have to name files &lt;code&gt;0_&lt;/code&gt;, &lt;code&gt;1_&lt;/code&gt; etc. This is because files in &lt;code&gt;plugin/&lt;/code&gt; are loaded automatically by vim and neovim in sorted order. Naming files like that was a turn off for me, so I went with single &lt;code&gt;vim.pack.add&lt;/code&gt; call and created a new dir called &lt;code&gt;plugins/&lt;/code&gt; in &lt;code&gt;lua/&lt;/code&gt; dir and shoved all plugin related config there. I enforced &lt;a href=&quot;(https://github.com/feniljain/dotfiles/blob/9681a844aadc971dae416836e2dc75feb328b8d3/nvim/.config/nvim/lua/fenil/plugins/init.lua#L1-L8)&quot;&gt;setup order&lt;/a&gt; in &lt;code&gt;lua/plugins/init.lua&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With all of this out of the way, migration was really SMOOTH! And I kinda love that I am not pulling a heavy dependency like lazy.nvim in my dep tree :)&lt;/p&gt;
&lt;p&gt;If you have advanced usecases or want to understand the feature better, there are two awesome guides: official manual and then &lt;a href=&quot;https://echasnovski.com/blog/2026-03-13-a-guide-to-vim-pack.html&quot;&gt;this&lt;/a&gt;. Read it end-to-end, each section has something you can take away. It is the most comprehensive guide out there right now.&lt;/p&gt;
&lt;p&gt;One small trick I learned from the article above is placing &lt;code&gt;vim.loader.enable&lt;/code&gt; speeds up startup times for free and this is blessed on us by Folke himself!! Ofc I added that and instantly realized 25ms off the loading time xD&lt;/p&gt;
&lt;h3&gt;LSP&lt;/h3&gt;
&lt;p&gt;Neovim v0.12 also brings in more ergonomic LSP usage support. Now, you can place LSP server setting in &lt;code&gt;lsp/&lt;/code&gt; dir and just call &lt;code&gt;vim.lsp.enable(&amp;lt;file-name-in-lsp-dir&amp;gt;)&apos;&lt;/code&gt; and this is all the setup you need! &lt;code&gt;nvim-lspconfig&lt;/code&gt; is now reduced to just maintain settings for upstream LSP servers. As these rarely change, I just copied from upstream and placed in my &lt;code&gt;lsp/&lt;/code&gt; dir, I also realized I only use two of them now: rust_analyzer and taplo (toml LSP server, also for rust dev :P). With this, I was able to cut down a bunch of lines in my LSP config and also trim down deps of &lt;code&gt;nvim-lspconfig&lt;/code&gt; and &lt;code&gt;mason-lspconfig&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;ui2&lt;/h3&gt;
&lt;p&gt;This is an experimental feature where command line meets messages meets pager meets dialog windows. Honestly its best explained in &lt;a href=&quot;https://neovim.io/doc/user/lua/#ui2&quot;&gt;official docs&lt;/a&gt;. This was an interesting change which allowed me to trim down a dep I really liked: &lt;code&gt;fidget.nvim&lt;/code&gt;. It shows LSP progress on the bottom right corner. Now I do it in ui2 itself using &lt;a href=&quot;https://github.com/feniljain/dotfiles/blob/9681a844aadc971dae416836e2dc75feb328b8d3/nvim/.config/nvim/lua/fenil/config/autocmds.lua#L33-L48&quot;&gt;this&lt;/a&gt; autocommand, that&apos;s it, 15 lines are all we need. I get the progress in same place as command line without &lt;code&gt;Hit Enter&lt;/code&gt; prompts.&lt;/p&gt;
&lt;p&gt;This works best with &lt;code&gt;cmdheight = 0&lt;/code&gt; , which prevents &lt;code&gt;Hit Enter&lt;/code&gt; prompts. &lt;code&gt;cmdheight&lt;/code&gt; feature was merged in 0.11 itself, I had tried it then but it felt incomplete and weird, but now with ui2 it has the perfect UX, they have really nailed this! There&apos;s just one small hiccup, somehow I am not able to see marco record messages, I could reproduce it on master with minimal config so its definitely an upstream issue, hopefully that gets fixed, but till then I have &lt;a href=&quot;https://github.com/feniljain/dotfiles/blob/9681a844aadc971dae416836e2dc75feb328b8d3/nvim/.config/nvim/lua/fenil/config/autocmds.lua#L58-L73&quot;&gt;two hacky autocmds&lt;/a&gt; which almost do the same job just slightly worse 🙃.&lt;/p&gt;
&lt;h3&gt;No vimscript in config&lt;/h3&gt;
&lt;p&gt;I finally took the leap and ditched out all the vimscript from my config, its completely lua based now! I know I am very late to the party, but I really wanted to keep it around so that I can use it on VMs where vim is the default. Well what finally prompted this move was making a minimal vimscript based vim config, which I can easily drop anywhere and get productive with vim! &lt;a href=&quot;https://github.com/feniljain/dotfiles/blob/main/vim/minimal-vimrc&quot;&gt;Here&apos;s&lt;/a&gt; the minimal config for the curious. I also have a minimal tmux config in the same lines &lt;a href=&quot;https://github.com/feniljain/dotfiles/blob/main/tmux/.tmux.conf.minimal&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Bugs discovered&lt;/h2&gt;
&lt;p&gt;This is an interesting section cause I usually never come across any hiccups when upgrading, neovim is a super polished, heavily tested software. People are out there doing builds super frequently to test latest and greatest! ( I was one of them till few years ago :P )&lt;/p&gt;
&lt;p&gt;But this release I came across two interesting bugs!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;macro recording message display with ui2 and cmdheight=0&lt;/li&gt;
&lt;li&gt;a memory segfault with ui2 + invalid rtp and syntax on!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First one we have already discussed, I hope it gets fixed in an upcoming version or even next release is fine :P&lt;/p&gt;
&lt;p&gt;For the second one, this is something severe and I was very astonished to come across it! First, link to &lt;a href=&quot;https://github.com/neovim/neovim/issues/39815&quot;&gt;bug report&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;minimal repro is just:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require(&apos;vim._core.ui2&apos;).enable()
vim.cmd [[
set rtp+=$LMAO &quot; Cannot be a non-existing dir, needs to be an env var which does not exist
set syntax
]]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So conditions are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ui2 should be enabled&lt;/li&gt;
&lt;li&gt;rtp should be set to an env var which does not exist&lt;/li&gt;
&lt;li&gt;syntax should be on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The reason I came across this is because of this weird rtp setting I had set in &lt;a href=&quot;https://github.com/feniljain/dotfiles/blob/8ee7f08e3d5c978d3a667ff3479f4150d10ceeda/nvim/.config/nvim/plugin/sets.vim#L17&quot;&gt;my config&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set rtp+=$GOPATH/src/golang.org/x/lint/misc/vim
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have no recollection why I had added this, I have messed around a lot with my config over the years, so there are artifacts I still find in weird corners. But main point being, I don&apos;t have golang installed in my system, so &lt;code&gt;GOPATH&lt;/code&gt; is not set and hence satisfies condition we mentioned above. I am not exactly sure what is causing this crash in neovim internally, it needs some investigation 🧐.&lt;/p&gt;
&lt;p&gt;But yeah interesting times! I created a new APPNAME with nvim-debug and managed to track it down to this config and also reproduce on latest &lt;code&gt;master&lt;/code&gt;. I have reported it, let&apos;s see if someone upstream picks it up before I get my hands dirty 🏃.&lt;/p&gt;
&lt;h2&gt;Sides&lt;/h2&gt;
&lt;p&gt;I was checking &lt;code&gt;:checkhealth vim.lsp&lt;/code&gt; and realized I hadn&apos;t seen &lt;code&gt;:checkhealth&lt;/code&gt; in a while, I did that and boom, it was so much cleaner!! Now we have ✅ and ❌ and ⚠️ to show overall health of the features etc, and in general it looked really really clean!&lt;/p&gt;
&lt;p&gt;There are also other features like &lt;code&gt;:restart&lt;/code&gt;, etc which I didn&apos;t delve into much cause I wasn&apos;t sure how to make use of those features right now. There are bunch of other features too, do go through all the &lt;a href=&quot;https://neovim.io/doc/user/news-0.12/#news-0.12&quot;&gt;release notes&lt;/a&gt;. Amount of things I have realized by reading the release notes in detail is mind blowing, 100% recommended!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Overall, I am super happy with this new release, only thing I couldn&apos;t change this time is colorscheme, I didn&apos;t have any lined up to try out 😓.&lt;/p&gt;
&lt;p&gt;Except that, I am already looking forward to more amazing things in upcoming releases (multi-cursor looking at you). Till then, chao!&lt;/p&gt;
</content:encoded></item><item><title>Working with LLMs</title><link>https://fknil.com/blog/working-with-llms/</link><guid isPermaLink="true">https://fknil.com/blog/working-with-llms/</guid><pubDate>Tue, 05 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As we usher in this new era of LLMs, it is interesting to see how different people are starting to work with them. And as a typical keyboard thudding monkey, I want to optimize my workflow too. Because a true master understands tools at it his/her disposal the best.&lt;/p&gt;
&lt;p&gt;The way I currently work with them is straight forward way popularized by Claude Code, plan with it first in plan mode, then jump into implementation. I try to manually approve everything, but still I lose context in the &quot;hit enter&quot; hell. To over come it, I sometimes just let it make all the changes and then go back and start editing it. Now, ideally this should work, you plan meticulously and once the plan is solid, bang on, all code will be perfect. Right? Right?&lt;/p&gt;
&lt;p&gt;I think that&apos;s a wrong model to think how software engineers work. Most of the times, we discover/realize things on the fly, and that could be as small as a super small limited scope change to a complete re-design. So it is more of an iterative loop rather than a one shot model. In that case, one would go in and out of plan mode refining the spec as they learn more.&lt;/p&gt;
&lt;h2&gt;My questions&lt;/h2&gt;
&lt;p&gt;But before trying to refine our process, lets try to come up with points I want answers to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;How do I know I have explored all the possible ways to attack a problem, could there a simpler solution?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Another is, breaking down abstractions at correct boundaries, I think LLMs struggle with this right now. I see a lot of people dumping code in places where it shouldn&apos;t belong in the first place. Why is it dumped there, cause no one cared enough to think about boundaries. Well, this was a problem before LLMs too, but its much more worse right now.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Writing code by hand is a process which forces one to slow down and look at the surrounding code, think about frictions we face when coming up with code. Just being lazy and realizing a lot of things. LLMs don&apos;t have that (1). How to bring back this process of slowing down? And in what form? Hand-write everything again?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How do I trust the tests written by LLM? Amount of people who are not reading generated tests is baffling high. No one, literally no one I know is reading generated tests. They think if there are tests its enough. Amount of times I have found generated tests to not be helpful is actually very high. Like the saying of man goes: &quot;To know a man, check his trash&quot;. &quot;To know about an implementation, check its tests&quot;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How to find subtle problems within the implementation, Antirez put it nicely: &lt;code&gt;&quot;but still things that superficially work do not mean they are optimal.&quot;&lt;/code&gt;(2)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;User workflows&lt;/h2&gt;
&lt;p&gt;Before we try to answer these questions, lets try to read Antirez&apos;s use of LLMs for array type support in redis (2) (3). Summarizing it the way I understood it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;He wrote first design draft completely by himself&lt;/li&gt;
&lt;li&gt;Brought in LLM, started attacking draft from different angles, this would have likely required him asking correct questions to LLM&lt;/li&gt;
&lt;li&gt;He read whole code line by line with extreme care. I liked this a lot: &lt;code&gt;but still things that superficially work do not mean they are optimal.&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;He rewrote the whole implementation again in a mix of manual and LLM mode&lt;/li&gt;
&lt;li&gt;Extensive testing, a complete month dedicated to just that&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In his own words towards the end:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;For high quality system programming tasks you have to still be fully involved, but I ventured to a level of complexity that I would have otherwise skipped. AI provided the safety net for two things: certain massive tasks that are very tiring (like the 32 bit support that was added and tested later), and at the same time the virtual work force required to make sure there are no obvious bugs in complicated algorithms. To write the initial huge specification was the key to the successive work, as it was the key to review each single line of sparsearray.c and t_array.c and modifying everything was not a good fit.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As we are at it, these are some ways I have seen people around me use it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clowns: Absolute direct vibe code, this is just dumb&lt;/li&gt;
&lt;li&gt;GreatPretenders: Give the problem to LLM, act like they understand it by saying: &quot;we manually accepted edits&quot;, test it on basic cases and ship to production.&lt;/li&gt;
&lt;li&gt;Meticulously try to plan things with it, try to attack from different angles. From here on two more routes:
&lt;ul&gt;
&lt;li&gt;Strategist: Write code using a LLM assisted autocomplete&lt;/li&gt;
&lt;li&gt;OldieGoldie: Write code completely by hand&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We are not going to talk about Clowns and GreatPretenders at all except one statement to these people, PLEASE stop making my life difficult.&lt;/p&gt;
&lt;h2&gt;Thoughts on my questions&lt;/h2&gt;
&lt;p&gt;Now that we have everyone&apos;s workflows in place, let&apos;s try to come back to our questions. (Answers in the same bullet point number as the question)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;I like Antirez&apos;s approach here, he took a month just to write the spec, and he didn&apos;t write first draft with the help of LLM, it was completely by himself. This is where I think Strategist and OldieGoldie&apos;s get defeated, I believe key point is: not reading the approach given by LLM first. Cause there are times, they just don&apos;t know, and they don&apos;t know what they don&apos;t know. They are not able to come up with few of the strategies you might come up with. You could call this the creative step or whatever. I have noticed, reading LLMs output first creates a bias in the mind, and also we might get hindsighted on asking the correct questions. That&apos;s why try to come up with a plan on your own and then work with LLM to try to attack it from different sides to solidify it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On this part, I think there are two steps where this comes up, first is when planning i.e. 1st step and next is when actually writing code by hand and noticing a friction point. First part is addressable during first step itself, this is usually the easy part. But when it comes to the latter, I think it correlates with 3rd point of mine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now this is a tricky one, one needs to slow down, we slowed down once in the initial planning phase, but when next? In the iterative cycle I mentioned above, how do we slow down during the actual implementation section to notice these frictions? Well one way is converting into OldieGoldie, it is slow but definitely works! Though one could be lost completely in implementation details and want to complete it fast, which would lead us to the pre-LLM era problem of people writing absolute horrendous code without respecting any abstractions.&lt;/p&gt;
&lt;p&gt;So, completely automated is bad, completely hand written is dicey, then Strategist wins? Well, I don&apos;t think so, again this is a point about slowing down, fancy autocompletes are not a great way to slow down and understand cross module dependencies. Well then what? I like Antirez&apos;s way here, seemingly he generated all code first as a PoC, realized few things during PoC to fix, re-generated it, assumed just reading everything in extreme detail would help but he didn&apos;t know the answer to: &lt;code&gt;but still things that superficially work do not mean they are optimal&lt;/code&gt;. So he went back and rewrote whole implementation in a mix of manual and AI-assisted mode.&lt;/p&gt;
&lt;p&gt;The difference between Strategist and this is using code as a throw-away signal, Antirez used the first version as a PoC, that&apos;s it. He then, rewrote the implementation in his own way completely, this makes the process so much faster and more context aware than one shotting the implementation and making abstractions etc on the fly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For this point, Antirez said two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Everything was working, and this type has massive testing, thanks, again to AI&quot;&lt;/li&gt;
&lt;li&gt;&quot;When this stage was done, I started, during the third month, to stress test the implementation in many different ways.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don&apos;t think there&apos;s info on what he did here. As such testing is a very subjective topic and how to do it properly for a particular system is a monster of its own. For now, I try to follow the same procedure as before, try to come up with test cases myself and then involve LLMs to expand upon them on their own and combine to form a better list. This helps avoid a bunch of test cases which add 1K lines of abstractions on their own to test a simple thing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For this one, I think 3rd point above goes in enough details about everything. Key to this point I believe is slowing down and reading code multiple times to try and think from different angles.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This kinda also lays out how I want to try using LLMs going forward.&lt;/p&gt;
&lt;h2&gt;Unanswered Questions in Antirezs&apos; article&lt;/h2&gt;
&lt;p&gt;Taking a small detour and going back to Antirez&apos;s article, I have a few things I would have liked to understand in more details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When he used in LLM in the planning phase, what part of it was him trying to probe questions out of LLM as an experienced user and what part of it was, LLM finding defects/improvements on its own?&lt;/li&gt;
&lt;li&gt;What part of codebases did he rewrite manually and what parts were rewritten using LLM? How did he decide which part to allocate to who?&lt;/li&gt;
&lt;li&gt;How did he approach testing in general, did he check LLMs generated tests in super detail? Did he rewrite them too? What did his one month of testing look like in detail? How did LLMs help outside unit tests?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It was interesting to think about these things while writing this article down. I would have not imagined myself thinking about these things because I had previously been haunted by a college senior of mine being too strict on writing down huge number of pages of LLD, HLD, PRD, etc etc for club projects. Ofc we never finished the projects which he was supervising. I still don&apos;t know if all of this was coherent or just random rambling. Well, there&apos;s one thing for sure, I have something new to try and I will make sure I keep the rigor up in LLM age! [4]&lt;/p&gt;
&lt;p&gt;Footnotes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(1) &lt;a href=&quot;https://bcantrill.dtrace.org/2026/04/12/the-peril-of-laziness-lost/&quot;&gt;peril-of-laziness-lost&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;(2) &lt;a href=&quot;https://antirez.com/news/164&quot;&gt;antirez blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;(3) &lt;a href=&quot;https://github.com/redis/redis/pull/15162&quot;&gt;redis github PR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;(4) &lt;a href=&quot;https://oxide-and-friends.transistor.fm/episodes/engineering-rigor-in-the-llm-age&quot;&gt;engineering-rigor-in-the-llm-age&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>ClaudeHeads</title><link>https://fknil.com/blog/claude-heads/</link><guid isPermaLink="true">https://fknil.com/blog/claude-heads/</guid><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So firstly what are ClaudeHeads? They are people who have claude in place of their head. They literally think LLM is the only answer and can only think using them, they are just straight up bad at individual thinking, but that doesn&apos;t matter, cause LLM can solve everything given right context, given all the information in the world about thing which exists.&lt;/p&gt;
&lt;p&gt;For me this is a problem. I joined database industry cause I could not bear writing HTTP APIs for the rest of my life. I am not smart enough personally, but there are horrible software engineers out there, you would find shitty code in all parts of the software stack. But for something which is performance critical, needs to correct always, and is always the black box for programmers, that would need highest and purest levels of programmers, right right??? Well it seems I could only enjoy this dream for sometime, cause LLMs have given birth to ClaudeHeads. We use an open source project named datafusion and have based our database on it. Its not a direct stock integration, we have had to make a lot of changes according to our needs, and it seems distribution is still an unsolved problem there. Also main IP of planner stays with us, single execution is not a solved problem, but open source projects are very very good!&lt;/p&gt;
&lt;p&gt;Well, given that it is open source, of course LLMs are trained on it. Now, that is one part of the equation, in recent months they have also become good enough to interact with private parts of our codebase. Migration to a datafusion based engine is a recent enough project and we had been working hard to get performance on TPCDS-like benchmark[1], TPCH-like benchmark, Clickbench, and a bunch of internal benchmarks. We were very very slow as compared to our good old internal custom developed Java engine, as compared to Databricks, as compared to Snowflake. Whole team was heads down working on getting perf better than all of the above combined. As me and other senior colleagues took an &quot;old school&quot; methodological approach of looking at heap profiles, CPU flamegraphs, custom metrics collected by us, finding gaps in our understanding of the system, as not whole codebase was familiar to us yet. Here enters my ClaudeHead hero, who downloads research papers of Datafusion/Arrow etc, keeps them in a folder, keeps all TPCDS queries, their flamegraphs, heap profiles and metrics together, and send the agents to &quot;find perf improvements&quot;. The result was pretty shit with earlier models, but what about recent ones? You leave them for a night and they conjure up a bunch of things. Tho how do you test them?&lt;/p&gt;
&lt;p&gt;So, incidentally someone in my company developed a easy to use benchmark setup. What was left now, multiple branches started getting created, purely vibe coded and benchmarked in parallel. What ever improved perf was posted as it is after &quot;understanding from Claude summary&quot; to the channel and merged to main. Well the problem is &quot;understanding&quot; part is absent, if asked to reason about the change from different angles, like architectural correctness, my friend would turn around and just ask Claude. There&apos;s no head working there, it&apos;s just Claude. Well how do I know this? Cause I have asked questions around why some part of it didn&apos;t make sense in larger scheme of things. Why even tho metric shows there&apos;s nothing to optimize, you keep repeating there&apos;s an optimization in a specific region, without backed by proof, just because Claude said that. What&apos;s worse, this is a junior engineer just entering the field. Not good at coding, not good at databases, not good at CS fundamentals. But given LLMs, he can keep on posting perf optimizations and get them merged. One could argue, if those don&apos;t make sense why can&apos;t you prove them wrong? Well here comes the main point of article, my views on ClaudeHeads and how they are correct at times, but expert bullshitters at times. Back in the days, when no LLMs existed, if someone bullshitted, they had to put a LOT OF EFFORT to even get something remotely good out, btw this is considering that it was still considered easy, &lt;a href=&quot;https://en.wikipedia.org/wiki/Brandolini%27s_law&quot;&gt;Brandolini&apos;s law&lt;/a&gt;. During the process, they learned 100s of things and would definitely come out as a much better engineer, but now? Tell LLM to fire off and conjure stuff, what if that does not make sense in grand scheme of things and would literally break in just a different environment (someone shares my &lt;a href=&quot;https://x.com/siddharthkp/status/2046890064450302110&quot;&gt;feelings&lt;/a&gt;). Well that&apos;s benchmaxxing. But what if you could keep benchmaxxing again and again for each dataset. That&apos;s not exactly what&apos;s happening, but I am thinking through scenarios.&lt;/p&gt;
&lt;p&gt;Not understanding what changes you are making to me is the biggest risk of all time, and it breaks what I thought about before starting to work on databases. It&apos;s not a race of understanding, now it&apos;s a race of trying random folder structures with random bits of information to get the best output out of LLM models. Oh guess what, I am still stuck in the old model, and this has caused a big disadvantage to me. Not only am I slow now, I am also losing learning opportunities myself, just because someone decided to not understand them and rely completely on LLMs. I can pick up LLMs to speed up my work, but all I have ever learnt is, slow and steady wins the race. I believe it to my heart, mental models are the biggest factors of a product. That&apos;s the reason when someone who understands codebases deeply leaves, new team gets in frenzy. That&apos;s the reason losing a product person who understood product deeply, is such a big loss. They are hard to replace. Code was never the moat, mental models were the actual secret sauce. But in this case, trash out the mental models, we will just use LLMs to not just write code, but also think, not build mental models, just straight up outsource thinking.&lt;/p&gt;
&lt;p&gt;Writing code is amongst the best way to build mental models, slow, deliberate thinking is what dials down core ideas of anything. This applies to product building as well as programming. Having faced the &quot;friction&quot;, and letting mind battle with it is the best way. I recently read a blog post in similar vein, but talking about &lt;a href=&quot;https://ergosphere.blog/posts/the-machines-are-fine/&quot;&gt;astrophysics&lt;/a&gt;. It&apos;s an excellent read, do go through it.&lt;/p&gt;
&lt;p&gt;But yeah, this is my problem, sorry I am not slow, I am just not a ClaudeHead.&lt;/p&gt;
&lt;p&gt;[1] I say TPCDS-like cause I remember how badly PlanetScale was thrased in an official blog post when the data generator used by them did not actually comply with TPCDS specifications :upside_down:&lt;/p&gt;
</content:encoded></item><item><title>Estimating filter equality selectivity using NDVs</title><link>https://fknil.com/blog/ndv-filter-equality-selectivity/</link><guid isPermaLink="true">https://fknil.com/blog/ndv-filter-equality-selectivity/</guid><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Now that I work on databases, I have a habit of keeping up with upstream datafusion PRs. Today I noticed an interesting PR talking about usage of NDVs in equality filter selectivity. I have always been fascinated by NDVs cause my colleagues in planner team always mention them as something super helpful. I started looking into the PR and it turned out to be a small one, but there was a review on it and honestly I did not understand it at all. So I sat down to do some reading on how this works.&lt;/p&gt;
&lt;p&gt;Firstly, what are NDVs? NDVs are number of distinct values, these are usually stored at parquet file level. We can also compute NDVs for a column, i.e. how many distinct values a single column contains.&lt;/p&gt;
&lt;p&gt;What is filter selectivity? For a filter supplied in a query, number of rows selected by it is called it&apos;s selectivity. For e.g. a filter which filters out 50 rows out of total 100 has a selectivity of 50%.&lt;/p&gt;
&lt;p&gt;Now lets understand how do they come together, lets say we have a join query like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select * from A where A.x = B.y;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this, we have our join condition as &lt;code&gt;A.x = B.y&lt;/code&gt;, if we can predict what will be the selectivity of this filter expression we can make interesting decisions based on it. A good example is: do we want to use partition-wise join or non-partition-wise join? ( i.e. if we have loads of rows to join on, we can distribute them across cores instead of doing it on single core itself )&lt;/p&gt;
&lt;p&gt;NDVs help us do exactly that, let&apos;s say we have a condition where &lt;code&gt;y = 42&lt;/code&gt;. And let&apos;s say &lt;code&gt;y&lt;/code&gt; column has 5 distinct values, that means our NDV count is 5. As we don&apos;t have exact histograms telling us about data distribution, we assume each value is &quot;uniformly distributed&quot; across whole column. For e.g. if &lt;code&gt;y&lt;/code&gt; column is made up of &lt;code&gt;{38,39,40,41,42}&lt;/code&gt; and has 100 values in total, we assume there are 20 values of 38, 20 values of 39 and so on. This assumption means probability of 42 getting matched is equal to all others distinct values i.e. 1 / 5. If we multiply this with total number of rows in the column, we get selectivity of &lt;code&gt;y = 42&lt;/code&gt; as 20. Here key point is understanding us assuming uniform distribution, if we had histograms, we could exactly tell how many rows have value 42 in the column, but NDVs work as next best case.&lt;/p&gt;
&lt;p&gt;This estimation of rows helps in join order estimation, join type estimation, etc.&lt;/p&gt;
&lt;p&gt;After understanding this I noticed there was a review comment on the PR and tried to decode that. Review was as follows&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;I think this new `1 / distinct_count` branch is a little too broad as written. Right now it fires whenever the pruned interval collapses to a single value, but that is not quite the same thing as proving we have an equality filter.

For example, if the incoming stats already describe a singleton interval, or if a conjunction of inequalities narrows the range to one point without actually adding any selectivity beyond the existing stats, we would still scale by `1 / NDV` here and end up under-estimating the row count.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was a total bouncer for me, it was so high that if this were a cricket match, umpire would call it a WIDE. But let&apos;s try to break it down, so author&apos;s current condition to use &lt;code&gt;1/NDV&lt;/code&gt; is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if ...
	target.distinct_count
                    &amp;amp;&amp;amp; distinct_count &amp;gt; 0
                    &amp;amp;&amp;amp; !target_interval.lower().is_null()
                    &amp;amp;&amp;amp; target_interval.lower() == target_interval.upper() {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this interval means zone maps i.e. min/max values of that column. In our case above &lt;code&gt;y&lt;/code&gt; would have min/max values as &lt;code&gt;{39,42}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Condition checks if NDV count is not zero and &lt;code&gt;target_interval&lt;/code&gt;&apos;s lower value is same as upper value, if everything passes we assume our filter selectivity as &lt;code&gt;1/NDV&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;According to reviewer, &lt;code&gt;1/NDV&lt;/code&gt; estimation is incorrect in following cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;if the incoming stats already describe a singleton interval&quot;&lt;/li&gt;
&lt;li&gt;&quot;if a conjunction of inequalities narrows the range to one point without actually adding any selectivity beyond the existing stats&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both of these reviews at the core address the problems of:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shape of data changes as it gets processed by different operators
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;singleton does not guarantee an equality filter source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lets try to understand above line with an example. Lets say we have two filters on our &lt;code&gt;y&lt;/code&gt; column due to some CTE/subquery etc:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;first being: &lt;code&gt;y &amp;gt;= 41 || y &amp;lt;= 42&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;and second being: &lt;code&gt;y &amp;gt; 33 AND y &amp;lt; 42&lt;/code&gt; (non equality condition)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After first filter we would have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bounds: &lt;code&gt;[41, 42]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;NDV count: 5 (notice it didn&apos;t change)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When we come to second filter and apply bounds to predicate we only get rows containing 41. Here we will predict selectivity as &lt;code&gt;1/NDV&lt;/code&gt;. This is the exact problem, lets say out of first filter we get 70 rows out i.e. first filter has selectivity of 70%. Now lets say we have 35 rows of &lt;code&gt;41&lt;/code&gt; and 35 rows of &lt;code&gt;42&lt;/code&gt;, after applying second filter 35 rows are remaining i.e. 50% selectivity. But, if we go by NDV route, we get &lt;code&gt;70/5&lt;/code&gt; i.e. 14 rows, that is a super low estimation!&lt;/p&gt;
&lt;p&gt;Our NDV count did not change as data flowed through both filters, same phenomenon can happen with different operators in the middle. We also saw that even though we got singleton interval as an&lt;/p&gt;
&lt;p&gt;This was an interesting dive, which confused me a lot at different places, even while writing this down!&lt;/p&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/sql/relational-databases/performance/cardinality-estimation-sql-server&quot;&gt;Cardinality estimation SQL Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/apache/datafusion/pull/20789/&quot;&gt;datafusion PR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blobs.duckdb.org/papers/tom-ebergen-msc-thesis-join-order-optimization-with-almost-no-statistics.pdf&quot;&gt;DuckDB Paper&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Chinaga Betta Hike</title><link>https://fknil.com/blog/chinaga-betta-hike/</link><guid isPermaLink="true">https://fknil.com/blog/chinaga-betta-hike/</guid><pubDate>Sun, 08 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Chinaga betta is a nice little day hike bear Bengaluru. It is said to be 2.1 kms one side, so in total of 4.2 kms up and down. It&apos;s a simple hike, can be done with family and friends. I recently got to visiti it and I am gonna mention how was the experience. First thing is it needs permit, so book it from arayna vihaara &lt;a href=&quot;https://aranyavihaara.karnataka.gov.in/&quot;&gt;website&lt;/a&gt;. Choose a convenient slot, in my time it was just 6 AM to 6:30 AM. This is needed cause it&apos;s said that a forest ranger will accompany you to the top, I say it that way, cause surprise surprise there was no one when we reached there.&lt;/p&gt;
&lt;p&gt;Trek starts from the base of temple &lt;a href=&quot;https://maps.app.goo.gl/9r7iUrNYPkihCvZp6&quot;&gt;Torana Anjaneya Swami Temple&lt;/a&gt;. This is where forest department is supposed to check your IDs before starting. There were a lot of locals when we reached there, its a temple which seemed super active and when we were returning it also seemed they were in the process of sacrificing a goat. We didn&apos;t stand back to see that. Well that&apos;s for a later bit, first in the start, when we were approaching base of the temple we were very scared as it was pitch black and when we entered forest side, it started to feel like off roading. So driving through a lonely road in night with no one in sight, was a bit concerning, but when we reached there and saw few fellow hikers we were relieved.&lt;/p&gt;
&lt;p&gt;We waited for forest ranger till 6:45, but when we realized we were played for a fool, we just started on our own. So yeah, 250 rupees went in vain :(&lt;/p&gt;
&lt;p&gt;Okay, next thing, let&apos;s talk about the actual trail. We followed trail from &lt;a href=&quot;https://www.gaiagps.com/public/NTAxgUNVldkLEmi99JQLIm1W/NTAxgUNVldkLEmi99JQLIm1W&quot;&gt;this&lt;/a&gt; website. I would divide whole hike into four sections, first is big temple to small temple, next is rocky/slaby tiring uphill section, next is flatlands and finally the last remaining part to the summit.&lt;/p&gt;
&lt;p&gt;Forest ranger is mostly not needed for the hike&apos;s majority, it&apos;s just that at the top, there&apos;s a vertical rock, which you have to climb and most people would not be comfortable doing that. I was a climber so I climbed it pretty easily (subtle flex xD). Hike starts at the back of the temple, and there&apos;s a outward protruding rock at the top, which has a flag above, that is your summit.&lt;/p&gt;
&lt;p&gt;First part when we start from the back of the temple, there&apos;s a trail which seems to go in the forest, follow that. Once you follow that you will reach another small temple. There will be two paths there, and just as Robert Frost&apos;s protagonist, we have to the road less taken. This is first part done, its just light walking, perfect warmup for the next tiring section.&lt;/p&gt;
&lt;p&gt;Next section is a where uphill starts, its through mildly dense forest, well it was less denser cause we could see people there had burnt a lot of trees to keep it clear for trail. This section is also where we saw sunrise. It would have been better to watch it from flatlands above, but we were late due to waiting for forest ranger :(&lt;/p&gt;
&lt;p&gt;This section is also slippery at times, two of my friends survived the slip, but it could get dangerous, so either wear good shoes or be extra careful where you are stepping and how you are shifting body weight. It shouldn&apos;t be a problem for most people, but extra caution is never harmful.&lt;/p&gt;
&lt;p&gt;There&apos;s a section between flatland and this uphill where you will start seeing eucalyptus trees. They are beautiful with little yellow flowers on them. This gave me a feeling of walking through magic forest of berserk. If locals hadn&apos;t burnt a lot of trees, I wonder if I would have declared it Garden of Eden.&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://fknil.com/rss-images/blogs/non-tech/2026/chinaga-betta-hike/walk-through-eucalyptus-trees.jpeg&quot;&gt;Image: Walk through eucalyptus trees&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Also while going uphill you will notice white arrows, follow them. Though you could also just rely on the trail map you have downloaded, it was on OpenStreetMaps on IndiaHikes website so you could use any FOSS maps app to view it.&lt;/p&gt;
&lt;p&gt;Next section is flatlands, it is what the name says. I don&apos;t think it&apos;s called flatlands in any blog or something, but I am calling it that cause of minecraft biome xD This is one spot you can catch sunrise. Best would be top, but even this is fine. From here you would start getting views of surrounding area. Its serene, breathe in and get ready for the remaining part!&lt;/p&gt;
&lt;p&gt;Now, next is walking through some part of flatlands to reach farthermost bottom of last section, there should be a easily visible trail starting there. Just follow that.&lt;/p&gt;
&lt;p&gt;As you walk through the last section, you will come across two big rocks creating a narrow space between them. You have to squeeze and pass, that is also very slippery, but all I could think at that point is, if I could climb this chimney 😂&lt;/p&gt;
&lt;p&gt;Once you complete that you would reach the final vertical rock which you have to climb. There&apos;s a rope there to assist but it seemed we were the first one to reach so it was thrown above!? Not sure why would someone do that. It looks like this:&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://fknil.com/rss-images/blogs/non-tech/2026/chinaga-betta-hike/vertical-wall-to-climb.jpeg&quot;&gt;Image: Vertical wall to climb&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;It doesn&apos;t look much but there&apos;s no proper footing down below, so if you slip you can get injured. And getting up is one thing, getting down is more scary unless you have someone looking at your feet and telling you where the footholds are.&lt;/p&gt;
&lt;p&gt;They are also carved inside the rock, and not projecting outwards. Also part of the reason why when getting down you have to look for them. Well, getting back in our case, I was the guy who got pushed forward to climb to get rope from above. I had no safety, so my friends were a bit concerned but I was confident as I had climbed much higher rocks with much dicier footholds and handholds as compared to this in Hampi. I threw the rope down and assisted everyone else to climb above. As we reached above, we could get a much clearer view of everything around, it&apos;s a beautiful place. On one side, you are seeing mountains till eyes can reach, covered with a blanket of clouds. Next side, you see tiny settlements, with lesser hills, perfect for people to make actual small towns around. While it&apos;s not a lot of height, wind there was super strong. So if you are lean and light weight, please don&apos;t fly off xD&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;https://fknil.com/rss-images/blogs/non-tech/2026/chinaga-betta-hike/amongst-the-clouds.jpeg&quot;&gt;Image: Amongst the clouds&lt;/a&gt;
&lt;a href=&quot;https://fknil.com/rss-images/blogs/non-tech/2026/chinaga-betta-hike/tiny-settlements-between-hills.jpeg&quot;&gt;Image: Tiny settlements between small hills&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;We had a dog guide us the whole time, she was so cute and playful, unfortunately she disappeared when we completed the hike, so we couldn&apos;t treat her :( We also could not carry her to to topmost section as that was a climb on a straight rock. It wasn&apos;t the biggest but not possible for us to do with a dog.&lt;/p&gt;
&lt;p&gt;And yeah that&apos;s it, we came down the same path, but there&apos;s another surprise waiting for you after hike, we stopped by the Swandenahalli Lake. We think it&apos;s a small pond rather than a lake. We chilled there for some time, skipped some stones which itself was good enough to offset lake vs pond disappointment :)&lt;/p&gt;
&lt;p&gt;And that&apos;s it on the way back we tried Pavithra Idli Hotel&apos;s Benne thatte idili, vada and masala dose. They have been cooking since 1942 and are pretty famous, we had to wait for 10-15 minutes to get a seat on a Saturday morning. Benne Thatte idli wasn&apos;t upto to the hype for me, I have had better ones near in Jayanagar. But Masala dose was better and we watered everything off with a hot filter coffee, always the best part for me xD It&apos;s worth a try once :)&lt;/p&gt;
&lt;p&gt;And that&apos;s it, enjoy and have a nice trip.&lt;/p&gt;
&lt;p&gt;References: https://indiahikes.com/documented-trek/chinaga-betta-trek&lt;/p&gt;
</content:encoded></item><item><title>Understanding Snapshots in Apache Iceberg</title><link>https://fknil.com/blog/iceberg-snapshots/</link><guid isPermaLink="true">https://fknil.com/blog/iceberg-snapshots/</guid><pubDate>Wed, 12 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;External Link&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://www.e6data.com/blog/apache-iceberg-snapshots-time-travel&lt;/li&gt;
&lt;li&gt;https://archive.is/VJ7pE&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Sink consistency in RisingWave</title><link>https://fknil.com/blog/risingwave-sink-consistency/</link><guid isPermaLink="true">https://fknil.com/blog/risingwave-sink-consistency/</guid><pubDate>Wed, 12 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;NOTE: I am mostly writing this down to present to someone who is already familiar with the system, but I have laid down some ground work to make it slightly better. Write up is also heavily code referential, so sorry if that&apos;s not up your alley.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/risingwavelabs/risingwave&quot;&gt;RisingWave&lt;/a&gt; is a popular and open-source streaming database, it can work with a variety of different sources and sinks and has capabilities to provide performant real time analyses on streaming data along side service ad-hoc queries. Basically a lot of buzzwords.&lt;/p&gt;
&lt;p&gt;I have grown interest into the system and was trying to understand how it prevents data loss with so many different sinks in case one of it&apos;s compute node dies? We will be looking into handling of iceberg sink cause that&apos;s what I am working with these days. I am going to assume familiarity with iceberg already cause understanding that would take another several blog posts.&lt;/p&gt;
&lt;p&gt;One of the good features of iceberg is it&apos;s decoupling between data files and metadata files. One can take existing parquet files and create a table out of them easily. Work for the &lt;a href=&quot;https://github.com/apache/iceberg-rust/issues/932&quot;&gt;same&lt;/a&gt; is active in iceberg-rust. Even when comitting iceberg writers do the same, write data files first and then try to write metadata files, if they fail (they may fail cause another writer&apos;s commit would cause ACID guarantees to fail on table) they just have to re-generate metadata files and try to commit again.&lt;/p&gt;
&lt;p&gt;So writing data files vs committing are separate processes, same happens in iceberg-rs and hence RisingWave, for iceberg sink these are the locations where each occurs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Writing happens under &lt;code&gt;IcebergSinkWriter&lt;/code&gt; &lt;a href=&quot;https://github.com/risingwavelabs/risingwave/blob/1a6eb0001c806c547de129d4cf66035ec66e4fe1/src/connector/src/sink/iceberg/mod.rs#L863&quot;&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Commiting happens &lt;a href=&quot;https://github.com/risingwavelabs/risingwave/blob/1a6eb0001c806c547de129d4cf66035ec66e4fe1/src/connector/src/sink/iceberg/mod.rs#L1208&quot;&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Getting back to RisingWave, core idea of persisting such state in databases is to use some kind of logs, a lot of databases have their own WAL implementation. RisingWave also leverages concept of log stores for the same. &lt;a href=&quot;https://github.com/risingwavelabs/risingwave/tree/main/src/stream/src/common/log_store_impl&quot;&gt;These&lt;/a&gt; are the current log stores implementation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In Memory Log Store&lt;/li&gt;
&lt;li&gt;KV Log Store&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now our doubt was what if RisingWave compute node crashes before commit happens. LogStores implements &lt;code&gt;LogReader&lt;/code&gt;. &lt;code&gt;LogReader&lt;/code&gt; abstraction shows what &lt;a href=&quot;https://github.com/risingwavelabs/risingwave/blob/1a6eb0001c806c547de129d4cf66035ec66e4fe1/src/connector/src/sink/log_store.rs#L158C22-L179&quot;&gt;all methods&lt;/a&gt; does it provide, namely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;next_item&lt;/code&gt; , read next item in log&lt;/li&gt;
&lt;li&gt;&lt;code&gt;truncate&lt;/code&gt; , increments read offset in log&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rewind&lt;/code&gt; , decrements read offset in log&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These methods are used along side RisingWave&apos;s internal global clock to make sure no data is lost. Hierarchy of internal clock looks like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;barriers&lt;/code&gt; every configurable ms, configurable using &lt;code&gt;barrier_interval_ms&lt;/code&gt; in system params&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkpoints&lt;/code&gt; every N barriers, configurable using &lt;code&gt;checkpoint_frequency&lt;/code&gt; system param&lt;/li&gt;
&lt;li&gt;&lt;code&gt;commits&lt;/code&gt; every N checkpoints, configurable for iceberg sink using &lt;code&gt;commit_checkpoint_interval&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So we can keep reading data async on every barrier using &lt;code&gt;next_item&lt;/code&gt; and keep &lt;code&gt;truncate&lt;/code&gt;ing on every commit. This would ensure we lose no data for different types of sinks.&lt;/p&gt;
&lt;p&gt;Let&apos;s see what happens for iceberg sink:&lt;/p&gt;
&lt;p&gt;Firstly, how &lt;code&gt;LogReader&lt;/code&gt; relates to our iceberg writer. &lt;code&gt;LogReader&lt;/code&gt; is used by &lt;code&gt;LogSinker&lt;/code&gt; , in our case &lt;code&gt;DecoupleCheckpointLogSinker&lt;/code&gt; &lt;a href=&quot;https://github.com/risingwavelabs/risingwave/blob/5dcc141cf86b5b41e7e6965ac7ec840c73aad247/src/connector/src/sink/decouple_checkpoint_log_sink.rs#L80&quot;&gt;here&lt;/a&gt; and that finally calls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For writing:  &lt;code&gt;write_batch&lt;/code&gt; &lt;a href=&quot;https://github.com/risingwavelabs/risingwave/blob/5dcc141cf86b5b41e7e6965ac7ec840c73aad247/src/connector/src/sink/decouple_checkpoint_log_sink.rs#L138&quot;&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;For committing: &lt;code&gt;commit&lt;/code&gt; &lt;a href=&quot;https://github.com/risingwavelabs/risingwave/blob/1a6eb0001c806c547de129d4cf66035ec66e4fe1/src/meta/src/manager/sink_coordination/coordinator_worker.rs#L272&quot;&gt;here&lt;/a&gt;, this follows central clock of barriers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, &lt;code&gt;DecoupleCheckpointLogSinker&lt;/code&gt; also listens to central clock of barrier and writes data files to object store on each barrier &lt;a href=&quot;https://github.com/risingwavelabs/risingwave/blob/1a6eb0001c806c547de129d4cf66035ec66e4fe1/src/connector/src/sink/iceberg/mod.rs#L980-L1090&quot;&gt;here&lt;/a&gt; (i.e. call &lt;code&gt;close&lt;/code&gt; method on &lt;code&gt;data file writer&lt;/code&gt;), but it actually commits the result on every N checkpoints.&lt;/p&gt;
&lt;p&gt;So technically, if barrier and checkpoint values are not same and a compute node crashes between two checkpoints, we would have written data files to object store, but it would not be committed i.e. no metadata files. So these would fall under table maintenance job of &lt;code&gt;orphan files&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This can be mitigated by simply setting &lt;code&gt;checkpoint_frequency&lt;/code&gt;  to 1 i.e. trigger at every barrier and also &lt;code&gt;commit_checkpoint_interval&lt;/code&gt; to 1 i.e. &lt;code&gt;commit&lt;/code&gt; on every barrier/checkpoint.&lt;/p&gt;
&lt;p&gt;Now, how to increase batching size? That can be done by configuring &lt;code&gt;barrier_interval_ms&lt;/code&gt; . Though this could be a bad idea cause barriers are used internally for a lot of other things, they are like &lt;code&gt;ticks&lt;/code&gt; in minecraft engine. So making everything slower for batching can make us lose other system internal state/data leaving system in weird non-recoverable condition.&lt;/p&gt;
</content:encoded></item><item><title>Lets write a Brainfuck Interpreter: Optimizations</title><link>https://fknil.com/blog/brainfuck-jit-interpreter-2/</link><guid isPermaLink="true">https://fknil.com/blog/brainfuck-jit-interpreter-2/</guid><pubDate>Sun, 26 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In last part, we wrote a naive implementation of brainfuck, which is pain-stakingly slow, let&apos;s try to optimize it, we will majorly discuss two major
optimizations in this blog. We will end up with really nice speedups at the end, so buckle up and let&apos;s go!&lt;/p&gt;
&lt;h2&gt;First optimization&lt;/h2&gt;
&lt;p&gt;One of the best things about implementing brainfuck is it&apos;s implementation is simple and straightforward and hence one can find optimization opportunities realtively easily. We don&apos;t try to plot a flamegraph, cause we know most of the time is spent in &lt;code&gt;exec&lt;/code&gt; function, that&apos;s where we execute all of our operations, so any optimizations done in that flow would give us direct noticeable speedups.&lt;/p&gt;
&lt;p&gt;Let&apos;s look at implementations of our operands again, &lt;a href=&quot;https://github.com/feniljain/brenphuk/blob/6b00f84be79c00679dc28ba917b853ff2e18beea/interpreter.c#L66-L142&quot;&gt;this is the core loop&lt;/a&gt; right now. There&apos;s not much to see, implementations for &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;,&lt;/code&gt; are pretty simple and one liner even :P . So let&apos;s have a look at multi-liners i.e. loop implementations, here most hot path would definitely will be finding it&apos;s corresponding loop operand, let&apos;s say we have a program like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[:::[::]:::[::[::]::]]
^1  ^2     ^3 ^4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(&lt;code&gt;:&lt;/code&gt; here means any random operand), we have 4 loops in total, 1 being the parent loops of all, containing 2 and 3 as their immediate child loop and finally 4 inside 3. Let&apos;s say 1 repeats 5 times. In a single interation of loop1 we will be finding end of loop2 once, which would make this find operation happen 5 times. Now let&apos;s say loop3 executes 10 times, for loop4 we will execute find operation 10 * 5 = 50 times, this is wasted computation. We can do this computation once and store it for whole execution of program.&lt;/p&gt;
&lt;p&gt;So do we make a kind of caching mechanism to store just for the inner loops? Technically we also have to jump for outer loops, so we do need jumping index for them too, but only once for most parent loop, and fewer times for depth one loops. What if we precompute all bracket locations? We as such do it while executing, maybe do it before execution starts, and then reference them to jump easily around. Let&apos;s give it a try and see our benchmark results.&lt;/p&gt;
&lt;p&gt;We make an array as big as program size and fill it in with -1 values, at exact index of loop operands we will fill in it&apos;s corresponding loop operands index. So we create two arrays: &lt;code&gt;open_brackets_loc&lt;/code&gt; and &lt;code&gt;close_brackets_loc&lt;/code&gt;. Now just before entering the core loop of &lt;code&gt;exec&lt;/code&gt; we call a new function called &lt;code&gt;fill_brackets_loc&lt;/code&gt;, this takes in program and it&apos;s length and calculates all brackets location along with filling them in our arrays. Implementation is simple, we find a &lt;code&gt;[&lt;/code&gt; and maintain a counter till we find corresponding &lt;code&gt;]&lt;/code&gt;, same as what we did in last blogpost, but we will only do it once this time, at the very start. Code looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void fill_brackets_loc(char *prog, int prog_len) {
  int i = 0, next_open_bracket_loc = -1;

  while (i &amp;lt; prog_len) {
    switch (prog[i]) {
    case &apos;[&apos;: {
      int brackets_depth = 0;
      for (int j = i; j &amp;lt; prog_len; j++) {
        if (prog[j] == &apos;[&apos;) { // found a new loop start operand
          if (next_open_bracket_loc == -1 &amp;amp;&amp;amp; j != i) {
            next_open_bracket_loc = j;
          }
          brackets_depth++; // increase the counter
        } else if (prog[j] == &apos;]&apos;) { // found a new loop end operand
          brackets_depth--; // decrease the counter
        }

        if (brackets_depth == 0) {
          open_brackets_loc[i] = j; // filling in our arrays
          close_brackets_loc[j] = i;
          break;
        }
      }

      if (brackets_depth != 0) {
        ABORT(&quot;brackets mismatch&quot;); // oops didn&apos;t find corresponding loop operand
      }

      break;
    }
    default:
      break;
    }

    if (next_open_bracket_loc != -1) {
      i = next_open_bracket_loc;
      next_open_bracket_loc = -1;
    } else {
      i++;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can even do one better by also storing each &lt;code&gt;[]&lt;/code&gt; identified when transversing nested loops. But for now, this works :P&lt;/p&gt;
&lt;p&gt;Now our &lt;code&gt;[&lt;/code&gt; handler in &lt;code&gt;exec&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;case &apos;[&apos;:
  if (tape[pointer] == 0) {
    int idx = open_brackets_loc[i];
    if (idx == -1) {
      DBG_PRINTF(&quot;[: got bracket_loc as -1 for i: %d&quot;, i);
      ABORT(&quot;invalid state&quot;);
    }
    i = idx;
    continue;
  }

  break;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We directly look up the location of corresponding loop operanding and jump!&lt;/p&gt;
&lt;p&gt;same for &lt;code&gt;]&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;case &apos;]&apos;: {
  if (tape[pointer] != 0) {
    int idx = close_brackets_loc[i];
    if (idx == -1) {
      DBG_PRINTF(&quot;]: got bracket_loc as -1 for i: %d&quot;, i);
      ABORT(&quot;invalid state&quot;);
    }
    i = idx;
    continue;
  }

 break;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running our benchmarks now gives us:
Factor: ~7s
Mandelbrot: ~22s&lt;/p&gt;
&lt;p&gt;That&apos;s some big gains from a simple observation! But wait we have more :)&lt;/p&gt;
&lt;h2&gt;Second optimization&lt;/h2&gt;
&lt;p&gt;Before this occurs, I made a small change to our core &lt;code&gt;exec&lt;/code&gt;, instead of using characters I am using enum variants for identifying each character, it&apos;s essentially the same thing as before just different representation. For the coversion between character operations and enum variants I wrote a simple &lt;code&gt;parse&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;enum Op_type {
  INVALID = 0,
  FWD,
  BWD,
  INCREMENT,
  DECREMENT,
  OUTPUT,
  INPUT,
  JMP_IF_ZERO,
  JMP_IF_NOT_ZERO,
};

void parse(char *prog, int prog_len) {
  int i = 0;

  while (i &amp;lt; prog_len) {
    enum Op_type op_type = INVALID;
    switch (prog[i]) {
    case &apos;&amp;gt;&apos;:
      op_type = FWD;
    case &apos;&amp;lt;&apos;:
      if (op_type == INVALID)
        op_type = BWD;
    case &apos;+&apos;:
      if (op_type == INVALID)
        op_type = INCREMENT;
    case &apos;-&apos;: {
      if (op_type == INVALID)
        op_type = DECREMENT;
      break;
    }
    case &apos;.&apos;:
      op_type = OUTPUT;
      break;
    case &apos;,&apos;:
      op_type = INPUT;
      break;
    case &apos;[&apos;:
      op_type = JMP_IF_ZERO;
      break;
    case &apos;]&apos;:
      op_type = JMP_IF_NOT_ZERO;
      break;
    default:
      break;
    }

    if (op_type == INVALID) {
      i++;
      continue; // this can happen when there are comments which are supposed to
                // be ignored
    }
      i++;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now an interesting optimization I have seen done in Bytecode Interpreters is combining instructions when they occur together way too often. This could happen with same or different instructions too. I learnt about this first time while completing (Crafting interpreters)[https://craftinginterpreters.com/] an amazing book by Bob Nystorm. So let&apos;s try to find if it is possible to combine any instructions in our case. We add an array with size of &lt;code&gt;[number of instructions][number of instructions]&lt;/code&gt;. This is because we want to check how each instruction relates with other ones. At the end of parse function we add this code to make it record op_assoc:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ops[++ops_len] = op;
if (ops_len &amp;gt; 0) {
    // This logic simply tries to unite op_assoc[1][5]
    // and op_assoc[5][1] into one single field
    int op_type_1 = (int)ops[ops_len].op_type;
    int op_type_2 = (int)ops[ops_len - 1].op_type;
    if (op_type_1 &amp;gt;= op_type_2) {
      op_assoc[op_type_2][op_type_1]++;
    } else {
      op_assoc[op_type_1][op_type_2]++;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We try to unite results of form &lt;code&gt;op_assoc[i][j]&lt;/code&gt; and &lt;code&gt;op_assoc[j][i]&lt;/code&gt; into one field &lt;code&gt;op_assoc[i][j]&lt;/code&gt;, cause we don&apos;t want to see associativity of &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;[&lt;/code&gt; and &lt;code&gt;[&lt;/code&gt; and &lt;code&gt;+&lt;/code&gt; as separate results. With this done, let&apos;s try to get output of it for mandelbrot:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DEBUG: op_assoc[1][1]: 3506
DEBUG: op_assoc[1][3]: 438
DEBUG: op_assoc[1][4]: 337
DEBUG: op_assoc[1][5]: 3
DEBUG: op_assoc[1][7]: 498
DEBUG: op_assoc[1][8]: 568
DEBUG: op_assoc[2][2]: 3604
DEBUG: op_assoc[2][3]: 386
DEBUG: op_assoc[2][4]: 246
DEBUG: op_assoc[2][5]: 3
DEBUG: op_assoc[2][7]: 362
DEBUG: op_assoc[2][8]: 521
DEBUG: op_assoc[3][3]: 224
DEBUG: op_assoc[3][7]: 30
DEBUG: op_assoc[3][8]: 86
DEBUG: op_assoc[4][4]: 2
DEBUG: op_assoc[4][7]: 462
DEBUG: op_assoc[4][8]: 133
DEBUG: op_assoc[5][7]: 1
DEBUG: op_assoc[5][8]: 1
DEBUG: op_assoc[7][7]: 10
DEBUG: op_assoc[8][8]: 32
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Highest oens are (2, 2), (1, 1), so repeating instructions, specifically &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;&lt;/code&gt;, these should be easy to club. Let&apos;s do just that, we will add a &lt;code&gt;repeat&lt;/code&gt; field for each operation which will store how many times does the operation repeat. After this we can make exec function increment values by &lt;code&gt;repeat&lt;/code&gt;&apos;s value instead of just 1, after this change our &lt;code&gt;exec&lt;/code&gt; function looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int exec(char *prog, int prog_len) {
  DBG_PRINT(prog);
  int i = 0, val;

  parse(prog, prog_len);
  // print_op_assoc(); // This is for checking which all ops occur together
  fill_brackets_loc();

  while (i &amp;lt;= ops_len) {
    // start = clock();
    switch (ops[i].op_type) {
    case FWD:
      pointer += ops[i].repeat; // We increment by `repeat` now
      break;
    case BWD:
      pointer -= ops[i].repeat; // We increment by `repeat` now
      break;
    case INCREMENT:
      val = (int)tape[pointer];
      val += ops[i].repeat; // We increment by `repeat` now
      tape[pointer] = (char)val;
      break;
    case DECREMENT:
      val = (int)tape[pointer];
      val -= ops[i].repeat; // We increment by `repeat` now
      tape[pointer] = (char)val;
      break;
    case OUTPUT:
      printf(&quot;%c&quot;, tape[pointer]);
      break;
    case INPUT: {
      char ch = (char)getchar();
      tape[pointer] = ch;
      break;
    }
    case JMP_IF_ZERO:
      if (tape[pointer] == 0) {
        int idx = open_brackets_loc[i];
        if (idx == -1) {
          DBG_PRINTF(&quot;[: got bracket_loc as -1 for i: %d&quot;, i);
          ABORT(&quot;invalid state&quot;);
        }
        i = idx;
        continue;
      }

      break;
    case JMP_IF_NOT_ZERO: {
      if (tape[pointer] != 0) {
        int idx = close_brackets_loc[i];
        if (idx == -1) {
          DBG_PRINTF(&quot;]: got bracket_loc as -1 for i: %d&quot;, i);
          ABORT(&quot;invalid state&quot;);
        }
        i = idx;
        continue;
      }

      break;
    }
    case INVALID:
      ABORT(&quot;INVALID shouln&apos;t have leakded till here, there&apos;s a bug in parsing &quot;
            &quot;code&quot;);
    default:
      break;
    }

    i++;
  }

  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simple and easy, let&apos;s benchmark this change:&lt;/p&gt;
&lt;p&gt;Factor: ~2.16s
Mandelbrot: ~5.9s&lt;/p&gt;
&lt;p&gt;And we get another round of massive speedups! Whole code is available &lt;a href=&quot;https://github.com/feniljain/brenphuk/tree/attempt_3&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is where halt our efforts for optimizations, next we are going to learn about JITs from systems perspective, how do we leverage kernel APIs to achieve JITting.&lt;/p&gt;
</content:encoded></item></channel></rss>