Golang Physics Performance

preview_player
Показать описание

In this video I spent some time benchmarking my ECS (Entity Component System) framework against the popular Rust ECS, Bevy. We had to do some performance analysis and optimize a few things, but all things considered, we ended up doing pretty well. Specifically, we optimized by doing bounds check elimination (BCE), inlining, and using the recently released Go generics

// Socials

// Code

// Good Article

// Errata
1. Calling Monomorphization "The other end of the spectrum" from GC shape stenciling is a bit overblown. Go stencils (ie monomorphizes) its generics based on the GC shape, so there still is some monomorphization happening which can give you nice performance gains for things with different GC shapes. The actual "Other end of the spectrum" is probably something like doing a full dictionary-based boxing and doing no stenciling at all.

2. Why does Bevy Run Slower than Go?
I apologize for not covering this in the video. I think number 1 and 2 are probably the biggest contributors to Bevy running slow.
a. Bevy has A LOT more features than my ECS, each of those features might contribute to a global optimization for most cases, but in my benchmark contribute to a slowdown. I expect that most people aren't disabling multithreading and the system scheduler and manually executing every game loop.
b. Bevy has a few different layers at which you can use the ECS, I'm not a bevy expert, so I may have written the code in an abnormal way where bevy ran slower than it usually would have. Very specifically, Bevy seemed to slow down the more I started adding and deleting components, I'm not sure the limitation there, but I thought it was notable.
d. Pointed out by laundmo in the comments:
1. "by default, bevy uses what it calls "Table" storage for components - this is fast to iterate but slow to update. In use cases where you might want to add/remove components a lot, you can switch to SparseSet storage on a per-component basis." - I didn't know this, but it does align with what I saw where Bevy slowed down once I started deleting entities every frame.
2. "The ECS benchmark [in errata C] you mentioned is outdated by 2 years, and was also recently archived to represent this. Bevy has had multiple optimizations since then. I see you using bevy_ecs 0.7 - in 0.9 there were some decent performance improvements which would be interesting to see."
Рекомендации по теме
Комментарии
Автор

For a 5 minutes video it packs a whopping amount of great insights. Thanks a lot

valera_cool
Автор

Rust iterators and its other zero-cost abstractions are what makes it so good in my opinion. Being able to write more readable code, and less of it, and get more optimized code than I could've written explicitly myself is just a win-win.

Sure memory safety is great, but even std C++ iterators can have poor performance, depending on how you use it. Thanks to Rust's borrow checker, it can optimize iterators by not needing to do boundary checks, since it knows the size a head of time.

Using abstractions in Rust are often faster than explicit code. Of course it's not a silver bullet (e.g. there's no way to bailout early in an iterator closure).

dealloc
Автор

An excellent video showing how Golang can be fast if memory is correctly managed. It would be really interesting to get an update using the latest versions of Golang, specifically using (PGO) Profile Guided Optimisation.

runny
Автор

Rust basically implies most of what you were optimizing manually in Go. Borrow checker and exhaustive compile time checks allow for that as a default.

nikoladd
Автор

To be fair, Cart, the guy who started Bevy, is pretty freaking brilliant.

LongestYardstick
Автор

I gave up on Go to switch to Rust -- primarily because they were taking way too long to release generics. Rust has a very rapid, but stable, release cycle, and Go will get left in the dust. Which is unfortunate, I believe it's one of the best languages we have for programming, but the speed at which limitations are removed is hurting the adoption.

tsalVlog
Автор

People really like hating on GC'd langs, especially in gamedev but people stil use and they all use GC and/or interpreted langs on top of C/C++.

Anyways good job!

protosevn
Автор

Well Go has iterators now since 1.23 released.

laszlotorhosi
Автор

4:00 I never heard the term "box" used in programming outside of Rust lingo (and basic explanations for variables). It means heap allocated objects there. What do you mean by it in this case? Also, good job on matching Bevy's ECS, even if the optimizations ended up being a bit gnarly

Also, queries can be iterated faster in bevy by using the for_each method rather than using the iter methods that I found in your repository. You should try updating them! I could try to fork and benchmark it if you want, sounds interesting

Dorumin
Автор

Amazing that you got a GC language running this fast

sohn
Автор

Sorry for the impolite comments from some Rustaceans here. Great experiment! But as you mentioned, definitely curious about workloads with more disparate alloc. Hmm but in that case, maybe the allocator becomes a factor too, and in Rust you can change it from system default to jemalloc or mimalloc, so more variables to consider. It’s really hard to isolate only the ECS code to compare.
More than performance though I’m curious how much you feel the Rust style of using types helps you avoid errors or annoys you from progression. The more interesting trade off between Go and Rust is I think of Go, less static analysis for fast compile and more attention to debugging, versus of Rust, more static analysis (and annotation and API restrictions) for less debugging and slower compile.

YuFanLou
Автор

Not a game programmer myself, but I do write go in my day to day work. Learnt alot from this video about go code optimization. Do you have a compiled resource on where I can learn more on this topic?

jeffreylean
Автор

this is amazing!
thx for making and sharing

toukaK
Автор

Oh so it's this man right here who needed iterators in GO 3:45

moveonvillain
Автор

This is awesome content! It's pretty cool seeing how well Go did, and I really was expecting to see a bigger difference between the two. I guess in the case of this particular simulation it makes sense that GC wouldn't be too big of an issue.

I've been meaning to try my hand at learning Go, because I think it could complement Rust pretty well and this video may just be the motivation I needed to get over that hill lol

askii
Автор

Rust also does bounds checking. You can use the non-bounds checking api directly. But if you indexed directly into arrays in Rust, you are using bounds checks everywhere.

simonfarre
Автор

Excellent work! Working on a VERY similar problem at the moment and watching your process has been helpful, if for nothing else, to validate some decisions I've made along the way.
I've mostly been looking at Unity's DOTS/ECS implementation as reference, but being closed-source, I've had to make lots of assumptions based on the API alone.
In any case, this is great content. Hoping to see more adoption of Go for games.

RayBarrera
Автор

Go does have some things now for creating iterators, might be worth checking out.

caldog
Автор

Btw Rust also does bounds checking on arrays/slices

tannerted
Автор

Awesome video! But I was expecting the result to be much more larger though, maybe you haven't compile the rust code in release mode? cargo run --release?

vintagewander