Re: [???] GC can't keep up while the game is paused

Mon Oct 04, 2021 5:22 pm

Graf Zahl wrote:Oh my, this mod is creating roughly 4000(!!!) layout objects per frame! It occasionally skips a frame but that only seem to be frame drops where the HUD is not being rendered for a tick.
It seems to create a huge OOP-based layout hierarchy and instead of caching it recreates it EACH! SINGLE! TIME! it gets rendered.

I'm sure if this was Java it would start to choke under the constant GC stress as well. It's simply not how a garbage collected language can be used. They occasionally need some time to take down all the trash, but here it continues with this insanity while the GC helplessly tries to catch up, never managing.

TBH, code like this is what gives OOP a bad name.
This code is so extremely scattered into micro-methods that I have no clue where even to look for a start of this all.
I'm sorry, but in this case my only response is "Fix the mod!"
There has to be some means to detect whether the most recent layout is still valid or not so that it won't have to be recreated over and over and over again in its entirety.



@Graf: which mod are you talking about exactly? My PDA kit or Player701's mod?

Re: [???] GC can't keep up while the game is paused

Mon Oct 04, 2021 5:32 pm

RRWM of course.

Re: [???] GC can't keep up while the game is paused

Tue Oct 05, 2021 6:05 am

Graf Zahl wrote:Oh my, this mod is creating roughly 4000(!!!) layout objects per frame! It occasionally skips a frame but that only seem to be frame drops where the HUD is not being rendered for a tick.
It seems to create a huge OOP-based layout hierarchy and instead of caching it recreates it EACH! SINGLE! TIME! it gets rendered.

I'm sure if this was Java it would start to choke under the constant GC stress as well. It's simply not how a garbage collected language can be used. They occasionally need some time to take down all the trash, but here it continues with this insanity while the GC helplessly tries to catch up, never managing.

TBH, code like this is what gives OOP a bad name.
This code is so extremely scattered into micro-methods that I have no clue where even to look for a start of this all.
I'm sorry, but in this case my only response is "Fix the mod!"
There has to be some means to detect whether the most recent layout is still valid or not so that it won't have to be recreated over and over and over again in its entirety.

Most of the objects that get created are immutable and they should probably use a cached pool of some sort, although I'm not sure how difficult it might be to implement that in ZScript, if at all possible. Let's start with no static variables (although it could probably be done with StaticEventHandler, provided that UI code can access it) and no generic dictionaries (only string->string), or, for that matter, no generic types at all. This should probably be done on the engine side, although I guess it'd likely be too much to ask for a rather niche use case.

However, there might be a way to optimize the code somewhat. That's in fact what I've been working on after reporting the struct-related crash, and it does involve detecting layout changes. The problem, is, however, that traversing the layout tree and asking each leaf node if anything has changed still involves calculations of some sort (to compare the results with the cached ones) and produces more wrapper objects. I cannot get rid of them all entirely - their sole purpose is convenience; the code would be a lot more difficult to read and maintain otherwise. Granted, however, the total number of objects in this case might be less, but I'm yet to get to the testing phase so I can't tell what the actual difference is going to be.

Also, I did not receive any email notifications after my last reply, so I'm sorry for not replying earlier.

Re: [???] GC can't keep up while the game is paused

Tue Oct 05, 2021 6:42 am

"No static variables" is pretty much unavoidable. I don't really understand why you even need them - to display a HUD you need a status bar object where you can store anything you like.

Re: [???] GC can't keep up while the game is paused

Tue Oct 05, 2021 6:43 am

One more thing: if the objects in question were structs instead, we wouldn't have this problem at all, since everything would be created on the stack and not in the heap. But we do have a problem here, or, in fact, several. That's what I was talking about in my latest bug report when I said structs had some drawbacks. So, allow me to elaborate:

  1. You cannot return a struct from a method, instead you have to use a parameter. This alone necessitates a major rewrite of the entire script code, and the result will not look too pretty. I really have no idea why it's been designed like that.
  2. All structs passed as parameters (even those not marked out) are passed by reference. This doesn't cause much problems as long as the struct is not modified, but it's still weird - either I am missing something, or there doesn't seem to be a way to pass a struct by value at all, which is simply not good.
  3. As a result of the above, every time you want to construct an object that has one or more struct fields, you have to copy their contents manually. This essentially requires implementing copy constructors for all structs, since copying the contents "by hand" will necessitate updates in every place of the code where the copying happens in case the struct layout is changed later.
