Latest Posts (20 found)

On interactive Go tours

Over the past two years, I've published interactive tours for five Go releases, from 1.22 to 1.26. I know some of you have read them, and I've received a lot of kind words from you (even some core Go team members reached out) — thank you so much for that! Tour history: Go 1.22 • 1.23 • 1.24 • 1.25 • 1.26 + Go features by version Unfortunately, at some point, writing these tours stopped being fun and started to feel like a part-time job. I'm not really excited about that, so I've decided to stop. I still like Go (well, most of it). I read a lot of Go code, I write some Go code, and I write Solod code, which is also Go 🙂 (Solod is a systems language with Go syntax and a Go-like stdlib). I'm still pretty close to the language and will probably continue to write about it. But the interactive tours story is over.

0 views

Dot product: Component vs. Geometric definition

The goal of this post is to answer a simple question: why are the following two definitions of the vector dot product in Euclidean space [1] equivalent for vectors \vec{a} and \vec{b} : Here’s a graphical depiction of our vectors (focusing on for clarity, though this applies to any-dimensional vectors). It shows both the components of the vectors and the angle between them. The length of the arrow for \vec{a} is |\vec{a}| . We’ll show two proofs of the equivalence here, the geometric proof and the projection proof . The Appendix describes some properties of dot products that facilitate these proofs. We’ll be using this diagram of our vectors \vec{a} and \vec{b} , as well as the vector \vec{c}=\vec{a}-\vec{b} : Using the law of cosines [2] on the triangle formed by the three vectors: Since for any vector \vec{a} , we have \vec{a}\cdot\vec{a}=|\vec{a}|^2 (see Appendix), let’s rewrite this equation as: But \vec{c}=\vec{a}-\vec{b} and the dot product obeys the distributive property (see Appendix). Therefore: For this proof, we’ll assume the geometric definition is correct and will see how it leads to the component definition. We’ll begin by denoting vectors \vec{e}_1,\vec{e}_2\dots\vec{e}_n as the standard orthonormal basis for . For example, in 2D space, these basis vectors are \vec{e}_1=[1\ 0] and \vec{e}_2=[0\ 1] , shown in this diagram: If we take an arbitrary \vec{a}\in\mathbb{R}^n and calculate its dot product with a basis vector, we can use the geometric definition: where a_i is the component of \vec{a} in the direction of \vec{e}_i . The diagram makes it easy to see why this is true from basic trigonometry, but in the more general case this is just a vector projection . Now let’s represent vectors \vec{a} and \vec{b} as linear combinations of the basis vectors: And calculate the dot product \vec{a}\cdot\vec{b} , beginning by rewriting \vec{b} with its linear combination of basis vectors representation: Using the fact that the dot product distributes over linear combinations: But earlier we’ve shown that \vec{a}\cdot\vec{e}_i=a_i . Therefore: Which is the component definition \blacksquare . A generalization of dot products in is the inner product , which is an operation meeting some specific requirements, defined on a vector space. The inner product is denoted as \langle x,y\rangle:\mathbb{R}^n\times\mathbb{R}^n\to\mathbb{R} , and must satisfy the following requirements for all vectors x,y,z\in\mathbb{R}^n and scalars a,b\in\mathbb{R} : For , we define the inner product operation in its component formulation as: Let’s prove the requirements listed above for this operation; this is fairly straightforward, given the well-known properties of scalar multiplication and addition on : Linearity in the first argument: Positive-definiteness: Consider the components of vector x . Clearly, \forall i\quad x_i\cdot x_i=x_i^2\ge 0 . Since the vector x is not the zero vector, at least one of its components is nonzero, and for that component x_i\cdot x_i>0 . Therefore: Now that we’ve proved all the inner product requirements on our operation \langle x,y\rangle , we can say that is an inner product space with this operation. By meeting these requirements, it can be readily shown that our inner product operation has additional useful properties: The third property is particularly helpful, because it means the inner product is bilinear , and thus is distributive over addition. Note that these are shown for the component definition of dot product. It’s not too hard to prove distributivity for the geometric definition using the notion of projections and how they add up. The norm of a vector x in an inner product space is defined as |x|=\sqrt{\langle x,x\rangle} . Therefore, the square of the norm is |x|^2=\langle x,x\rangle . The norm is used to express the notion of magnitude , or length of a vector. If you think of a vector x\in\mathbb{R}^n in Cartesian coordinates, the definition of the norm is a generalization of the Pythagorean theorem. Component definition: \vec{a}\cdot\vec{b}=\sum_{i=1}^{n}a_i b_i Geometric definition: \vec{a}\cdot\vec{b}=|\vec{a}||\vec{b}|cos(\theta) , where |\vec{a}| is the magnitude of \vec{a} and is the angle between the vectors’ directions Symmetry: \langle x,y\rangle=\langle y,x\rangle Linearity in the first argument: \langle ax+by,z\rangle=a\langle x,z\rangle+b\langle y,z\rangle Positive-definiteness: if x\ne 0 then \langle x,x\rangle>0 \langle x,0\rangle=\langle 0,x\rangle=0 \langle x,x\rangle=0 if and only if x=0 \langle x,ay+bz\rangle=a\langle x,y\rangle+b\langle x,z\rangle \langle x+y,x+y\rangle=\langle x,x\rangle+2\langle x,y\rangle+\langle y,y\rangle

0 views

In defense of not understanding your codebase

