Stream: beginners

Topic: ECS


view this post on Zulip Brendan Hansknecht (Mar 26 2022 at 16:55):

So I just wrote a simple ECS in c++ to better understand them that I am gonna trying port to Roc in order to get a better understanding of the performance and potential APIs. It is just a simple "firework" simulation thing that I made up. The ECS is a simplified and directly implemented version of the ECS in this talk.

This is what the output looks like:
output.gif

view this post on Zulip Brendan Hansknecht (Mar 26 2022 at 16:55):

I can control the max number of entities and how fast the fireworks spawn:
faster.gif

view this post on Zulip Brendan Hansknecht (Mar 26 2022 at 16:57):

I also can turn off rendering to just benchmark the underlying ECS performance. Turns out that drawing a bunch of colorful translucent circles is slow. Probably need to optimize the rendering more in general. But since I just care about the underlying ECS that doesn't matter.

view this post on Zulip Brendan Hansknecht (Mar 26 2022 at 16:59):

Current possible number of entities while maintaining 60 FPS (on my M1 mac):

No idea if those are actually decent numbers for what is being done, but still should be good enough for comparing the Roc to the C++ version for performance since they will be written in similar ways.

view this post on Zulip Brendan Hansknecht (Mar 26 2022 at 17:08):

For anyone interested, the repo is here: https://github.com/bhansconnect/roc-ecs

But currently it is only the c++ base and a readme. Next need to make a c++ platform and add a directly ported roc version.

view this post on Zulip Brendan Hansknecht (Apr 04 2022 at 22:51):

Got this working in Roc. It is a pretty direct port of the c++ version and not the prettiest code, but it is fully functional. Also had to work around some Roc issues to make it fully functional:
roc.gif

view this post on Zulip Brendan Hansknecht (Apr 04 2022 at 22:51):

Would post performance information, but currently I can't compile it in an optimized build. Need to work around this issue first

view this post on Zulip Richard Feldman (Apr 04 2022 at 23:05):

cooooooool

view this post on Zulip Brendan Hansknecht (Apr 10 2022 at 23:39):

So I got the Roc version compiling in optimized build. Definitely gonna need to debug the performance. It essentially is the same speed as the debug build. Probably some references/copying of lists that is ruining the performance. But where we stand rn:

~5,000 circles for Roc vs ~700,000 for c++

view this post on Zulip Brendan Hansknecht (Apr 10 2022 at 23:40):

So direct port of the c++ algorithm definitely block some roc optimizations and causing pain.

view this post on Zulip Brendan Hansknecht (Apr 10 2022 at 23:44):

Yep, definitely a referencing issue. Essentially all of the time is spent in memmove caused by List.set. So need to double check my references here.

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 04:02):

So found at least part of the issue. Roc does not know how to optimize updating a list inside of a struct directly. As in this code:

nextModel = { model & graphics: List.set model.graphics (Num.toNat id) { color: fadeColor color fade, radius} }

It will copy every time with current roc. Instead you need something like:

graphics = model.graphics
tmpModel = { model & graphics: [] }
nextModel = { tmpModel & graphics: List.set graphics (Num.toNat id) { color: fadeColor color fade, radius} }

Just changing a couple of those and performance jumped up to 16,000. There are still more to change.

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 04:31):

Ok, I have a fixed at least all of the basic copying issues. I think it is still doing some extra copying but not completely sure. This now brings the Roc numbers up to 140,000...so 5x slower than the equivalent c++ implementation that can run at 700,000. Definitely a respectable start.

view this post on Zulip Richard Feldman (Apr 12 2022 at 11:27):

Nice! Can you open an issue for the missing optimization?

view this post on Zulip Folkert de Vries (Apr 12 2022 at 15:15):

that's disappointing that LLVM cannot figure it out

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 15:22):

Disappointing, but not really surprising. We increment the refcount, ask it to do something that will lead to a memmove, and then decrement the refcount. So we are doing really global effects. From llvms view you might explicitly want to move the data to a new location and free the old one.

view this post on Zulip Folkert de Vries (Apr 12 2022 at 16:00):

so then what is a good fix here in terms of code gen

view this post on Zulip Folkert de Vries (Apr 12 2022 at 16:01):

take the value out and zero out the field?

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 16:13):

That may actually be the simplest solution as silly as it sounds.

view this post on Zulip László Benedek (Apr 12 2022 at 16:18):

Is that panic safe? If you just zero out the bits then it could be an invalid object for a while. I don't know whether that matters for Roc. Interestingly, this is a problem that I also encountered in my language (I try to do full program ownership inference) and I "solved" this by replacing updates with ctor calls where I simply plug in each unchanged and changed value. In this case, I believe there isn't any moment where the object is invalid.

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 16:24):

No app state is recoverable on a panic. Only platform side state, so it should be safe.

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 16:25):

Also, if the record was boxed and shared between threads or something crazy like that. Zeroing the field would require copying the record to a local version before actually zeroing it. Which should also be safe.

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 16:25):

Would have happened when updating the record anyway. Just happening earlier now.

view this post on Zulip László Benedek (Apr 12 2022 at 16:29):

I'm not talking about recovery, just safe destruction. How do you call the dtor of something that has invalid bits? Or do you simply forget everything in case of panics and let the platform figure out what to clean up?

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 16:31):

It's the platform's problem

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 16:32):

Many platforms will likely just crash with a message. Some will just delete an arena and move on. Others may just log it and leak memory.

view this post on Zulip László Benedek (Apr 12 2022 at 16:38):

What about other resources? Sockets, files?

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 16:44):

Roc doesn't have control over sockets or files. Only the host does.

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 16:44):

So Roc literally doesn't know how to close a socket.

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 16:45):

It would have to call a host function to get it to close.

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 16:46):

From rocs point of view, after a panic, the platform could pass the same socket back into it again, so if we closed it, that would be problematic.

view this post on Zulip László Benedek (Apr 12 2022 at 16:49):

I see. I wasn't aware of how much responsibility the platforms have.

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 16:53):

Yeah, they deal with all forms of IO. Roc is really just a subfunction to a platform that does some pure data transformations. The only guaranteed* functions that a platform will provide are panic and memory allocation/resize/deallocation functions.

Not technically guaranteed: On an embedded system, I might have a empty roc_alloc that just crashes. So the roc programmer would not be allowed to use anything that allocates.

view this post on Zulip Richard Feldman (Apr 12 2022 at 17:03):

out of curiosity, did you also try this way?

graphics = List.set model.graphics (Num.toNat id) { color: fadeColor color fade, radius}
nextModel = { model & graphics }

view this post on Zulip Brendan Hansknecht (Apr 12 2022 at 17:46):

Just tested. That copies.


Last updated: Jul 06 2025 at 12:14 UTC