Optimization Stuff

General discussion about LÖVE, Lua, game development, puns, and unicorns.
User avatar
Felix_Maxwell
Prole
Posts: 23
Joined: Wed Dec 04, 2019 3:15 pm

Re: Optimization Stuff

Post by Felix_Maxwell » Thu Dec 17, 2020 9:44 pm

Thanks, I got a little worried for a minute there.

So If I can attach references wherever I want, if I wanted a piercing projectile to only damage each specific entity it hits only one time, could I add an array of references to entities it's already hit onto the projectile, and then compare to see if one of those is the entity it's currently being compared against? So not an equality check, rather a check to see of two references point to the same table. I think I saw something once about being able to do this with metatables but I can't remember or find it. Is there a way to do this efficiently?

Edit: could I use __eq for this by attaching it to my class module and put the metatable comparison functionality (if that's how it's done) in there?

User avatar
pgimeno
Party member
Posts: 2479
Joined: Sun Oct 18, 2015 2:58 pm

Re: Optimization Stuff

Post by pgimeno » Thu Dec 17, 2020 10:05 pm

If I understand you correctly, you just need to use == without any __eq redefinition. Using == on tables only gives true if they are the same object. It gives false if they are different objects, even if they have the same content. Example:

Code: Select all

a = {1}
b = {1}
c = a
print(a == b) -- prints false
print(a == c) -- prints true

User avatar
Felix_Maxwell
Prole
Posts: 23
Joined: Wed Dec 04, 2019 3:15 pm

Re: Optimization Stuff

Post by Felix_Maxwell » Fri Dec 18, 2020 1:37 am

Wow, that's so... easy! Thanks!

User avatar
Xii
Citizen
Posts: 64
Joined: Thu Aug 13, 2020 9:09 pm
Contact:

Re: Optimization Stuff

Post by Xii » Sat Dec 26, 2020 12:45 am

You shouldn't leave the garbage collector enabled in a game, at all. You should collectgarbage("stop") and manually collectgarbage() only when appropriate (like level transitions). You should create all the tables you're going to need during initialization, and not create any during gameplay.

Remember, the garbage collector scans through all tables every time it runs, and it completely stops your game while doing so. That's bad.

grump
Party member
Posts: 678
Joined: Sat Jul 22, 2017 7:43 pm

Re: Optimization Stuff

Post by grump » Sat Dec 26, 2020 12:44 pm

Xii wrote:
Sat Dec 26, 2020 12:45 am
You shouldn't leave the garbage collector enabled in a game, at all. You should collectgarbage("stop") and manually collectgarbage() only when appropriate (like level transitions). You should create all the tables you're going to need during initialization, and not create any during gameplay.
That's terrible advice. Disabling memory management and implementing your own strategy/heuristics is a recipe for disaster. It's nearly impossible to produce zero garbage in non-trivial interactive games. You'll end up sprinkling your game code with expensive collectgarbage calls as it grows, calling it more often than required - only to have it still occasionally crash in tight spots on weaker machines.

Avoid garbage where possible. Use object pooling and memoization. Tweak GC parameters if you must. Don't break your game by disabling garbage collection.

User avatar
Felix_Maxwell
Prole
Posts: 23
Joined: Wed Dec 04, 2019 3:15 pm

Re: Optimization Stuff

Post by Felix_Maxwell » Sat Dec 26, 2020 2:59 pm

grump wrote:
Sat Dec 26, 2020 12:44 pm
It's nearly impossible to produce zero garbage in non-trivial interactive games.
Does changing the amount of garbage significantly change the CPU use of the GC? I thought it used most of it's time 'mark and sweep'-ing through all of the tables in the game. I assume you cut down garbage by nil-ing stuff out when you're done with it?
ivan wrote:
Thu Dec 17, 2020 7:14 am
Like pgimeno said, creating and destroying a lot of intermediate tables is the main performance issue for the GC.
What do you mean by intermediate? do you mean something like:

Code: Select all

local arg = {} -- is this an 'intermediate' table?
arg.hp = 10
arg.speed = 5

function foo:new(arg)
	self.hp = arg.hp
	self.speed = arg.speed