As a software engineer, how well do you have to understand your own codebase? My guess is that people who work on small codebases with low-turnover teams (say, Redis or games like The Witness ) would say “obviously you have to understand it completely, otherwise you can’t do good work”. I’d also guess that people who work on large codebases with high-turnover teams (say, the Google web search backend or GitHub) would say “obviously you can’t understand it completely, you just have to do the best you can in your local area”. These are two largely different ways of programming with different methods, practices and cultures 1 . However, the first group is over-represented in online discussion about software engineering 2 . I want to defend the second group against the first. In many software engineering environments, there’s nothing wrong with being in a state of partial understanding. In fact, in large systems a partial understanding is the best you can do. The best articulation of the “you have to understand your codebase” side is Peter Naur’s famous paper Programming as Theory Building . I like this paper, but I think it goes too far in that direction. Naur’s core point is that when programmers work on a program, the code is really just a by-product, and the main product they’re working on is their “theory of the program”. That’s made up of their intuitive sense of what’s happening and why, which can only be partially captured by code or documentation. If they lost the code, they could rewrite the program easily. If they lost their understanding (say, if the team experienced 100% turnover), they would struggle to make sense of the code. So far, so good, but Naur goes further than this. He says that the theory should not be reconstructed from the code. According to Naur, you’re better off scrapping the program entirely and having a new team rebuild it from scratch , building up a new theory in the process 3 : reestablishing the theory of a program merely from the documentation, is strictly impossible … [therefore] the existing program text should be discarded and the new-formed programmer team should be given the opportunity to solve the given problem afresh Anyone who’s been an effective software engineer at a large company knows that Naur is dead wrong about this. There are at least two reasons. First, you simply can’t rebuild large software systems from scratch . Sufficiently large systems (if they have users) contain thousands of weird cases and quirks that cannot be reimplemented. Even a team that’s intimately familiar with the system couldn’t do it: there’s just too much stuff to juggle. Successful rewrites always start by carving out the existing codebase into small isolated chunks, then rewriting one chunk at a time. In other words, rewriting a software system involves making a bunch of changes to the old system. If you can’t change the old system, you certainly can’t replace it with a new one. Second, abandoned systems are revived all the time . In a tech company with hundreds of millions of lines of code and thousands of engineers, it’s not uncommon for a codebase to have nobody left who’s familiar with it 4 . All it takes is a few people to quit at the wrong time, or for a codebase to be unmaintained for a year. Not only have I seen other teams do this, I have personally taken ownership of abandoned codebases, figured them out, and gotten to a point where I could effectively work with them. It takes time, but building a new theory of the codebase is possible. You start by understanding one flow end-to-end, then slowly branch out from there, making careful changes as you go. In sufficiently large codebases, everyone operates with an incorrect theory of the program . The defining feature of modern software systems is that they’re just way too big for anyone (or even a whole team) to keep in their head: nobody understands it all . To be effective, you have to figure out a way to work with a merely partially-correct theory. This is why I keep going on about taking a position and confidence . If you’re not sure about something, you can’t just sit back and wait for someone with a perfect understanding to come and give you the answer. If you’re a competent engineer, that person is you . You have to grit your teeth, make your most educated guess, and then deal with the consequences. To be generous to Naur, it’s possible that in 1985 the average size of a program was several orders of magnitude smaller than today, and that when Naur writes about “large programs” he’s not talking about tens of millions of lines of code. Naur’s first example of a large program is a 200,000 line industrial monitoring program, and his second example is a compiler. In 1987, the first version of the compiler GCC was about a hundred thousand lines of code; in 2015 GCC was over fourteen million lines. I can believe that rewriting one or two hundred thousand lines of code is relatively straightforward, particularly if you get to reuse existing tests. Not so for one or two million. LLMs are often cited as a tool that’s bad because it impedes the ordinary process of theory-building. I think this is overly simplistic. Like many software tools, LLMs are a double-edged sword: they make it harder to construct a detailed mental theory of the software, but they allow you to build a partial theory quickly and they can help you leverage that partial theory more effectively. This is a complex tradeoff that I’m still thinking about. Setting LLMs aside, I’m confident that it’s silly to say that anything that interferes with your theory of the software must be bad. Here is a partial list of other things that make it harder to maintain a theory: Like most things in software, “maintaining a theory of the codebase” is one value among many. Sometimes it’s the most important value and you sacrifice other values for it; other times you trade it off for speed, or legal compliance, or for political reasons 5 . Almost all engineers — particularly “pure” engineers — prefer to maintain an accurate mental model of their software. It’s more fun, less stressful, and feels more like “real engineering”. That’s why many engineers take up open-source projects in their spare time in order to work on small codebases by themselves: in order to do engineering work where they can maintain an accurate Naur theory of the codebase. I don’t think there’s anything wrong with that. However, at work you are paid to do a job . In other words, they pay you money to adopt their set of engineering values. It’s hopefully well-understood that however much you might personally care about performance, sometimes you have to write slow code at your job (for instance, to get a project done on time, or to accommodate some awkward requirement). Maintaining a theory of the codebase is the same kind of thing. I wrote about this at length in Pure and impure software engineering . I think many of the repeated arguments we have in the software industry are caused by the pure total-understanding culture coming up against the impure partial-understanding culture. Open-source engineers are more excited to blog about their work, the raw engineering content is typically more impressive (because coordination problems dominate big proprietary systems), open-source projects can be legally written about while proprietary systems can’t, and even if you could do it legally, writing about large codebases is impossible because it requires too much specific context . I re-read the relevant chapters of Ryle’s The Concept of Mind (which Naur cites throughout) and I think Ryle is more generous about theory-building. For Ryle, theory-building or know-how automatically happens as you do things. It’s fully consistent with Ryle to think you can pick up an existing codebase just from the code, purely by puzzling it out. Naur says: “Lest this consequence may seem unreasonable, it may be noted that the need for revival of an entirely dead program probably will rarely arise, since it is hardly conceivable that the revival would be assigned to new programmers without at least some knowledge of the theory had by the original team.”. If only! Some engineers might say that maintaining a theory is the core value, because without it you can’t fulfill any of the others. I disagree. You could say the same thing about readability, or maintainability, or correctness, or a bunch of other engineering values. We trade off “core” values like this all the time. Other people being allowed to write code in your codebase Having to implement legally-required features like accessibility and data protection Allowing your colleagues to quit their jobs or move between teams Having to upgrade software versions for security patches Bringing in libraries or other dependencies I wrote about this at length in Pure and impure software engineering . I think many of the repeated arguments we have in the software industry are caused by the pure total-understanding culture coming up against the impure partial-understanding culture. ↩ Open-source engineers are more excited to blog about their work, the raw engineering content is typically more impressive (because coordination problems dominate big proprietary systems), open-source projects can be legally written about while proprietary systems can’t, and even if you could do it legally, writing about large codebases is impossible because it requires too much specific context . ↩ I re-read the relevant chapters of Ryle’s The Concept of Mind (which Naur cites throughout) and I think Ryle is more generous about theory-building. For Ryle, theory-building or know-how automatically happens as you do things. It’s fully consistent with Ryle to think you can pick up an existing codebase just from the code, purely by puzzling it out. ↩ Naur says: “Lest this consequence may seem unreasonable, it may be noted that the need for revival of an entirely dead program probably will rarely arise, since it is hardly conceivable that the revival would be assigned to new programmers without at least some knowledge of the theory had by the original team.”. If only! ↩ Some engineers might say that maintaining a theory is the core value, because without it you can’t fulfill any of the others. I disagree. You could say the same thing about readability, or maintainability, or correctness, or a bunch of other engineering values. We trade off “core” values like this all the time. ↩

0 views
Unsung Today

“…or I could click seventy buttons.”

I like Angela Collier’s videos about physics and I was delighted to discover this 18-minute one … = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/or-i-could-click-seventy-buttons/yt1-play.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/or-i-could-click-seventy-buttons/yt1-play.1600w.avif" type="image/avif"> …because it’s a great continuation to the thread about the complexity of Microsoft Office I shared recently. Collier talks about why physicists prefer LaTeX to Word. LaTeX is sort of a nerdy HTML that predates HTML. It looks like this… = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/or-i-could-click-seventy-buttons/1.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/or-i-could-click-seventy-buttons/1.1600w.avif" type="image/avif"> = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/or-i-could-click-seventy-buttons/2.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/or-i-could-click-seventy-buttons/2.1600w.avif" type="image/avif"> …and given how nerdy HTML already is, you might imagine this is a power-user tool that’s chiefly about power and control. But Collier makes the argument that there are some things that LaTeX makes much easier: This is really interesting because it goes right to the core of the uncomfortable truth: naïve design decisions meant to make things easier might achieve the opposite. I shared the ForkLift example where the team didn’t understand what made the previous version great , and more recently the animation that could slow people down . (Of course, there is also the issue of typographical craft of LaTeX documents set in Computer Modern , but let’s save this for another time.) Also, the video starts with Collier apologizing for potentially making the audience feel dumb in a prior video. I don’t think it’s a joke, and I found it thoughtful and refreshing. #attention #complexity #enshittification #flow #youtube there is absolutely no need (or peer pressure) to spend time styling the document by choosing fonts, colors, etc., there is no “live preview,” and making a PDF is a separate step similar to compilation in coding – which means it doesn’t constantly occupy your mind, GUIs can slow you down because the keyboard is faster than the mouse, LaTeX doesn’t give you a lot of control over positioning, which is better than giving you only a semblance of control over positioning ( this is the TikTok meme Collier alluded to briefly ).

