Optimization Stuff

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

Re: Optimization Stuff

Post by Felix_Maxwell »

Xii wrote: Sat Dec 26, 2020 7:25 pm If you don't learn to deal with it, you'll hit a performance ceiling sooner or later.
I don't think knowledge is the problem - I would say it's a pretty safe bet that most everyone in this thread knows C/C++, and that we make games with Lua because we choose to. Would you not hit a performance ceiling at some point with C++, C, Assemble language, or even pure machine language? Since LOVE uses luaJIT, we can make giant games without this stuff tripping us up, and in at least my mind that's an indication that we're getting to an age where unless you're really bad at writing fast code, you can make a smooth, feature-rich game without having to manually manage memory (though LOVE does actually allow inlined C code if I I know this correctly)
Xii wrote: Sat Dec 26, 2020 7:25 pm unless you actually start thinking about what the computer is doing with all those anonymous tables you're creating every frame
This is exactly the type of stuff we are talking/thinking about in this thread.
User avatar
ReFreezed
Party member
Posts: 612
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

Re: Optimization Stuff

Post by ReFreezed »

Xii wrote: 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.
Developers being "lazy" is hardly anything new. Games like the original Doom pushed the limits of the available systems at the time, and yet they had parts of the code that wasn't "as optimized as possible" simply because they didn't need to be. (If I remember correctly, the asset handling was pretty poor for example.)

