Zed Weekly: #29

December 15th, 2023

As we close in on the end of the year, we're all looking forward to enjoying the benefits of Zed's shiny new foundation with GPUI 2. It's been fun watching all the features come back online in the nightly build and starting to use the new version of Zed as a team. Here's some updates from the trenches...

Nathan and Antonio

GPUI 2 is a huge improvement in developer productivity, but the performance has to be excellent. With more and more of Zed coming back online on top of the new framework, this week we really put our focus on understanding and improving GPUI's performance.

For benchmarking, we use a simple test where we hold down the binding to switch tabs with a key repeat rate of 60hz.

Switching tabs at at 60Hz

Here's a histogram showing the results of this week's work. The red includes an improved approach to allocation, discussed below. The two different clusters can be explained by different content between the two tabs. We think there's some more improvements to be had, but we're getting closer.

Histogram of frame times
Histogram of frame times

A big known issue is that rendering a frame performs a lot of allocations. Every element in the element tree needs to be a trait object, because we want the system to be open-ended and compositional. For example, it needs to be possible for a Div element to contain child of any type.

In the early days of GPUI, I experimented with using the bumpalo to "bump allocate" all elements. With bump allocation, each new allocated value is appended after the previously allocated value in memory. Because we always allocate at the end, its not possible to free individual elements and reuse that memory, but it's also a lot simpler and faster. This is a perfect fit for frame rendering, because once a new frame is rendered, we drop all the objects used to render the previous frame and recycle the memory.

The problem with bumpalo is that every allocated value carries a lifetime that ties it to the arena in which it's allocated, and these lifetimes were adding a lot of complexity to the APIs, so in GPUI 1, we made the decision to eat the cost of malloc and simply box our trait objects.

With GPUI 2, we're allocating a lot more closures, and we also have the cost of performing layout via taffy, which is more expensive than our old approach but also a lot more flexible and powerful. To buy ourselves more headroom, we ended up implementing our own simple bump allocated arena, which we access via a thread-local variable during frame rendering. Rather than using lifetimes, we enforce memory safety dynamically, invalidating our references whenever the arena is cleared.

Another change we made was avoiding moving objects from the heap to the stack. This was caused by a design decision I made for Element::paint to move self. At the time, it seemed like a good idea, to help match the semantics of each element only being used once. What worked semantically, however, was slow in practice, because moving self to paint an element was causing the object to be copied from the heap to the stack for no good reason. Changing paint to take a mutable reference to self sped things up at a slight cost to ergonomics.

We have a few more ideas to explore next week. Our goal is for our highest-complexity full-window redraws to take 4ms or less on an M1. Then we intend to use caching to avoid performing layout and paint on views that haven't changed, which should provide even more headroom for edits. Antonio or I will report again next week where we've landed with this goal.

Marshall

This update is coming to you live (well, as live as written text can be) from Zed2!

I spent most of this week polishing up various bits of Zed2. Much of this was tweaking UI styles, but there were a handful of bugs I was able to squash in the process.

During the latter part of this week I revisited our old friend the theme_importer to improve the quality of imported VS Code themes. This is a bit of a tricky task, as each VS Code theme is unique in the way it applies its styles. Initially it felt like a game of whac-a-mole, with a change in the importer to fix one theme inevitably causing a breakage in another.

After spending a bit of time with it I'm quite happy with the results, and so far we've been able to import a handful of different theme families and have them look great in Zed2.

I'm looking forward to test-driving these new themes in Zed2 this coming week as I start using it in anger for my day-to-day coding.

Joseph

This week, I had my first chance to learn about and use the new UI framework to port our feedback crate. I'm really stoked about how it's enabling me to actually get some code into Zed and quickly iterate on UI changes. We are switching over from an editor tab to a modal window with an embedded editor. The porting experience has been a bit challenging, but fun nonetheless. I'm excited to see how this new library empowers the community of people interested in contributing to Zed.

Julia

I've spent the last few days chasing down test failures associated with a change Antonio and I made to when focus handlers are called. We had a timing issue with editor splits where the newly created editor pane wouldn't be rendered yet when we ran the focus handlers for the focus change. As a result the workspace never learned which pane received the new focus and command palette actions would continue to affect the original editor pane.

We changed focus handlers to instead be called on each frame draw, after the element tree had been rendered, so we'd have the most up to date information about the focus tree structure. Unfortunately a handful of tests assumed that the focus handlers would be called immediately which needed fixing, and many more had issues with assuming something can be focused without actually rendering it. Debugging each one took time and lots of debug prints to catch focus cycles, but eventually the branch went green :)

Nate

The list of things keeps growing, but somehow we are getting to a point where we are almost able to use Zed2 daily to build Zed2.

The state of Zed2 today!
The state of Zed2 today!

(My UI scale is pretty small, so don't fret if things feel tiny to you.)

Feels really exciting to be taking steps towards the finish line with this project – While the new app will feel pretty great, unlocking the ability to iterate on the app quickly will be the real win!

We've been able to ship a number of customizations we and others have been wanting for a while now as well. Here is a little video showing off some of them:

The UI is becoming highly configurable. Here is an example of a number of settings that can be changed.

Short update from me, but going to stay heads down and keep pushing forward!

Nathan again

Thanks for your patience as we continue to make progress that isn't quite visible on the surface. This work is really about creating a beautiful codebase for you all to work in when we open source the codebase next year. It's been hard to slow down on our visible progress, but as the overhaul nears completion, I'm increasingly confident that this investment will pay off in the months and years to come. Thanks for reading!


Note: Zed2 does not imply a new version number, it is simply an internal alias we use denote the version of Zed using the new GPUI framework.