0 views

Building intuition about LLM parameter counts

When I was building my GPT-2 implementation in JAX , I started with just token embeddings for the input, and a separate output head (as I was not using weight tying ). It wasn't an LLM -- no Transformer blocks, no attention, no feed-forward networks. I was somewhat surprised when I noticed that even that stripped-down model had 77 million parameters with the "small" settings I was using to train -- specifically, an embedding dimension of 768. However, I realised I shouldn't be -- with a vocab size of 50,257, each of those components is essentially a 768 × 50 , 257 matrix, and that is indeed over 38 million numbers. But the finished LLM at the end of the project was only 163 million parameters -- that meant that the input and output components alone were almost half of it. That felt like a surprisingly large percentage. I had a similar shock when I was first looking into the feed-forward network , and realised that it had roughly twice as many parameters as the attention layers. When we learn about the internals of LLMs, a lot of the focus is on the attention mechanism. This makes sense -- it's the hardest part to get your head around. The rest of the setup, at least for simple GPT-2 type models, is fairly standard stuff. But that means that it is easy to overestimate how much of the total parameter count of the model attention uses up -- especially for smaller models, where the token embeddings and the output head are so large in comparison to the Transformer layers that make up the actual body of the LLM. OpenAI released GPT 5.6 today, so I decided to take its "Sol" variant for a ride in Codex and asked it to write a visualiser . It shows breakdowns of how the parameters are split between embeddings, attention, the FFNs, and the output head for different sizes of GPT-2 models (or your own custom settings with the same architecture), and you can also add/remove weight tying and QKV bias. It did a really good job -- check it out! Here's a screenshot of what it showed for GPT-2 small without weight tying. It's well worth a play. In particular, it's interesting to see what happens as the number of tokens in the vocab gets very large (many modern models have hundreds of thousands). You can very easily create a "tiny" model which is almost entirely embeddings and the output head.

0 views

The Persistent Gravity of Cross Platform

This week’s discussion of the ChatGPT app and its move to Electron merits a link to my evergreen article The Persistent Gravity of Cross Platform : At the highest level, cross-platform UI technologies prioritize coordinated featurefulness over polished simplicity. I’ve added a coda to that article about how coding agents actually strengthen the argument for Electron on large teams, at least for now. The initial release of the new ChatGPT app has been clumsy – there’s a lot of work to do to get Electron ChatGPT (née Codex) as polished as it should be. But, like it or not, cross-platform code is the least-bad way to coordinate a massive team on a rapidly changing product.

0 views

How to Create Your Own Decentralized Messenger Protocol

Ever wondered how to build a decentralized messenger without any central servers? It's all about federation - just like email! In this post, I'll show you how to design a simple protocol from scratch, from server discovery using .well-known files to handling end-to-end encryption.

0 views

Premium: The Hater's Guide To The Memory Crisis

