Revision Difference
optimizationTips#565547
<cat>Dev.Lua</cat>
<title>Concepts - Optimization Tips</title>
# Knowing When to Optimize
While optimization can improve the performance of your code sometimes this can come at the cost of the readability of your code. If you or others is actively working on some code maintaining the readability of that code is important for quicker development time.
Knowing what state you want your code to be in should be the first thing you think about when approaching optimization.
- Is the code a final completed solution?
- will anyone ever wanna look at this code to figure out how it works?
- does 3 microseconds of performance really justify the illegible nature of the code?
This article may introduce some tips that are "technically faster" but harder to interpret at a glance. Before you implement them at the sake of speed really consider if compromising the legibility of this section of code needed.
⤶
With this all being said there are places you should focus on optimization, anything that runs regularly, like code inside the infamous three hooks `tick`, `think` and `render`, can impact performance and should be once of the first places you look when starting to optimize.
While proper optimization ups the code's performance, at times it may disperse the code's readability. If you or others are actively working on some code, maintaining the code's readability is more preferable first for shorter development time.
Knowing in which state you want your code to be should be the first thing on the mind before taking on optimizing.
- Is the code a final complete solution?
- Will anyone ever wanna look at this code to figure out how it works?
- Does saving 3 or so microseconds of CPU time justify potential consequent illegibility of the code?
This article introduces some tips that are technically faster but harder to interpret at a glance. Before you implement them at the sake of speed consider if compromising the legibility of this section of code is excusable.
⤶
That being said, there are places where you should prioritize optimization: anything that runs regularly (e.g. code inside those hooks/functions called every tick or frame) may impact performance and should be one of the first places you consider optimizing.
# The Most Important Tips
These tips are broadly applicable and more often than not should be the default. Each Subheading will provide the basic version of the tip and then a deeper understand as to why. This additional why section is completely optional, you don't need to understand why these tips work at a low level to use them.
## Use Local Variables!⤶
<page text="Local Variables">Beginner_Tutorial_Variables#localvariables</page> are significantly faster than global variables. This tip is so broadly applicable that the standard should be to always use local variables unless there is no way to achieve the desired result without using a local.
These tips are broadly applicable and more often than not should be the default. Each subheading will provide the basic version of the tip and then a deeper understanding of its rationale.
## Use Local Variables⤶
<page text="Local Variables">Beginner_Tutorial_Variables#localvariables</page> are noticeably faster than global variables. This tip is so broadly applicable that the standard should be to always use local variables unless there is no way to achieve the desired result without using a local.
```lua
myVar = 0 -- Bad! Lua is global by default
local myVar = 0 --Good! myVar will only be accessible within the defined scope⤶
someRegularVar = 0 -- Unfavorable. Lua is global by default
local someRegularVar = 0 -- Fine. 'someRegularVar' will only be accessible within the scope in which it's defined⤶
```
⤶
⤶
### Understanding Why
This difference comes from how the Lua Virtual Machine handles them internally. Local variables are compiled into **registers**, these are fixed-size slots allocated on the stack for each function. During runtime, each local variable is assigned an integer index (the offset) into this register array.
This means:
- Access to local variables now only need a direct index operation
- The Lua VM emits a single [opcode](https://en.wikipedia.org/wiki/Opcode) for accessing or modifying local variables
- Accessing local varibles bypass any need for name resolution or [hashing](https://en.wikipedia.org/wiki/Hash_function)
⤶
locals in [closures](https://en.wikipedia.org/wiki/Closure_(computer_programming)) are treated as [upvalues](https://www.lua.org/pil/27.3.3.html). These upvalues point directly to the stack (or heap allocated closure variable) if the function outlives the scope) maintaining its fast access.
Globals On the other hand, are not stored in fixed memory slots. Instead, they are entries into a special table. For Garrys Mod (Lua version 5.1) this is the `_G` table.
⤶
Locals in [closures](https://en.wikipedia.org/wiki/Closure_(computer_programming)) are treated as [upvalues](https://www.lua.org/pil/27.3.3.html). These upvalues point directly to the stack (or heap allocated closure variable) if the function outlives the scope) maintaining its fast access.
Globals on the other hand, are not stored in fixed memory slots. Instead, they are entries into a special table. For Garrys Mod (Lua version 5.1) this is the `_G` table.
The process to access a global variable from that table involves:
1. Hashing the variable string name
2. Performing a table lookup using that key
3. Returning or modifying the value associated with that key.
This where the next broadly applicable tip comes in super handy.
⤶
## Caching!⤶
Always cache repeated lookups of the same data. If the information doesn't change every time you look at it, there's no need to rebuild the entire table each time. Instead, build the table once and reuse it each time. You can then monitor for changes that might affect the data and then rebuild the table only when necessary.⤶
⤶
This is crucial in high performance areas of your code _(notable examples including hooks like `tick`, `think`, and `render`)_⤶
⤶
⤶
## Caching⤶
Try to always cache repeated lookups of the same data.⤶
⤶
If the information is persistent, there's no need to rebuild the entire table each time. Instead, build the table once and reuse it.⤶
⤶
If the information changes over time, you can monitor changes related to the stored data and then rebuild/update the table when necessary.⤶
⤶
This is crucial in performance-weighty areas of your code _(notable examples are hooks like Tick, Think, and Render-related)_.⤶
```lua
--
-- Bad Example
--
hook.Add( "Tick", "BadExample", function()
for i, v in ipairs( player.GetAll() ) do --Every time this is executed the player.GetAll() function will run⤶
print( v:GetName() )
hook.Add( "Tick", "*BadExample*", function()
for i, ply in ipairs( player.GetAll() ) do -- New table is created each tick and returned from C to Lua, thanks to the player.GetAll()⤶
print( ply:GetName() )
end
end )
--
-- Good Example
--
local cachedPlayers = {}
local function updateCache()
cachedPlayers = player.GetAll()
end
hook.Add( "PlayerConnect", "PlayerJoinedUpdateCache", updateCache )
hook.Add( "PlayerDisconnected", "PlayerLeftUpdateCache", updateCache )
local function DoOurThing()
for i, v in ipairs( cachedPlayers ) do -- Now we we have one table that we can reuse, instead of rebuilding a new one each time
print( v:GetName() )
local function DoStuff()
for i, ply in ipairs( cachedPlayers ) do -- Now we we have one table that we can reuse, instead of rebuilding a new one each time
print( ply:GetName() )
end
end
hook.Add( "Tick", "GoodExample", DoOurThing )
hook.Add( "Tick", "*GoodExample*", DoStuff )
-- Notice the two things we've improved here, avoiding creating repeated anonymous functions (closures) and using a cached table
```
### What to cache?
the simple thing to remember if object creation. if calling a function or completing an operation results in a object being created you want to do that as few times as possible.
Common Things Not Cached that **SHOULD** be cached
- <page>Color</page> Objects⤶
- unmoving <page>Vector</page> or <page>Angle</page> types⤶
- <page>Material</page> and <page>IMaterial</page> Types⤶
- <page>Entity</page> Type⤶
- unchanging <page>Player</page> Types⤶
- <page>Weapon</page> Type⤶
A thing to remember is object creation. If calling a function or completing an operation results in an object being created, you want to do that as few times as possible.
Common Things Not Cached that **better** be cached
- <page>Color</page>(-s)⤶
- <page>Vector</page>(-s)⤶
- <page>Angle</page>(-s)⤶
- <page>IMaterial</page>(-s)⤶
- <page>Entity</page>(-ies)⤶
- <page>Player</page>(-s)⤶
- <page>Weapon</page>(-s)⤶
### Understanding Why
This function goes hand in hand with avoiding repeated heap allocations. Creating new objects _(`{...}` or `function()`)_ allocates new memory for that object on each iteration, triggering garbage collection and hurting performance. Even efficient garbage collection carry runtime and memory costs.
⤶
## Smart Type Usage ( Color(), Vector(), Angle() )
Garry's Mod types like `Color()`, `Vector()`, and `Angle()` are just tables _(because everything in Lua is a table)_ which means calling these constructors falls into the same issues we covered in the caching section of this article. To summarize that section, avoid repeated object creation wherever possible.⤶
⤶
However that's not the only way to optimize the handling of these objects. In Garry's Mod you will often encounter the need to manipulate these objects in some way shape or form. While you can use regular operators like `+`, `-`, `*`, and `/` the way these work is by creating a new object of that type, and filling in the resulting operations. Instead of you use functions like:⤶
- <page>Vector:Add</page>⤶
- <page>Vector:Sub</page>⤶
- <page>Vector:Mul</page>⤶
- <page>Vector:Div</page>⤶
⤶
provided by from the vector class for example. These functions instead skip that object creation, and the resulting garbage collection call, to still complete the desired operation.⤶
⤶
## Using Lookup Tables Instead of Iteration⤶
Covered more in depth on <page>List-Styled_Tables</page>. Looking up a value in a table is a lot faster than iterating through the entire table to find which value you need.
⤶
If you know what key your desired value is stored at in a table, doing:⤶
Creating a *new* object implies memory allocation, initializing the object, and, commonly, constructing the object. That potentially triggers garbage collection, which may produce a lag, and increases CPU time.
⤶
Also, if supported, using arithmetic operators upon these objects produces a new object with the operation's result. If possible, replace with the corresponding metamethods, which will modify the original object instead.
⤶
⤶
## Using Lookup Tables Instead of Iteration⤶
Covered more in depth on [List-Styled Tables](/gmod/List-Styled_Tables).⤶
⤶
Looking up a value in a table by known key is much faster than iterating through the entire table to find the needed value. ⤶
⤶
`value = tbl["knownkey"]` > searching `value` through for-loop.⤶
⤶
⤶
## Localizing Common & Meta Functions⤶
Some coding applications run some set of functions **a lot**. When this is unavoidable it'll be resourceful to pick out most expensive and/or most frequently called functions and localize them.⤶
⤶
Balance here is a key. Localizing every function will be redundant and at times even counterproductive. Use this technique sparsely.
⤶
⤶
## Avoid Nested Loops (Exponential Growth)⤶
Loops are essential to programming. ⤶
⤶
But Nested Loops should, as a rule, be avoided at all if possible. This is because of exponential growth of the number of operations.⤶
Example:⤶
```lua
MyVar = myTable["TheKeyIWant"]⤶
```⤶
will be faster than needing to do something like⤶
```lua⤶
for i, v in ipairs( myTable) do -- Now we we have one table that we can reuse, instead of rebuilding a new one each time⤶
if MyVar == ValueIWant then⤶
MyVar = v⤶
for _, ply1 in player.Iterator() do⤶
for _, ply2 in player.Iterator() do⤶
DoThingHere()⤶
end
end
⤶
```⤶
⤶
## Localizing Common & Meta Functions⤶
Some coding applications will require you to run some standard functions a LOT. When this is unavoidable it can be beneficial to cherry pick out some of the more expensive functions and localize them.⤶
⤶
for example say we need to constantly track multiple entities on the map every tick. The best function for this would be to use <page>Entity:GetPos</page>. so we will localize this to our file.⤶
⤶
```lua⤶
local ENTITY = FindMetaTable("Entity")⤶
local getPos = ENTITY.GetPos -- get the metatables version of :GetPos() and store it in our local getPos()⤶
⤶
function PrintThatPosition(ent)⤶
print(getPos(ent))⤶
end⤶
```
This allows us to gain performance on <page>Entity:GetPos</page> calls. However, the key is to balance these, localizing every function you use will lead to bad code and in some cases the opposite effect. While this can provide large performance gains if used correctly, it should be used sparsely.
⤶
## Avoid Nested Loops (Exponential Growth)⤶
Loops are essential to programming. Nested Loops should be avoided if at all possible. This is because of the exponential growth they bring. The below example demonstrates a common performance sink: ⤶
```lua⤶
-- Bad Code⤶
for _, ply1 in player.Iterator() do⤶
for _, ply2 in player.Iterator() do⤶
DoThingHere()⤶
end⤶
end⤶
```⤶
this is because of the quick nature this code will grow in execution time. For 3 players this code will only run 9 times _(`3*3`)_ however the second you took this code to a server with 20 players that same code now needs to run over 400 times _(`20*20`)_⤶
⤶
## Minimize Networking⤶
the <page>net</page> library is one that every programmer will need to use at one point or another if they want to do anything between the client and server. However, in the source engine, the net library is very limited and not as robust of other games today. This means you must be select with the information your sending to the server and back.⤶
If we do some arithmetic, for 3 players this code will run only 9 times (`3 * 3`), but for 20 players this will run all the 400 times (`20 * 20`).
⤶
⤶
## Minimize Networking⤶
Consider reading <page text="Net Library Usage, the Improving part in particular">Net_Library_Usage#improving</page>.⤶
# Niche / Pico Optimizations⤶
This section includes code that falls under the umbrella of "yes its faster but at what cost". Some of the tips here only gain you one or two microseconds and significancy harm the legibility of your code. You should really only be included if you either know what your doing, or are out of other options.
⤶
**!!!! You Will Never _NEED_ These, and should avoid using them !!!!**⤶
⤶
##Using Inline Expression over Function Calls
Using inline expressions over function its technically faster. Note this example saves less than `0.01` of a second⤶
⤶
# Micro-Pico-Optimizations⤶
This is about squeezing out the capacity for high performance as maximally as achievable.
⤶
You'll never have a dire need in these techniques. This is quite suitable when the product is in a final state and you want to fine-tune it in terms of performance; or, you know what you're doing and that's your preference. But by and large it's preferable to save yourself time and not integrate these everywhere and always.⤶
⤶
### Using Inline Expression over Function Calls
```
table.insert(tbl, 0) -- The Standard Way⤶
tbl[#tbl + 1] = 7 -- Technically Faster
⤶
```⤶
⤶
##Optimizing Math Calculations ⤶
Computers can solve certain math equations quicker than others, restructuring math equations to increase the efficiency at which the computer can solve it leading to more performance, but this gain is menial at best. ⤶
⤶
This will also make your math MUCH harder to follow, if your working on a team, publishing an addon, or building a library chances are anyone who looks at your code will struggle to reverse how you got these numbers originally. ⤶
⤶
```⤶
-- Multiplication Over Division⤶
x / 2 -- Standard⤶
⤶
x * 0.5 -- Technically Faster ⤶
table.insert( tbl, 0 ) -- Standard⤶
tbl[#tbl + 1] = 7 -- Technically faster
```
⤶
### Math⤶
Computers can solve certain math equations quicker, restructuring math equations to increase its calculation's efficiency.⤶
⤶
This may also make your math harder to follow. In such a scenario, outlining your math in the code would be relevant.⤶
```
-- Squaring over Exponentiation ⤶
x^2 -- Standard⤶
⤶
x * x -- Technically Faster ⤶
--⤶
-- Multiplication over Division⤶
--⤶
x / 2 -- Standard⤶
⤶
x * 0.5 -- Technically faster⤶
⤶
⤶
--⤶
-- Squaring over Exponentiation⤶
--⤶
x ^ 2 -- Standard⤶
⤶
x * x -- Technically faster⤶
⤶
⤶
--⤶
-- Factoring Expressions⤶
--⤶
x * y + x * z + y * z -- Standard⤶
⤶
x * ( y + z ) + y * z -- Technically faster⤶
```
⤶
```⤶
-- Factoring Expressions⤶
x*y + x*z + y*z --Standard⤶
⤶
x * (y + z) + y*z --Technically Faster⤶
⤶
```⤶
⤶
⤶
⤶
# Notable Related Resources⤶
- https://gitspartv.github.io/LuaJIT-Benchmarks/⤶
* Focuses on the "Micro-Pico-Optimizations" thing⤶
Garry's Mod
Rust
Steamworks
Wiki Help