I'm really sorry for potentially derailing this thread, but if we didn't have the above issues then we probably wouldn't be having this discussion now. But I worked with what I had at my disposal, and I couldn't find a better way to implement what I wanted without sacrificing the cleanliness of the code. Optimizing it was long overdue, but since the mod itself hasn't gained much interest, I have been constantly postponing it, while also being on lookout for engine-side updates that could make my life easier somewhat.

Re: [???] GC can't keep up while the game is paused

Tue Oct 05, 2021 7:24 am

You have to consider the implications of passing structs by value. This would necessitate a copy each time it gets passed. Same for returning them.

Re: [???] GC can't keep up while the game is paused

Tue Oct 05, 2021 7:31 am

But it is normal behavior in other programming languages (C, C++, and C# to name a few), and it shouldn't affect performance much as long as the struct is lightweight - which is exactly the case here. Like I said before, most of the problematic objects have only a few scalar fields in them - at most four, if I remember right.

Re: [???] GC can't keep up while the game is paused

Tue Oct 05, 2021 7:46 am

You are making comparisons with big professional languages. Just look at Java for a more realistic comparison. This is just a simple game scripting language, you cannot expect this kind of sophistication from it - and you surely cannot expect to use it like such a language, because the underlying system was never designedfor it.

Re: [???] GC can't keep up while the game is paused

Tue Oct 05, 2021 8:58 am

Graf Zahl wrote:You are making comparisons with big professional languages.

Perhaps I am, and it is certainly my fault because I've worked quite a lot with some of them. But what I really want is to write clean code that is relatively easy to maintain, and having out parameters all over the place certainly does not contribute to maintainability. It would work the same as if structs were actually passed by value except that it would look much uglier. I guess if all else fails, I will have to settle for this option when the crash is fixed.

Re: [???] GC can't keep up while the game is paused

Wed Nov 24, 2021 7:26 am

I've implemented some heavy optimizations in my mod, and now the memory usage does not seem to actively increase at all during gameplay. However, when the menu is opened, it still appears to go up about 3-4 MB every second. I don't know what else there is left to optimize, the only remaining thing is the stack of drawing operations where each frame stores rectangles signifying the borders of the current drawing area as well as its padding. It is a bit difficult to implement any caching there, especially considering the dynamic nature of the data structure, so there are still new objects that get created on every draw pass.

Is it certain that the GC process cannot be improved any further beyond its current implementation? I can also post the current test build of my mod if the devs would like to profile it and compare the results with the previous version. I of course cannot be 100% sure that I haven't missed anything during the rewrite, so maybe there is still some way to cut down on the number of allocations even further.

Re: [???] GC can't keep up while the game is paused

Wed Nov 24, 2021 9:03 am

That behavior is perfectly normal for a garbage collector. Its entire point is to accumulate some data before actually collecting it.
As long as the memory gets regularly collected and freed, it only tells us that the GC is doing its job.

Re: [???] GC can't keep up while the game is paused

Wed Nov 24, 2021 9:22 am

I'm not sure but I don't think I actually saw the memory usage go down while paused, only after unpausing. I will test some more though, and report tomorrow with more detailed results.

Re: [???] GC can't keep up while the game is paused

Thu Nov 25, 2021 8:04 am

So it seems that my initial optimizations were not enough, because in several testing sessions, while the game was paused, the memory usage simply went up continously and never stabilized or went down. Then I went ahead and implemented a cache for the draw stack data in my UI code via an array of history entires containing previous input and output data. The idea is that if the sequence of push/pop operations and their input data is the same at each draw pass, then the calculations that result in new objects being created can be skipped, and cached output can be used instead. It seems that this finally helped, and the memory usage in menu/pause mode now either doesn't increase at all, or goes up only a little and then stops.

This still needs a lot of testing before I can release an update though, and I'm sure there are edge cases where this caching mechanism won't work, e.g. if a continuous scrolling animation is in effect. This could be countered by disabling all HUD animations while the game is paused, if only there were a way to detect this in ZScript. I've found a feature suggestion thread but it's marked "duplicate" for some reason, although I couldn't find anything else related to this topic. It doesn't seem to be implemented as of now, but if someone else knows more about this, please tell me. Thank you very much.

Anyway, I wish to thank Graf for his earlier input in this thread, although I would still like to see "proper" handling of structs in ZScript someday... I guess we can all agree that using heap-based objects for such things is not a very good idea, as that was what caused this GC-related fuckup in the first place.