Hi premium readers! I’ll be taking a week off of the premium next week — July 17 — to have some well-earned rest. This will mark only the second time I’ve missed a premium piece since I started this newsletter in June 2025, and I hope you’ll forgive me for the (short) break. Don’t worry. Today’s piece is also an absolute banger. Everything’s more expensive, and it’s all AI’s fault. It really is that simple.  An AI data center is full of servers, which are in turn full of (for the most part) NVIDIA GPUs. Each NVIDIA GB300 has two B300 GPUs, the two of which have 576GB of High Bandwidth Memory (HBM, or HBM3e to be specific), and a CPU, which has 480GB of lower-power LPDDR5X RAM (the kind usually used in cellphones and other mobile devices). These systems tend to be sold in an NVL72 rack with 18 compute trays, bringing us to 36 GB300s , for a total of 20.7 terabytes of HBM and 17 terabytes of LPDDR5X RAM, and that’s before you get to the RAM associated with the high-speed networking gear and other associated components. Analyst estimates have the cost of the high bandwidth memory of a single NVL72 GB300 at around $15.27 per gigabyte, for a total of around $316,000 of HBM, and while I can’t seem to find a stable source for pricing around LPDDR5X, I think a fair estimate is around $4 per gigabyte based on this piece , so around $68,000 worth per NVL72 rack. At around 150kW of power draw per NVL72 , a 1GW data center (with 740MW of critical IT load) would have around 4,933 NVL7s racks — for a total of $ 1.894 billion in HBM and LPDDR5X costs, or around $2.559 million of HBM and LPDDR5X RAM per megawatt of IT load.  Oh, and each of these NVL72s can hold as much as a petabyte of expensive solid state storage, costing an additional tens of thousands of dollars.  Because HBM takes up more space on a wafer — the slice of semiconductor material that is etched using photolithography ( read: molten tin ) and then cut into separate dies (individual chips) — and generally has much higher margins (thanks to the triopoly of Samsung, SK Hynix and Micron), memory manufacturers are dedicating more space on their manufacturing lines to it than to regular consumer RAM, which allows (thanks to said triopoly) said manufacturers to charge effectively whatever they want for consumer RAM. And thanks to AI — to quote Tom’s Hardware and Counterpoint Research — NVIDIA is buying that LPDDR5X RAM at the scale of an Apple or a Samsung: The net result is pretty simple: every single consumer electronic of any kind is getting more expensive. Valve’s Steam Machine console debuted at a 30% higher price point than planned , Apple hiked the prices of its MacBooks and iPads and will likely have to do the same for its next iPhone . Nintendo , Microsoft and Sony increased the cost of their consoles, and the PS5 and Xbox Series now cost more today than they did when they first retailed, almost six years ago.  On the Android front, Samsung has bumped the price of its Galaxy smartphones , and manufacturers in this space (which tends to have smaller margins than those enjoyed by Apple) are likely to limit the number of new devices shipping with 16GB of RAM, as well as re-introduce models with 4GB of RAM   .  Meanwhile, memory manufacturers are having record quarters, with Micron’s revenue quadrupling year-over-year in Q3 2026 and its gross margin improving by ten percent (from 74.9% to 84.9%) quarter-over-quarter, and Samsung’s profits growing from $38 billion to $59 billion quarter-over-quarter thanks to the spiralling cost of revenue caused by…well…the companies setting the price of memory at whatever they’d like. This is a problem caused by the fact that these three companies — SK Hynix, Micron and Samsung — produce more than 90% of the world’s RAM, which is why there’s a price fixing lawsuit against them , per Polygon: To be clear, HBM is more expensive to make than regular RAM, and takes up significantly more space ( about 4x more ) on the wafer, but because of the incredible demand for AI servers, Samsung, SK Hynix, and Micron can charge effectively whatever they want for it, much like they are for the regular RAM that’s in short supply. The same is becoming increasingly true for the solid state storage that these companies (and others like Sandisk) sell too. Now, you may think it’s a little rich to suggest that memory manufacturers are colluding to rig their prices, perhaps a little judgmental , and you’d be wrong because they’ve done it before. Quoting Polygon again : To be clear, I am not saying — nor can I prove — that there is any kind of price-fixing or collusion going on. Nevertheless, there are three companies that effectively make all the world’s RAM, all raising prices at the same time, all seeing record profits, all riding high at a time when everybody else is suffering as a direct result.  The Wall Street Journal put it best : What makes this particular memory crisis so distinctly dangerous is that it isn’t a result of consumer demand so much as it is capital expenditures from very large companies making bets that don’t connect with reality.   Microsoft, Google, Amazon, and Meta aren’t spending $765 billion in capex in 2026 because of rapid demand by consumers for AI services, but a desperation caused by a lack of hypergrowth ideas , circular financing with Anthropic and OpenAI , and a vague concern that if they stop spending that the other guy will do something as a result. As I discussed earlier in the week , nobody can make a compelling case for building more data centers other than “we must do so, because of AI.” Nobody is having trouble accessing ChatGPT, Claude or another major AI service because of a lack of compute, outside of Anthropic and OpenAI’s continual rapacious hunger for more compute that doesn’t ever seem to involve them turning away business. While price increases generally help moderate demand for goods or services, none of that matters when you have four companies willing to spend a trillion dollars a year on the off chance that they might get something out of it .  As a result, Micron, Samsung, and SK Hynix can charge effectively as much as they want, and NVIDIA and others building black holes for AI capex can then pass those costs onto Microsoft, Google, Amazon, and Meta, who have given themselves a blank check to build whatever it is that they think will come out of the large language model era. Put another way, the capex spend of four of the largest companies of the world — all of whom are now funding their capex using debt — has now led to the single-largest increase in the price of consumer electronics in history, for the most part thanks to one company, NVIDIA, becoming the largest purchaser of HBM in the world because those four companies are buying so many GPUs.  To give you an idea of how bad that is, NVIDIA takes up roughly 65% of all high bandwidth memory, with the other 35% (mostly) going to specialist ASICs from Google and Amazon, and AMD’s Instinct line of AI GPUs.  This is a unique — and uniquely dangerous — bubble, because demand isn’t based on actual revenues or events happening outside of those in the imaginations of Sundar Pichai, Mark Zuckerberg, Andy Jassy and Satya Nadella. They didn’t start buying these GPUs because consumers demanded them. In fact, they did so without really checking whether consumers gave a shit, which is why I’m so worried about what comes next.  Only 23% of total DRAM wafers are taken up by HBM , but it’s accounting for a remarkable chunk of revenues, at least for SK Hynix, where it took up 40% of all DRAM sales back in Q3 2025 , the most-recent number I can get.  While I can’t find definitive numbers from Samsung or Micron, the situation is bad no matter which way you spin it. Either they’re increasingly-relying on HBM as a revenue driver to the point it’s crowding out the revenue from their other DRAM businesses (making them dependent on GPU and ASIC revenue), or their revenues are spiking because they’re able to crank up the cost of DRAM. This is setting everybody up for a dramatic and painful collapse, largely based on the strange nature of how memory is built and sold, unless cooler heads prevail and capex doesn’t accelerate based on hopium.  What happens when hyperscalers reduce their capex, or when banks stop issuing data center debt ? NVIDIA stops needing all that HBM, which means any and all capex dedicated to expanding manufacturing  infrastructure to produce more HBM — which is not particularly valuable outside of AI GPUs — will have been built to capture demand that doesn’t exist. While that capacity could be re-engineered to make useful DRAM with mass appeal, doing so will also drag down the profits of every memory manufacturer in the process, creating a supply glut the likes of which we’ve never seen in history.  The memory industry has gambled its financial future on the idea that there’s near-infinite amounts of capital available for data center capex, adjusting its supply chains and fabs to focus on scooping up demand that’s increasingly only made possible by the availability of debt. Microsoft, Google, Amazon and Meta have turned NVIDIA into a single point of failure for the entire tech industry, creating a painful present for consumers and a brutal future for suppliers, all because they decided to spend more than a trillion dollars on a dead end industry. The longer it takes for hyperscaler capex to retract, the more expensive everything becomes. The more GPUs that get sold, the more capacity that gets put toward high bandwidth memory, and the more that Micron, SK Hynix and Samsung can charge for it, which makes it more expensive to buy AI GPUs, which increases the amount that hyperscalers are spending on AI capex for effectively the same amount of gear. The longer that hyperscalers sustain this pace, the larger the return needs to be, and at this point, none of them have disclosed their AI revenues, which heavily suggests there’s yet to be a dollar of profit.  Yet the more they commit, the more committed they have to be. Pulling back at this point will prove to the markets that they’ve committed to too much capacity. Yet not pulling back means that hyperscalers will continue to turn their free cash flows negative in pursuit of an indeterminate goal. It’s a vicious cycle made worse by the fact that every spin of the capex wheel increases the price of just about every consumer electronic in the world , creating a market-wide inflation for what amounts to a speculative asset bubble. And If even one hyperscaler cuts their capex, the cartel-like memory industry is in for a nightmare scenario, one larger and uglier than any they’ve ever faced.  In the end, it all comes down to whose problem this high bandwidth memory becomes. Will SK Hynix, Samsung, and Micron have already built the RAM and face waves of cancellations, resulting in a bunch of fallow inventory it can’t use or sell? Or will they already have shipped it off to NVIDIA and ASIC builders, only for it to sit in warehouses waiting for the day it can finally be melted down? Who will end up holding the bag? The cartel of horrible fab-gargoyles, Jensen Huang’s Wallet Inspection Firm, one of the four simpleton hyperscalers, Broadcom, or one of the Taiwanese ODMs?  Just to be clear: everybody loses, unless the AI bubble continues in perpetuity. This is the Hater’s Guide To The Memory Crisis — and the terrible tale of the boom-and-bust memory industry.

0 views

Downsizing

With the 150th interview of People and Blogs now live, it’s officially time to downsize my online presence again. My digital life follows a somewhat regular rhythm and I alternate through phases of expansion, where I buy domain names, ship new projects, start newsletters, and chase a million ideas, and phases of contraction, where everything happens in reverse: domains are left to expire, projects are archived, newsletters are deleted, services are cancelled. And my recent decoupling from the web was the beginning of one of these downsizing phases. The Dealgorithmed newsletter has been deleted; the domain is not going to be renewed, and it will expire later in the year. My From the Summit newsletter and my personal newsletter have been merged into a single new newsletter called “ Thoughts and Walks ”. If you were already subscribed to one of my newsletters, you can manage your preferences from the Buttondown’s Portal and decide what type of content you want to receive. I'll write a more in depth post about my plans for the newsletter. The only project that has survived the cut—aside from this blog—is blogroll.org, and that is not going anywhere anytime soon because there are things I want to add to that site. But more on that at a later time. Decluttering is fun! It's a nice mental exercise to delete stuff and become lighter again. Thank you for keeping RSS alive. You're awesome.

0 views
Unsung Today

The adjective of the present or the verb of the future

