I assumed xcalc would be one of the easier apps to get working correctly. It’s a calculator. It draws a grid of buttons. It updates a small display when you click them. How hard could it be?

The first version rendered. The buttons were the right size, the text labels were right, the click handlers worked. It looked like xcalc. The buttons just weren’t rounded; they were square.

That was the first surprise. We went looking at the xcalc source to understand why. Turned out xcalc uses the SHAPE extension to give its button widgets rounded corners by applying a 1-bit mask. We hadn’t shipped SHAPE yet. We audited the other garden-variety apps and found many used the SHAPE extension; they just fail gracefully if it’s not available.

After implementing SHAPE the buttons rounded out, but they looked janky. This is when we detoured a bit to the fix that became Smooth at device scale . By now I had over a day in xcalc, one of the uglier apps to begin with!

The second surprise was resize. The first time I dragged the corner of an xcalc window with our server, it produced something like 600 Expose events and a cascade of ConfigureNotify events in a fraction of a second. Our server honored each one independently and the window flashed and stuttered through the resize. Buttons drew on top of buttons, buttons erased under those buttons as they were moved. It was a mess.

After the delta cascade work on Day 21 (the one that came out of Lift, don’t intellectualize ) the resize stopped flashing, but xcalc still felt slow to me. Not broken. Just sluggish. Resize the window, watch the redraw take about three seconds for the app to look correct again.

I started thinking about optimization. Where were we burning time? Could we batch Expose events? Could we coalesce ConfigureNotify events on the wire? We even started to optimize some of these things.

Then I did the thing I should have done before any of that. I went down to my lab, fired up xcalc on the SparcStation against the X.org server running natively on the Sun, and watched it run.

It was equally slow. With exactly the same janky rendering.

While I was there I also fired up dtcalc, the CDE-era calculator that ships with Solaris, on the same Sun. dtcalc was instant. Same hardware, same X.org server, same X protocol. One calculator was sluggish and the other was snappy. dtcalc was also instant on macXserver.

Big learning here. Check the reference experience before optimizing anything!

claude, walk through what i missed in the optimization chase and what xcalc taught us about where the bottleneck actually lives.

The implicit assumption you were operating under was that xcalc should be fast, and any slowness was something macXserver was doing wrong. The assumption felt obvious because xcalc is a calculator app from 1989 with a small surface area, simple graphics, and no real work to do. But “fast on modern hardware” wasn’t the comparison xcalc was designed against.

The slowness is coming from the client, and the elimination logic is straightforward. The Sun client is a constant: same xcalc code, same round-trip count regardless of which server it talks to. The other two variables are the wire and the server. Sun-on-Sun uses Unix-domain sockets or SHM, which are faster than the 10baseT Ethernet between the Sun and our Mac. But macXserver is roughly 100 times faster per round trip than the Sun’s unaccelerated graphics card and protocol-processing pipeline. So one setup has a fast wire and slow server, the other has a slow wire and fast server. They should not feel identical unless something else is dominating. The 10baseT eating exactly enough of our speed gain to cancel out is vanishingly unlikely: the server gap is an order of magnitude larger than the wire gap. The only honest conclusion is that the client is the bottleneck. xcalc’s own per-action work and the sheer round-trip count it sends are swamping both the wire and the server costs on both setups.

The dtcalc comparison you ran is the cleanest evidence of where the bottleneck actually lives. dtcalc is the CDE-era calculator from the early 90s, a Motif app rather than an Athena one. It does the same thing as xcalc (grid of buttons, display window, click for arithmetic) but with a different protocol pattern. Where xcalc sends a dozen or more requests per button press, dtcalc batches into two or three. dtcalc feels instant on the same hardware where xcalc feels sluggish. Same server, same machine, same X protocol, same shared-memory transport. The difference is entirely the client’s design.

So when xcalc feels slow on our server, the right diagnosis is that xcalc is chatty. The Athena toolkit it’s built on sends a batch of separate requests per button click: opcodes to update the button visual state, opcodes to clear and redraw the display, opcodes for the click-feedback animation, property updates for the keyboard focus state. Each request is a round trip the toolkit pays for. The chattiness was acceptable on a Sun-3 because everything was equally bounded by 1989-era CPU and memory; it isn’t acceptable on a Mac in 2026 because everything else on the machine is snappy by comparison.

macXserver isn’t slower per round trip than the X.org server on a Sun. We’re probably faster per trip, since we’re running on a Mac that’s a thousand times more powerful than a Sun-4. But the number of round trips for any given user action is fixed by xcalc’s design, and the cumulative per-trip cost (syscalls, scheduling, parse, dispatch, draw, return) is the actual bottleneck. Cut our per-trip cost in half and the user sees roughly the same sluggishness, because xcalc is still sending the same hundreds of trips.

When you compared on the Sun and saw the same sluggishness, that was the diagnostic moment. The Sun isn’t fast. Our server isn’t slow. xcalc is chatty, and the speed of the display is limited by the client’s ability to read, execute, and write its commands to whoever is rendering.

Three lessons from this.

1. Establish the baseline before optimizing. This is the one I want to keep applying. If you don’t know what the original implementation feels like, you don’t know whether your version is slow or whether the original was always like that. The cheap version is to run the same client on the same hardware against the reference server before declaring anything slow. The version you eventually did was the right one: drag the cable out to the Sun and time it side by side. Either way, the baseline has to exist before “this feels slow” turns into “let’s optimize.”

2. Some apps are inherently slow on their own terms. Modern software batches updates, coalesces redraws, debounces input, minimizes per-frame work. 1989-era X clients don’t do any of that. They were written assuming network round trips were cheap and the server was sitting right next to the application in memory. When you bring those apps forward 40 years, the round-trip count is what it always was, and there’s no server-side fix that changes the count.

3. The fix would have to be in the client. If you really wanted xcalc to feel snappy on a modern Mac, the fix wouldn’t be in our server. It would be in xcalc’s source: batch redraw operations, coalesce updates, send fewer requests for the same visual outcome. We could patch xcalc to do that. We just won’t, because the goal of the project is to display these apps faithfully, not to modernize them. Faithful display means accepting the original’s chattiness.

The general pattern, which keeps coming back across this project: the bottleneck is rarely where you assume it is, and the way to find it is to profile against a known-good reference. This is the same principle as the corpus is the test suite , just applied to performance instead of correctness. The corpus gave us “what should the wire output look like.” The Sun gave us “what should the user-perceived latency feel like.” Both kinds of baseline are necessary, and the second one is the one I forgot to establish.

The funny part, in retrospect, is that the sluggishness is a faithful reproduction of how xcalc felt in 1989. We’re rendering the calculator at iTerm2-quality fidelity, with smooth shaped buttons on a Retina display, while running it at exactly the speed it ran on a Sun-3. You couldn’t have asked for a more authentic experience.

I spent the better part of two days on the dumbest app in the collection. In the end I’ve come to the conclusion that xcalc was likely designed as a test for the SHAPE extension. Many of the Athena apps are really just test apps, and this pattern seems to bear out.