Last week, while debugging a SwiftUI performance issue, I was running instruments and found that my profiles were plagued with noise.

Godot while idling was using so much CPU time that it was skipping entire frames while rendering.

I set out to fix those, 0.5% here, 0.5 there, and the Godot Editor (and Xogot) no longer skip frames.

People had been complaining that Godot would burn your battery in an hour if you left it idling, it no longer does.

Details:

https://github.com/godotengine/godot/issues/116845

As a sidebar, when I started porting Xogot to Mac, my first reaction was "SwiftUI is just not up to the task, it is too slow".

I decide to give Instruments a shot with the new SwiftUI instrument, and was able to track down the problems - it took a while (and Claude scanning my traces), but Xogot powered by the same SwiftUI code from iPad on AppKit is smooth as butter.

It was just not as noticeable on iPad, as the touch interface would distract from the slowdowns.

I have this bad habit in SwiftUI, where instead of using new Views for components, I have many properties that share state:

struct Foo: View {
@State var someSharedState
var title: some View { ... }
var buttons: some View { ... }
var body: some View {
HStack {. buttons title }
}

Turns out that SwiftUI uses the "View" as the boundary of change, so if anything on my model changed, it would have to re-evaluate the whole view, not just say, the title.

Split your views.

@Migueldeicaza have this same problem with React. Tis why I like observable components 🥲@nicklockwood
@Migueldeicaza I do this too much as well, but often it’s because passing something like a button action down three layers is a lot of redundant code.
@josh oh and I also learned: environment is super expensive
@Migueldeicaza It’s also horribly unergonomic and evidently anti-pattern for things like the use case I mentioned. I draw fairly firm lines between “complex chunk of THIS VIEW for clarity” versus “this has its own state”.

@Migueldeicaza yikes. I like @environment and use it quite a lot to inject services which views can pull out and use to do stuff / read stuff.

I much prefer it to the pass-everything-down model, probably for similar boilerplate reasons that @josh mentioned.

How expensive is expensive? 😢

@samdeane @josh me too, I use it all the time, but there is no granularity on environment - check the wwdc talk on instruments for SwiftUI they explain what happens.

@samdeane @josh but even if it triggers q lot of changes, the trick is whether they matter or not.

A profiler really has a way of contradicting every feeling from our guts.

@Migueldeicaza The SwiftUI equivalent of “printf debugging” for unnecessary view updates is setting the background of every view to a random color. The flashing colors will show you which views are updating when you think they shouldn’t be.
@siracusa oh I should use that! Related my go-to hack to check quickly on layout is: .overlay(color.red.opacity(0.3))
@Migueldeicaza @siracusa View flashing is useful! I also do a lot of this nasty-looking but useful trick:
@joethephish @siracusa I also like the printChanges() trick, cheap when you don’t want to do a big instruments run (SwiftUI tool in instruments is heavy)
@siracusa @Migueldeicaza There's an idea for an @atpfm member special - favorite macOS & iOS programming / #xcode / Apple tips and hacks! Maybe it would have to be a video...
@Migueldeicaza I think this may be because SwiftUI can’t see inside the definitions of those computed vars to tell which properties of the model they depend on. If you inline them into `body` I believe it will try to optimise the update to changed-properties-only. I wish I better understood the mechanism: it should be possible to split out in a way which doesn’t wreck it. But generally I’ve learned to avoid computed vars in View structs because of this.
@tikitu that leads to another trap: body re-evaluations. Splitting the view reduces which views need their body re-executed, which shows up on the profile extensively - The instruments with SwiftUI talk explains that rare bit.

@Migueldeicaza Skimming the issue you linked, you'll be unsurprised to hear that these are all bugs we've had in web rendering engines over the years.

At the same time, the idea that most folks working on an engine have devices more powerful than an iPad is 😲😲😲

@slightlyoff there is so much low-hanging fruit in here.

Lots of people are using the equivalent for “idle” to run some process, but they often do polling there - it is just horrible.

@Migueldeicaza This is a whole new angle to my mantra "friends don't let friends make knockoff browsers"

@Migueldeicaza a few years back, I ended up with the same conclusion while working on CiderKit. And I’m still using AppKit for that reason.

https://chsxf.dev/2022/08/28/5-tup-why-i-quit-using-swiftui.html

Why I Quit Using SwiftUI

SwiftUI is currently the new trend in the Apple development world. And I wanted to give it a try. I did. However, in this post, I explain why I went back to AppKit for The Untitled Project’s authoring tools.

chsxf.dev
@chsxf oh, but we came to different conclusions - with the new profiler I was able to fix all the issues and stay in SwiftUI
@Migueldeicaza on that front you’re right. At the time, I wasn’t able to exactly pin point where the problem was, and it was simpler for me to stay with AppKit and actually do stuff that worked. But I’ve been considering revisiting this part for some time so maybe this is it.
@chsxf makes sense. Also the new profiler really takes a lot of the guesswork out of it. You can clearly see and find out what is wrong, and before it took a lot of trial and error and too much knowledge of sausage making.
@Migueldeicaza even though you’re not getting much attention on the PR, your work is not unappreciated. I looked into optimizing some godot runtime stuff when making scala bindings and determined that improving the performance of the engine would be very difficult with the architecture they’ve chosen.
@Migueldeicaza How does it feel to get back to coding with good old C++? 😄
@vadersb fixing bugs is ok, writing new code is just not enjoyable