My arch nemesis lives only about 1.5 blocks away from me. It’s a coffee shop door. More specifically, it’s a sign on that door: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-adjective-of-the-present-or-the-verb-of-the-future/1.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-adjective-of-the-present-or-the-verb-of-the-future/1.1600w.avif" type="image/avif"> This is what happens with embarrassing regularity: I am inside, about to step out, my brain reads PUSH from the other side – and so of course, like an idiot, I push the door instead of pulling it. Sure, bad design. But don’t worry, I am not going full Don Norman on you. I wanted to show you this other thing, in Pixelmator Pro: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-adjective-of-the-present-or-the-verb-of-the-future/2.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-adjective-of-the-present-or-the-verb-of-the-future/2.1600w.avif" type="image/avif"> A pretty non-threatening menu, it seems, but sometimes when I see a treatment like this, my brain actually sees this… = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-adjective-of-the-present-or-the-verb-of-the-future/3.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-adjective-of-the-present-or-the-verb-of-the-future/3.1600w.avif" type="image/avif"> …and it takes just a bit of extra thinking to figure out where I am and where I’m going. This is one of the recurring boolean problems in UX design. Given a choice, do we show the noun/​adjective of the present, or the verb of the future? Because another way would be to show the current state: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-adjective-of-the-present-or-the-verb-of-the-future/4.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-adjective-of-the-present-or-the-verb-of-the-future/4.1600w.avif" type="image/avif"> To me, this is unambiguous; the state is easy to understand visually without thinking, and the implied flip action also feels pretty natural. You could go even further: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-adjective-of-the-present-or-the-verb-of-the-future/5.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-adjective-of-the-present-or-the-verb-of-the-future/5.1600w.avif" type="image/avif"> Without knowing much of the context here, this would be my recommendation. Of course, this last configuration not only implies toggling but also implies showing , but that’s probably okay given all the context surrounding it? Now, like with many things I talk about here, I don’t have the benefit of user testing or research. (In practice, though, they aren’t often available for small things like this, anyway.) Also, this isn’t a universal recommendation. This is an evergreen UX problem for a reason. If there were other commands around it, the showing/​hiding verbs might have to appear. Same if no option had a checkmark by default. (One or two checkmarks establish an implied “show/​hide” verb for the whole section, but without any, it might feel like an unusual menu filled with only nouns.) There are more conventions – “Turn X On,” showing both options, submenus – each one with pros and cons. It’s good to be aware of all, because even if your tool uses one consistently, users might bring a different one as a default way of processing things. But the worst part about the Pixelmator menu is that it’s mixing conventions: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-adjective-of-the-present-or-the-verb-of-the-future/6.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-adjective-of-the-present-or-the-verb-of-the-future/6.1600w.avif" type="image/avif"> It’s hard for me to understand the rationale here, and it makes processing this menu even harder. Maybe I need to go to a certain neighbourhood coffee shop to get more coffee… #interface design #writing

0 views
Stratechery Yesterday

2026.28: XBOX On the Rocks

Welcome back to This Week in Stratechery! As a reminder, each week, every Friday, we’re sending out this overview of content in the Stratechery bundle; highlighted links are free for everyone . Additionally, you have complete control over what we send to you. If you don’t want to receive This Week in Stratechery emails (there is no podcast), please uncheck the box in your delivery settings . On that note, here were a few of our favorites this week. This week’s Asianometry video is on TOTO: From Toilets to E-Chucks . A Word from Mark Zuckerberg*.  I was delighted to see Ben insert himself into the CEO chair at Meta on Tuesday and write a script for Mark Zuckerberg as he tells the story of Meta and its AI investments in 2026. That article traces past Meta mistakes as well as those of investors who doubted the company, all to frame current investments in AI and the massive opportunities that remain central to the Meta’s future. A combination of history, analysis of the future, and fun, it’s a perfect summer read. As for a summer listen, we doubled back on all of it, plus Meta’s Muse-Spark release, for this week’s episode of Sharp Tech .  — Andrew Sharp Pulling the Plug on XBOX? It’s been years since there was good news coming out of the XBOX division at Microsoft and that trend continued this week, as XBOX CEO Asha Sharma announced plans to eliminate 3,200 jobs, or around 20% of its staff over the next 12 months. Wednesday’s Daily Update explores how Microsoft arrived at this point and why, in particular, the Game Pass initiative that was the last great hope for XBOX has been a failure. I’m not a gamer, but Ben’s rendering of the XBOX story — and the Game Pass story — is a great case study of both internet economics and management mistakes (and analyst ones!). — AS Toilet Talk . Look, I get that’s a little weird, but if there is one brand of household appliances that I cannot imagine living without, it is in the bathroom. Specifically, I absolutely love my Toto toilet, and was delighted that Jon made a video about the company on Asianometry . Here’s the twist: the reason why Toto is a subject of interest isn’t their toilets, but rather the fact the Japanese company also plays a critical role in the AI supply chain. — Ben Thompson A Script for Mark Zuckerberg — A script for what Mark Zuckerberg should say on Meta’s next earnings call. XBOX Cuts; Bundling and the Internet Solvent; Transaction, Coordination, and Sunk Costs — Microsoft’s Xbox division is conducting big layoffs, as the company deals with abject failure of its Game Pass strategy. Muse Image, Grok 4.5, Alex Karp on CNBC — The battle for verifiable data is increasingly defining the AI race, from Meta to Grok to the frontier labs. Online Insanity and Its Counterpoint — What we can and can’t achieve in response to paranoia and extremism online. The New ChatGPT App The Debt-Fueled Collapse of China’s Top Machine Tool Maker RCA and the Vacuum Tube’s Last Stand A Missile Test and New PLA Generals; The CITIC Plane Crash; America’s Taiwan Interests; Guo Wengui Jailed and Ezra Jin Released A Tale of Two Cities and Jaylen Brown, Minnesota’s Bet on LaMelo, Peterson Arrives and Mitchell Cashes Out Meta and Its Messaging Problem, The XBOX Reset, Q&A on Token Costs, American Soccer, Starlink in Nature

0 views
David Bushell Yesterday

Astro is fine I guess