end
Xii wrote:
Sat Dec 26, 2020 12:45 am
Remember, the garbage collector scans through all tables every time it runs, and it completely stops your game while doing so. That's bad.
I know the GC uses some clock cycles, but it dramatically optimizes my life. The project I'm working on can handle about 2200 entities, each with a drop shadow, plus 3600 tiles before it starts to dip even close to 60 fps, so it doesn't feel like the collector is getting in my way. I test for it sometimes by creating and destroying stuff for a while, and then waiting around to see if it 'hangs' but it doesn't seem to. Does someone know how often it runs by default?

User avatar
ivan
Party member
Posts: 1703
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Optimization Stuff

Post by ivan » Sat Dec 26, 2020 3:33 pm

Felix_Maxwell wrote:
Sat Dec 26, 2020 2:59 pm
[What do you mean by intermediate? do you mean something like:
Intermediate tables are basically short-lived and not stored (for very long):

Code: Select all

function getPosition()
  return {x,y}
end

grump
Party member
Posts: 678
Joined: Sat Jul 22, 2017 7:43 pm

Re: Optimization Stuff

Post by grump » Sat Dec 26, 2020 3:53 pm

Felix_Maxwell wrote:
Sat Dec 26, 2020 2:59 pm
I assume you cut down garbage by nil-ing stuff out when you're done with it?
"Garbage" is all memory allocations to which no strong reference exist anymore. Niling an object removes one reference, so it will still become garbage. I don't think there is any difference GC-wise between explicitely niling a local object vs. just letting scope take care of it. Although tighter scopes are mentioned in the LuaJIT docs as a way to help the compiler generate better code.
before it starts to dip even close to 60 fps, so it doesn't feel like the collector is getting in my way.
Unless your code is real bad, GC cycles will mostly show up as short spikes, not as a constant performance degradation. Like, one frame every x seconds takes 20 ms instead of 10. You don't always notice those in the fps counter, but a drawing frame time history diagram can be helpful to see them.

User avatar
pgimeno
Party member
Posts: 2479
Joined: Sun Oct 18, 2015 2:58 pm

Re: Optimization Stuff

Post by pgimeno » Sat Dec 26, 2020 3:59 pm

I had written this before grump's reply, so I'll mark the sections that are already replied in grey.
Felix_Maxwell wrote:
Sat Dec 26, 2020 2:59 pm
I assume you cut down garbage by nil-ing stuff out when you're done with it?
That produces garbage. When you set a variable to nil, the object becomes unreferenced, and the garbage collector needs to identify it as unreferenced in order to clean it up. A priori, Lua does not know whether there's some other reference to the object anywhere, that's why the garbage collector exists in the first place.


Felix_Maxwell wrote:
Sat Dec 26, 2020 2:59 pm
What do you mean by intermediate? do you mean something like:
[...]
In your example, for as long as arg is not set to a different value, the table will be kept in memory, and the GC will not have any need to clean it up.

Something that stresses the GC is if something like this is called many times every frame:

Code: Select all

myfunc({field1 = 3, field2 = 5})

Felix_Maxwell wrote:
Sat Dec 26, 2020 2:59 pm
I know the GC uses some clock cycles, but it dramatically optimizes my life.
Sure thing, but note that "some clock cycles" can add up to more than one frame time, causing micro-stutters. If you don't run it manually every frame, it will typically run every few frames. I don't know the rules that decide when it should run; I know the rules are not fully deterministic (it may depend on things like the amount of objects that could be created in a certain time, which isn't always the same due to caches and other stuff).

If you can run it every frame without a performance drop, that's probably best.

User avatar
Xii
Citizen
Posts: 64
Joined: Thu Aug 13, 2020 9:09 pm
Contact:

Re: Optimization Stuff

Post by Xii » Sat Dec 26, 2020 7:25 pm

grump wrote:
Sat Dec 26, 2020 12:44 pm
That's terrible advice. Disabling memory management and implementing your own strategy/heuristics is a recipe for disaster. It's nearly impossible to produce zero garbage in non-trivial interactive games.
If you can't do it, that's fine; but just because you lack the understanding of programming to achieve something doesn't make it "impossible".

In fact, this is how games have been programmed since basically forever. It's only in our post-modern era when computers have become so fast that developers now have the luxury of being, ahem, lazy.

But it's a trap. If you don't learn to deal with it, you'll hit a performance ceiling sooner or later. The complexity of your games can't grow beyond that barrier, unless you actually start thinking about what the computer is doing with all those anonymous tables you're creating every frame.

I mean, if you're just making Tetris clones or whatever then by all means, ignore everything I've said.

Post Reply

Who is online

Users browsing this forum: Google [Bot] and 60 guests