Anyway, disabling the garbage collector is like going to the full extreme and doing absolutely everything yourself, even though the GC can actually help with many small things (especially in a language where you don't have good control over, or tools to deal with, memory related problems). I don't think it has to do with understanding how to achieve something without the having the GC running, but rather understanding that disabling it has a cost too that may be very high (thus the "it's nearly impossible to produce no garbage in non-trivial games" statement, unless I misunderstand grump, and consequently the advice you gave wasn't good, at least on its own).

Now, of course you should minimize the amount of garbage-collectable objects you create during normal updates if you care about performance, but banning table creation completely etc. makes me question why you even use Lua at all. Just switch to C or something at that point and you don't even have to worry about any garbage collector (not to mention other benefits, like having a compiler that produces optimized machine code directly). I feel like that's closer to the actual solution if the GC is the thing that gets in your way.
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
User avatar
Xii
Party member
Posts: 137
Joined: Thu Aug 13, 2020 9:09 pm
Contact:

Re: Optimization Stuff

Post by Xii »

Felix_Maxwell wrote: Sat Dec 26, 2020 10:39 pm Since LOVE uses luaJIT, we can make giant games without this stuff tripping us up
Can you provide an example of a giant game made in LÖVE without stopping the garbage collector?
ReFreezed wrote: Sat Dec 26, 2020 11:39 pm understanding that disabling [the garbage collector] has a cost too that may be very high
I don't... get what you're saying. Where's the high cost? You create your tables in love.load(), then just simply not create any tables anywhere else. I see no associated cost.

You can't assume the computer has infinite resources anyway and keep spawning stuff until your game crashes; you have to set upper limits on your game objects anyway. So then, knowing exactly how many objects you can have, create the tables once and be done with it.
ReFreezed wrote: Sat Dec 26, 2020 11:39 pm banning table creation completely etc. makes me question why you even use Lua at all.
The Lua garbage collector is just one tool in a toolbox, and one particularily unsuited for game development. I use Lua because it's a brilliant language in general, largely for its simplicity.
User avatar
slime
Solid Snayke
Posts: 3131
Joined: Mon Aug 23, 2010 6:45 am
Location: Nova Scotia, Canada
Contact:

Re: Optimization Stuff

Post by slime »

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.

Remember, the garbage collector scans through all tables every time it runs, and it completely stops your game while doing so. That's bad.
Lua's garbage collector is incremental - a GC cycle doesn't go through every single allocated object, it does it a bit at a time. It even has APIs to control how big that 'bit' is.

Xii wrote: Sun Dec 27, 2020 12:13 am Can you provide an example of a giant game made in LÖVE without stopping the garbage collector?
I don't know of any game (LÖVE or otherwise) that uses Lua without using the garbage collector in gameplay. A common strategy is to use the APIs I linked above to collect for a fixed duration every frame, but I don't believe avoiding collection entirely is done much at all even (especially) in extremely large codebases. It's not a very good strategy compared to incremental collection, because realistically there will be times when something allocates and times when something is not referenced any more, even in gameplay. It's useful to know when that happens, but it's not useful at all to let memory pile up because of it.
User avatar
Imagic
Prole
Posts: 44
Joined: Mon Sep 30, 2019 8:20 am
Contact:

Re: Optimization Stuff

Post by Imagic »

I want to add something which seems to be lacking when speaking about optimizations and not the regular not subtle "you should not think about optimization".

Optimizing is complicated. It's not just about the complexity of the optimization in itself, it's also about the heuristic behind it. Time wasted on irrelevant optimizations is time that could be put into relevant optimizations or anything else. Being able to know what is relevant or not is as important as knowing how to do the optimization. That heuristic may be developed by acquiring experience: profiling, experimenting, thinking. The relevance of an optimization may be seen as a ratio of impact / difficulty.

The Lua language is not perfect, but IMHO it is one of the best to work within high or low levels of abstractions when needed, especially with LuaJIT. As an example, a lot of stuff in LuaJIT are not optimized on purpose, because they are not considered relevant, e.g. they could increase the complexity of the entire VM without much benefit. The JIT compiler itself has a heuristic to decide when to compile the code, e.g. a counter for loops; that heuristic is part of the optimization process. It doesn't compile everything because, naively, "it would be faster to just have machine code".
User avatar
pgimeno
Party member
Posts: 3544
Joined: Sun Oct 18, 2015 2:58 pm

Re: Optimization Stuff

Post by pgimeno »

To add an example to grump's "nearly impossible" assertion, this simple code creates at least two string objects that need to be garbage-collected:

Code: Select all

x = 5
print("x = " .. x)
I don't think that "x = " is created as a temporary object, but I'm not sure. But for sure, tostring(x) is created, then the concatenated result, and both need to be cleaned up. If you disable the GC, you'll have a leak. Basically any string formed using string operations (e.g. string.match or string.sub) has the same problem.

The only solution I can think of, if your program needs any kind of variable character display, is to handle non-constant strings as their numeric ASCII codes, having a table of the character for every code, handling kerning yourself (simplified by restricting yourself to fixed-pitch fonts) for displaying, and reimplementing the string operations that your program needs yourself, to handle strings as tables of numbers.

That amount of wheel reinventing as a workaround to avoid generating garbage falls in the "nearly impossible" category, if you ask me.

From all LuaJIT types, the only ones that don't generate garbage are number, boolean, nil and lightuserdata. The rest: string, table, coroutine, function, userdata and cdata have the potential of causing leaks when not garbage collected. Calling Object:release() is not enough to free a Löve object, because that doesn't free the Lua-side object, which is normally cleared by the garbage collector, therefore if the GC is disabled and you release a Löve object, you'll have a leak.
User avatar
Felix_Maxwell
Prole
Posts: 24
Joined: Wed Dec 04, 2019 3:15 pm

Re: Optimization Stuff

Post by Felix_Maxwell »

ReFreezed wrote: Sat Dec 26, 2020 11:39 pm Developers being "lazy" is hardly anything new. Games like the original Doom pushed the limits of the available systems at the time, and yet they had parts of the code that wasn't "as optimized as possible" simply because they didn't need to be. (If I remember correctly, the asset handling was pretty poor for example.)
Here's a bit of John Blow talking about a flame war he had with John Romero about it. Turns out they did it the right way as you describe, putting the work only where it's needed. Or maybe you're not talking about that exactly, in which case I misunderstand you.
slime wrote: Sun Dec 27, 2020 12:54 am Lua's garbage collector is incremental - a GC cycle doesn't go through every single allocated object, it does it a bit at a time. It even has APIs to control how big that 'bit' is.
These sort of juicy details are why I started the thread, thank you.
Imagic wrote: Sun Dec 27, 2020 1:38 am Optimizing is complicated. It's not just about the complexity of the optimization in itself, it's also about the heuristic behind it. Time wasted on irrelevant optimizations is time that could be put into relevant optimizations or anything else. Being able to know what is relevant or not is as important as knowing how to do the optimization. That heuristic may be developed by acquiring experience: profiling, experimenting, thinking. The relevance of an optimization may be seen as a ratio of impact / difficulty.
Totally agree. I think it's pretty safe to say that optimization should only be needed within update() and draw(), and when its speed is becoming a real problem. One of my angles in starting this thread is not necessarily how to optimize pieces of existing code, but how to structure everything in a way that doesn't stress the system and has a clean code style. But also other macro-optimisations that just generally make games run faster. For example I was wondering if someone was going to say 'only draw drawables if they appear on the screen', which to me seems like an optimisation you can get away without, albeit with at least a slight scent of laziness.
Xii wrote: Sun Dec 27, 2020 12:13 am Can you provide an example of a giant game made in LÖVE without stopping the garbage collector?
Love is able to run any reasonably-scaled game without running into garbage collection problems, if that's what you're asking. It's not a problem that I've seen come up for many around here.
User avatar
Xii
Party member
Posts: 137
Joined: Thu Aug 13, 2020 9:09 pm
Contact:

Re: Optimization Stuff

Post by Xii »

slime wrote: Sun Dec 27, 2020 12:54 am Lua's garbage collector is incremental
Oh hey, that's good info. Didn't know that. Mitigates the issue a little bit. Still, the collector has to collect an equal amount of garbage produced - sooner or later. The longer the game runs, the sooner that moment approaches.
pgimeno wrote: Sun Dec 27, 2020 3:29 am handle strings as tables of numbers. [...] That amount of wheel reinventing as a workaround to avoid generating garbage falls in the "nearly impossible" category, if you ask me.
I disagree. My fonts are custom sprite sheets anyway, so I'm rendering characters one by one anyway. Iterating through an array drawing characters isn't "nearly impossible" at all, it's basic programming.

Do you use like, font fonts in your game? Isn't authoring font files far more laborious?
Felix_Maxwell wrote: Sun Dec 27, 2020 3:46 am how to structure everything in a way that doesn't stress the system [...] macro-optimisations that just generally make games run faster.
The computer is at its most efficient when data is both linear and homogenous.

Linear, meaning, the data it needs next is located directly after the data it accessed last, i.e. an array. When you access table[1], the computer predicts you'll need table[2] next and pre-loads it from memory for faster access. If instead you access elements randomly, the computer makes bad predictions and you'll have a cache miss, at worst, for every access.

A cache miss to a computer is like you trying to drink milk and discovering that your refridgerator is empty, and you have to go all the way to the supermarket to get milk. It's molasses growing uphill in January slow.

Homogenous, meaning, all the data it's going through is of the same structure. This is a little more involved in a dynamic language like Lua, because tables of tables are not homogenous. So when the computer is iterating through an array of tables, it needs to "stop and consider" the structure of each table separately. This is why you hear game devs talking about "structs of arrays (SoA) instead of Arrays of Structs (AoS)". It means, having two arrays xs[n] = number and ys[n] = number may be faster to iterate through in unison than a single array xys[n] = {number, number} (for e.g. game object coordinates in the game world).
Felix_Maxwell wrote: Sun Dec 27, 2020 3:46 am Love is able to run any reasonably-scaled game without running into garbage collection problems, if that's what you're asking.
I'm new to the community; could you link me to one you think fits that description to study?

I note that you downgraded your adjective from "giant" to "reasonably-scaled"...
User avatar
pgimeno
Party member
Posts: 3544
Joined: Sun Oct 18, 2015 2:58 pm

Re: Optimization Stuff

Post by pgimeno »

Xii wrote: Sun Dec 27, 2020 7:29 pm
pgimeno wrote: Sun Dec 27, 2020 3:29 am handle strings as tables of numbers. [...] That amount of wheel reinventing as a workaround to avoid generating garbage falls in the "nearly impossible" category, if you ask me.
I disagree. My fonts are custom sprite sheets anyway, so I'm rendering characters one by one anyway. Iterating through an array drawing characters isn't "nearly impossible" at all, it's basic programming.
Well, I admit it may depend on the type of game, but then you're limiting yourself to games that don't need anything beyond constant strings. You're letting your programming style impose limitations on the type of programs you can write. Something as basic as using tostring() to e.g. display a score, needs to be reinvented, because it generates garbage; if you don't want to reinvent it, forget about RPGs with battle scenes where the amount of points lost can be seen "vanishing" from the creatures, for example. No games with text input either (like the Quake console, or like Moonring), because these need some basic string parsing, unless you reinvent the string handling functions in a way that doesn't generate garbage. No games like Tangerine Tycoon, which needs to display floating point numbers with their exponent and decimals, something even harder to reinvent than an integer conversion routine. And the list goes on and on.

And not only strings. My technique for pixel-accurate collision detection wouldn't be usable, so if you want that kind of collisions, you have to do it in a much slower way, or use an external binary library somehow, which is typically a royal pain to compile for all platforms.
User avatar
Gunroar:Cannon()
Party member
Posts: 1085
Joined: Thu Dec 10, 2020 1:57 am

Re: Optimization Stuff

Post by Gunroar:Cannon() »

Felix_Maxwell wrote: Sun Dec 27, 2020 3:46 am . For example I was wondering if someone was going to say 'only draw drawables if they appear on the screen', which to me seems like an optimisation you can get away without, albeit with at least a slight scent of laziness.
that I've seen come up for many around here.
It's is a good thing to only draw what appears on screen and can be especially useful when you draw a lot. You can use love.graphics.setScissor(or something like that). Oh, and I think for i=1, loops instead of using ipairs is faster.
The risk I took was calculated,
but man, am I bad at math.

-How to be saved and born again :huh:
Post Reply

Who is online

Users browsing this forum: No registered users and 67 guests