When I’m not fighting WordPress I deliver static HTML or the occasional JavaScript framework integration. For personal projects I have ‘fun’ with my own static site generator . This week was a side quest (soon to be main quest) to build my new company website. We’re talking proper business here so I can’t be messing about. I figured an off the shelf SSG would be most suitable. I asked the socials, “ 11ty or Astro ?” Both are popular but Astro had the edge. I gave Astro an early spin back in 2022 and found it slow . Maybe it’s good now? I ran with minimum release age to avoid immediately getting pwned . I selected Astro’s “Use minimal (empty) template” option and it generated both an and file — are you f — deep breaths, don’t fall for the rage bait. I code in a modern editor so I installed the recommended Astro extension. At first I struggled with Zed recognising HTML. I discovered a restart temporarily fixed the issue, but I guess I restarted one time too many because now the Astro LSP is completely broken. No modern comforts for me then. At least I can look at HTML without the red squigglies. I know what you’re going to say, “Dave bro, you’re inflicting this pain upon yourself! Just write HTML!” And I should. I just want native no-framework HTML includes , you know? Can you imagine the civilisation we’d live in if that could happen? I persevered and got my templates built with minimal fuss. I added a markdown collection and got the blog part blogging. It’s obvious that people use Astro to build real websites because all my “how do I” questions had an answer in the documentation. I’ve been forced to deploy way too many “React spaces” in my templates because Astro’s whitespace treatment is a mystery. I don’t need many components so I haven’t gone deep on Astro vs JSX . My site has zero JavaScript on the front-end. I plan to keep it that way. Edit: Christian Niklas on Mastodon shared a link to a recent Astro update where they added a option that defaults to no longer “following HTML rules.” Umm… okay. Set this to or if you’re building a website? I set it to . Minifying whitespace is over-optimisation. Astro has got the job done, despite the developer experience being broken out of the box. I dread to think what graveyard of dotfiles is installed if I choose a non-minimal start. I can easily de-Astro my templates should I need to. Right now Astro is solving the right problems and the issues are but a nuisance. Final conclusion: Astro is fine I guess. I’m not convinced Cloudflare’s acquisition is a good thing, considering their record for performative slop. I’ve lost my enthusiasm for DX and tooling to be honest. Even my own SSG experiments are collecting dust. I’d call the ecosystem a lost cause if I was being dramatic. I just try to avoid the worst of it and care about the end product: shipping a damn fine website! Which I can’t do because I’ve got more businessing to business before this particular site sets sail. Maybe in a few months? It’s looking awesome on though. Thanks for reading! Follow me on Mastodon and Bluesky . Subscribe to my Blog and Notes or Combined feeds.

0 views
Jeff Geerling Yesterday

QuadRF can spot drones and see WiFi through my wall

The QuadRF (pictured above) a phased-array radio built around a Raspberry Pi 5 and an FPGA board with picosecond-level timing. It does advanced signal processing and beamforming. It can see WiFi through walls and track drones in flight. If the open source community can come up with something like this, just imagine what governments are capable of. When you plug a computer into a network, tools like Wireshark can show all the hidden traffic you might not even know is there. WiFi packets are the same, but those travel through the air, allowing snooping without physical access.

0 views
Kev Quirk Yesterday

A Rant About Modern Cars

I recently bought a new Peugeot and the experience of getting setup on their online platform has been painful to say the least. Yesterday I picked up my shiny new (to me) Peugeot E-3008 GT. It's a beautiful car with lots of bells, whistles, and toys. I had my little MG EV for around 2.5 years, and it served me well, but I wanted something bigger, with more range. So I opted for the Peugeot. Anyway, since this is a modern car, it no longer comes with an owner's manual. Instead you need to install an app and read the manual there. So I did that and duly signed up for a Peugeot Connect account - all standard procedure in this internet age we find ourselves in. That was until it came to generating a password. I did my usual and generated a 30 character, complicated password with Bitwarden , only to be greeted with this ridiculous password complexity error: So my 30 character , random string password is apparently weak and the only way to make it secure is reduce it's length (and complexity) by ~50%. Not only that, I had to abide by a slew of other arbitrary rules along the way. I tried to generate a 16 character PW with Bitwarden a couple times, but the error persisted. So I ended up jumping over to Gemini, pasting the requirements in, and asking it to give me a password. Being the sycophantic AI that it is, it spat out a password that conformed to Peugeot's ridiculous rules. Or so I thought... OK, so the password Gemini generated for me was . Let's see how it stacks up to the requirements: So why the fuck is the password still being rejected as too weak ? I assume it's poor wording on Peugeot's part, but I ended up just typing gobbledegook into the field until it passed. Interestingly, also passed and was reported as a "very strong" password: For the record, is NOT a very strong password. Don't use that. Ever. I'm astonished that this is still an issue in 2026. Why on earth can't manufacturers get this simple shit right? It's basic stuff. All you're doing here is forcing people to use shitty passwords. I finally got into my bloody Peugeot account and tried to enable to Connect features so I can do things like control air-con from the app, only to find that it costs £90 (~$120) per year! This isn't a piece of hardware that I'm paying for. It's literally £90/year for a switch to be flipped in some software. Utter. Fucking. Robbery. Peugeot, you should be ashamed of yourselves. Aside from this, the new car is lovely. 🙃 Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️ You can reply to this post by email , or leave a comment . 8-16 characters ✅ An uppercase letter ✅ A lowercase letter ✅ A special character from the list ✅ No sequential characters ✅

0 views

Go-flavored concurrency in C

Go's concurrency is one of the main reasons people like the language. You write , send values through channels, and the runtime scheduler runs thousands of goroutines on just a few OS threads. It feels effortless. None of that machinery exists in C. Which made me wonder: how close can you get to Go's concurrency model using only POSIX threads? Obviously, native OS threads can't match the efficiency of lightweight goroutines, but what is the actual cost, when does it become a problem, and is there any way to at least partially avoid it? I ran into these questions while adding concurrency to Solod (So), a strict subset of Go that translates to plain C, with no runtime and no garbage collector. In the end, I came to the conclusion that you can do quite a lot with pthreads — as long as you're honest about the tradeoffs. This post is about the POSIX threads-based concurrency model I chose, the benefits it offers, and its limitations. Mutex/Cond • Atomics • Pool • Channel • Performance • Design • Wrapping up Everything in So's concurrency stack is built on two basic POSIX primitives: the mutex and the condition variable. is a thin wrapper around : Since So translates to C, this is basically a struct that holds a and a function that calls . Here's the transpiler output: That is the whole translation — the generated C is a near-mechanical mirror of the So code, only noisier. From here on, I'll mainly show the So version, but I'll also provide the C code for those who are interested. There's nothing exciting here: is a pthread mutex wrapper that panics if something goes wrong (which is rare). The companion primitive is , a wrapper around . It's the standard "wait until a condition holds" tool, associated with a mutex: These two types — and — are the foundation. Other concurrency tools — , the thread pool, channels — are built using a mutex and one or more condition variables. This has several effects on performance, as we'll see later. Not everything needs a lock. So's mirrors Go's: , , , , , and a generic , all with , , , and methods. The nice thing is that these don't need pthreads at all. They map directly to the C compiler's builtins — the same hardware instructions that Go's compiler emits. So there's no reason for them to be any slower, and they're not: Each number is the cost of one operation on a single thread. is a good example of using atomics effectively. Its fast path only needs a single atomic load — after the given function runs, every future call to checks a flag and returns: To actually run code concurrently, you need threads. The type wraps and its related functions: Consider this function: Usage example: It might look like , but that's just on the surface. starts an actual OS thread, not a goroutine. You have to eventually call to join or it, or else its resources will leak. Also, OS threads are expensive to create — they're nothing like Go's goroutines, which only need a few kilobytes of stack and start up in nanoseconds. That's exactly why you usually don't want to call inside a loop. For tasks that are short-lived or happen often, it's better to use a pool of long-lived worker threads and send tasks to them. to the rescue: Usage example: The first argument to , , is a memory allocator. Solod avoids hidden allocations, so anything that needs memory takes an allocator explicitly — here it backs the pool's task queue. Under the hood, a is a fixed group of worker threads that pull tasks from a shared queue (a ring buffer). It uses one mutex and a few condition variables: wakes up a worker when there are tasks to do, applies back-pressure when the queue is full, and lets know when everything is finished. It's a classic producer-consumer setup, about 200 lines of code , and there's nothing fancy about it. The heart of the pool is the worker loop. Each thread blocks until a task appears, runs it outside the lock so workers execute in parallel, then records that it finished: This is what separates a pool from a plain queue. bumps as it enqueues; each worker decrements it after running a task, and the last one out broadcasts . sleeps until the count hits zero: The tradeoff is that the number of worker threads is fixed. In Go, a program can handle thousands of concurrent I/O waits because blocked goroutines use very little memory. A So pool can't do this — if all N workers are parked on a blocking syscall, the pool is stalled until one returns. You have to set the pool size based on the workload, instead of letting the runtime manage it for you. Channels are an important part of Go's concurrency model, and So's gives you something quite similar. Just like in Go, it passes values by copy and comes in buffered and unbuffered flavors: is a thin generic shell over one of two engines, picked at creation time: Buffered ( ) is a mutex-guarded ring buffer with and condition variables — like the queue. Senders block when it's full, receivers block when it's empty. The full implementation also checks for , but I left it out for brevity. is the mirror method: block while empty, pop the next value, signal to wake a sender. It also handles the closed channel, returning once the buffer is closed and drained. The rest is this lock-wait-signal core. Buffer source code Unbuffered ( ) is a rendezvous: each send blocks until a receiver takes the value, copying bytes directly from the sender's stack to the receiver's destination without using an intermediate buffer. is the other half: it waits for a published, unclaimed value, copies bytes straight from the sender's stack into (no intermediate buffer), marks it as claimed, and broadcasts to wake the sender back, creating wakeup #2. One hand-off, two wakeups. Copying directly from the sender's stack is safe because of that second wakeup. is a pointer to , which lives on the sender's stack. While the receiver is reading it, the sender is parked in , so its stack frame stays alive. The sender only returns (and reclaims that memory) after the receiver sets and wakes it up. There's no need to copy into a shared buffer because the source is guaranteed to outlive the read. Rendezvous source code As you can see, the API is pretty similar to Go. Now let's look at the numbers. Here's the main tradeoff: pthread-based concurrency primitives are fast when no one has to block, but they get slow when someone does. And it's always for the same reason. Go schedules goroutines in userspace. When one goroutine blocks on a channel and another wakes it up, the runtime moves them between its own queues — no kernel involved. POSIX threads, on the other hand, don't provide a userland scheduler. When a thread blocks on a condition variable, it parks in the kernel, and waking it up requires a syscall. Every hand-off between threads that actually parks pays the cost of a syscall on both ends. You can clearly see the difference in the mutex benchmarks. With 8 competing threads, it all comes down to whether the waiting threads have to park or not: Each number is the average time for a single / pair. The uncontended benchmark runs on one thread, while the contended benchmarks have multiple threads fighting over the same mutex. Notice that So actually wins the first two benchmarks, and for good reason. So's is a plain call with nothing extra, while Go's adds more overhead — like starvation-mode tracking and a runtime that stays involved because a goroutine can be preempted in the middle of a critical section. When nobody parks, that overhead is the main cost, and the thinner wrapper is closer to the hardware. With an empty critical section (the spin benchmark), a waiting thread grabs the lock while still spinning and almost never parks — So wins by 2.8x. The uncontended benchmark (a single thread, no contention) shows the same thing: less code between the call and the lock, so 9ns versus 14ns. The picture flips the moment threads have to park. Give the critical section about a microsecond of real work (the work benchmark) and waiters exhaust their spin budget and park. Now every hand-off costs a wakeup syscall, and So drops to half of Go's throughput. The work is identical in both cases — the difference comes from the parking cost. Condition variables demonstrate this clearly because they always park: Each number is the cost of one rendezvous round: a single broadcast that wakes every waiter and hands control back, with N waiters plus one broadcaster. Pthread-based condition variable is consistently 7-10 times slower. There's no trick to close this gap — it's just the cost of waking up a real OS thread instead of a goroutine. Channels have the same issue because they're built using mutexes and condition variables: Each number is the cost of moving one value through the channel (send plus its matching receive). The number in parentheses is the buffer capacity. The uncontended case fills and drains a buffer from a single thread, so nothing ever blocks — it's just a lock plus a copy, which gives So a slight advantage. But the moment a producer and consumer actually start handing off work, So has to wake up a thread for every transfer that gets parked. It's worst for the unbuffered channel, where every value is a rendezvous with two wakeups: 23x slower. A larger buffer helps a lot — with room for 100 items, most sends go through without waking anyone, and the gap narrows to about 2x. The consequence is that the larger your tasks are, the better pthread-based concurrency works. If you use a channel for fine-grained, value-at-a-time streaming between threads, performance will suffer. But if you use a channel to pass whole work items to a pool, where each item takes tens of microseconds to process, the wakeup cost becomes negligible. The pool benchmarks on realistic workloads confirms this: Each number is the wall-clock time for 8 workers to process the whole batch. Here, So is within 1.1x of Go. The per-task dispatch cost is still present, but it's spread out over real work, and the performance penalty is pretty small. Benchmarking All benchmarks were run on an Apple M1 CPU running macOS. The C code was compiled with Clang 16 using these CFLAGS and mimalloc as the system allocator: The results shown are the medians from several benchmark runs. Each benchmark ran many iterations, following the same logic as Go's own benchmarking. The Go benchmarks used Go 1.26 and . Source code for both So's and Go's benchmarks: conc • sync Here's a summary of the strengths and weaknesses of the pthread-based approach: If you're looking for "thousands of cheap goroutines", the pthread-based approach will let you down. But if you're fine with "a few worker threads handling lots of tasks", it holds up well. Three decisions influenced the way I implemented concurrency in Solod. Pthreads, not fibers . I know there are coroutine/fiber libraries for C that avoid the kernel wakeup cost — single-threaded ones like neco , and multi-threaded ones like libfiber . A userspace scheduler is exactly what would help to match Go in the benchmarks above. I decided not to use one. I wanted something dead simple — an approach I could explain in a paragraph, using tools every C programmer already knows. The trade-off is that you lose some performance with fine-grained blocking, but in many real-world situations, pthreads work fine if you use a worker pool. For me, keeping things simple is more important than saving a few microseconds during task hand-offs. For now, at least. Standard library, not language . Go bakes goroutines, channels, and select right into the language. I decided to keep everything in the stdlib for two reasons. ➀ It follows So's "no hidden allocations" rule. In Go, quietly allocates a goroutine stack, and allocates a buffer. In So, all allocations are explicit: you pass an allocator to and , and you always know exactly where the memory comes from — whether it's the system allocator, an arena, or something else. ➁ A library is more flexible. Since a pool is a regular value, you can have as many as you need, each sized for its specific purpose. In a multi-stage pipeline where each stage needs a different capacity, you can start one pool per stage, each with its own and , instead of being given a single global scheduler. The language stays simple, and the flexibility is in code you can easily read. Timeouts, not select . Go's waits on several channel operations at once and proceeds with whichever is ready first. Implementing it would require a lot of work — a thread has to register interest on multiple channels, block once, and then wake up when any of them is ready — so I left it out. Instead, offers and , which cover two common uses of with a single channel: What's missing is the ability to block on multiple channels at once and continue with whichever one is ready first, as well as the option to mix sends and receives in the same selection. How close can you get to Go's concurrency using only pthreads? Close enough to be useful, but not enough to really match Go. You can wrap real OS threads with familiar APIs — mutexes, condition variables, pools, channels — and the code will look and act a lot like Go, at least until a thread needs to block. But there's no scheduler underneath, so when a thread blocks, it's an actual thread waiting in the kernel, not a goroutine that's paused for free. That's the main limitation of this approach. What you get in return is brutal simplicity. Every primitive is a thin wrapper with no runtime hiding behind it, so the performance is exactly what the OS gives you: fast atomics, fast uncontended locks, and pooled throughput within ~10% of Go on coarse-grained work. But as soon as you switch to fine-grained, one-value-at-a-time hand-offs, the cost of kernel wakeups becomes the main factor, and you'll notice the slowdown. If you think the pthread approach might work for you, I invite you to try Solod . It includes the and packages, along with many others ported from Go's standard library. ➕ Coarse-grained pooled workloads are within about 10% of Go's performance. ➕ Uncontended locks and spin-friendly critical sections perform quite well. ➕ Atomic operations are as fast as in Go. ➕ The implementation is 100x simpler. ➖ Anything that needs to park and wake an OS thread is much slower than Go's userspace scheduler. ➖ The pool can't handle thousands of blocked waiters like goroutines can. "Do this, but give up after a while" (Go's idiom). "Do this only if it won't block" (Go's non-blocking branch).

0 views
Ankur Sethi Yesterday

Data locality (sometimes) beats algorithmic complexity

I've been ECS -curious ever since I learned about it in the Bevy game engine documentation . The ECS architecture predictably improves performance in languages that give you low-level control over memory (C, C++, Rust, Zig, and friends). But how does it fare when used in high-level, dynamic, garbage-collected languages such as JavaScript? This is the question Dan Murphy set out to answer in The Physics of Memory : Is it possible to use an ECS-style architecture in Javascript? And for applicable operations, does that actually do better than objects + V8’s garbage collection? To answer the question, Murphy built a 2D physics simulation of 15,000 balls bouncing around in a box using several different techniques. He found that a JavaScript implementation of the simulation that used ECS outperformed the usual "giant graph of objects" OOP implementation by 24x. He writes: It's also worth noting how the usual OOP implementation creates GC pressure: In OOP, entities are scattered across the heap. As they move and interact, the JavaScript engine’s garbage collector is constantly triggered, and the CPU frequently stalls waiting for pointer lookups. This causes sporadic frame drops (micro-stutter). Because ECS uses pre-allocated, flat TypedArrays, memory access is 100% predictable and GC overhead is zero, guaranteeing perfectly smooth frame delivery. My favorite thing about Murphy's post is that you can run all his benchmarks in your own browser. I love it when technical explanations or benchmarks are accompanied by embedded "apps" you can play around with. I'm surprised at how much data locality matters for performance. An algorithm with worse big-O complexity can outperform one with better complexity if it makes good use of the CPU's L1/L2 caches. Very cool. Cache Locality > Algorithmic Complexity : At 15,000 entities, pointer-chasing and unpredictable tree branching cannot compete with the contiguous L1/L2 cache locality of a flat 1D array sort—even though trees have a better theoretical Big-O complexity. You Don’t Need WASM for ECS Wins : Simply switching your JavaScript codebase to a flat Structure of Arrays (SoA) layout yields up to a  24x speedup  over OOP. WASM is the cherry on top (another 2.5x), not the entry ticket. Pragmatism Wins : While a hand-tuned SoA is the absolute fastest, using a production ECS library like   still gives you a massive  14x speedup  over OOP while providing a clean, scalable API. IMO, for 99% of applications using a library is the correct engineering choice.

0 views

Kraken

Billy Harrow is taking people on a tour at London’s Natural History Museum when he makes an impossible discovery: the enormous specimen of Architeuthis dux, the museum’s carefully preserved giant squid, is gone, tank and all. Things are about to get even weirder: he is interrogated by some very odd cops, attacked by a man without a heart, rescued by a disciple of a religion that believes Architeuthis is its god, finds himself in conversation with a statue in London, and soon learns that London itself is alive and full of viscera and leucocytes and more. The plotting is bonkers, with more twists and turns and magical intercessions than I could keep track of. But the language cracks delightfully, and there’s enough irreverence for all the gods and then some. As the story builds towards not one but two apocalypses you realize that the end of the world is always just about to happen—and always, there is someone egging it on and someone trying to stop it. View this post on the web , reply via email , or become a supporter .

0 views
fLaMEd fury Yesterday

Mānawatia a Matariki

What’s going on, Internet? Mānawatia a Matariki, Happy Māori New Year! Today is a time for remembrance, celebrating the present, and looking to the future. In Māori culture, Matariki is the Pleiades star cluster and a celebration of its first rising in late June or early July. The rising marks the beginning of the new year in the Māori lunar calendar. See Matariki . I’ve spent the day on Waiheke, down on Onetangi with my amazing wife and family. We spent the morning on the beach. The early afternoon at The HEKE for a long lunch and then cuddled up with the kids watching Bluey this evening. I hope you’ve had a relaxing day too. If you want to get into some great homegrown kiwi music, RNZ put on ‘ Waiata 100: New Zealand’s most beloved homegrown songs ’ today, counting down the most loved kiwi songs as voted by 65,000 kiwis. Lots of great music in there, my only complaint is that a lot of bangers from the last decade have been overlooked I guess based on the voter generation. I’ll follow up a post of great music from the last five years another day. Anyway, happy Matariki. Hey, thanks for reading this post in your feed reader! Want to chat? Reply by email or add me on XMPP , or send a webmention . Check out the posts archive on the website.

0 views
Kev Quirk Yesterday

Extinct

Author: RR Haywood Genre: Sci-fi Released: 2018 Rating: ★★★★☆ The end of the world has been avoided—for now. With Miri and her team of extracted heroes still on the run, Mother, the disgraced former head of the British Secret Service, has other ideas… While Mother retreats to her bunker to plot her next move, Miri, Ben, Safa and Harry travel far into the future to ensure that they have prevented the apocalypse. But what they find just doesn’t make sense. London in 2111 is on the brink of annihilation. What’s more, the timelines have been twisted. Folded in on each other. It’s hard to keep track of who is where. Or, more accurately, who is when. The clock is ticking for them all. With nothing left to lose but life itself, our heroes must stop Mother—or die trying. Learn more on Goodreads ➡ I've really enjoyed this series - I'm a big fan of Haywood's writing, as regular readers will already know, I've read a few of his books . This one took me a little while to get through though; not because it was bad, just because I've had a lot going on at home, so haven't had much time for reading recently. Haywood recently released book #4 in this series, Rebirth, which I've already bought. But I don't know if I should take a break from the series. I have the Red Rising books on my Kindle and everyone keeps telling me how good they are, so I may jump over and start those. Any recommendations? Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️ You can reply to this post by email , or leave a comment .

0 views
Kev Quirk Yesterday

📝 2026-07-10 09:19: I have just eaten a GIANT bowl of granola, fresh fruit, Greek yoghurt, and home...

I have just eaten a GIANT bowl of granola, fresh fruit, Greek yoghurt, and home grown honey (by one of our neighbours). I have zero regrets, but I may skip lunch today. 🤣 Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️ You can reply to this post by email , or leave a comment .

0 views