Howdy, folks. Some theoretic thread here.
Since my addons have grown in size over original gamemode in like x1.5, I'm starting to think about optimization, because GMod is single-core app, both server and clientside.
Which functions to avoid? I heard that making local copies of global functions like
[CODE]local allPlayers = player.GetAll()[/CODE]
gives some performance boost.
Also, is that true that
[CODE]for k=1,k=k+1,k<#table; do
shit = table[k]
end[/CODE]
is much more efficient than calling [I]for k, v in [B]pairs[/B](table)[/I]?
Any advices for advanced GMod coding are welcome. Lets talk about making this game smoother.
[I]
it's late here so i'm sorry for any grammatical errors i've done up there :P[/I]
Don't worry too much about optimization until you've actually seen a problem -- then find the slowest functions, and optimize them case-by-case. There aren't really any techniques that you can use everywhere to speed everything up; localizing globals can help if you're calling them 40,000 times a second, but LuaJIT already does it automatically I think, and the code clutter in your second example isn't worth saving a call to pairs().
Until you see slowdowns, focus on writing clean, readable code.
Most optimally, you should use loops in Garry's Mod (or in general with LuaJIT) like this:
[img]http://wiki.garrysmod.com/favicon.ico[/img] [url=http://wiki.garrysmod.com/page/Global/pairs]pairs[/url]
Use with discontinuous tables or those with string keys. This guarantees for every element to be iterated thru in the table.
[code]local tbl = { [0] = false, 1, 7, nil, 10, ["stringkey"] = "data" }
for k, v in pairs(tbl) do
-- k can be a string or number
print(k, v)
end[/code]
pairs is essentially the same as using [img]http://wiki.garrysmod.com/favicon.ico[/img] [url=http://wiki.garrysmod.com/page/Global/next]next[/url] with a while loop.
[img]http://wiki.garrysmod.com/favicon.ico[/img] [url=http://wiki.garrysmod.com/page/Global/ipairs]ipairs[/url] or [URL="https://www.lua.org/pil/4.3.4.html"]Numeric for[/URL]
Use with numeric and sequential tables. This is most tables returned by the engine, including [img]http://wiki.garrysmod.com/favicon.ico[/img] [url=http://wiki.garrysmod.com/page/player/GetAll]player.GetAll[/url]. Using ipairs vs the numeric for is mostly personal notation preference, although, in my experience, numeric for has been slightly more efficient when iterating over larger tables. Also note that having a nil value in the middle of a numerical sequence will stop ipairs but not the count operator (#).
[code]local tbl = { false, 1, 7, nil, 10, "data" }
--[[ Prints:
1 false
2 1
3 7
4 nil
5 10
6 "data"
]]
for i = 1, #tbl do
print(i, tbl[i])
end
--[[ Prints:
1 false
2 1
]]
for k, v in ipairs(tbl) do
print(k, v)
end[/code]
As for your concerns of localising player.GetAll and similar engine table functions, LuaJIT handles most of that optimisation, although, you may do something like:
[code]local player_GetAll = player.GetAll[/code]
At the top of the file if you wish. Otherwise, make sure to just run the function once through your implementation and just store the table in a local variable if you want to use it more than once per Lua function.
[QUOTE=Luni;51162952]Don't worry too much about optimization until you've actually seen a problem -- then find the slowest functions, and optimize them case-by-case. There aren't really any techniques that you can use everywhere to speed everything up; localizing globals can help if you're calling them 40,000 times a second, but LuaJIT already does it automatically I think, and the code clutter in your second example isn't worth saving a call to pairs().
Until you see slowdowns, focus on writing clean, readable code.[/QUOTE]
Thanks for reply. But, you know, there is, for example, DarkRP, which lags like freaking hell even though there are no addons on server and there is a HL2RP which is really smooth at most cases. I saw some code when I've been working over HL2RP - it has tons of microoptimisations like redefining global functions to local and so on, but it runs smooth at 95% cases for that price.
That's the reason for this thread - summon those hardcore coders, which could develop gamemode themselves and ask them for good advices on that. While we're waiting them to come, I'm happy to discuss this topic :3
Those optimizations are fairly small in the grand scheme of things. In most cases, you will only notice improvements on the nanosecond scale.
Before you attempt any optimization, you should use a profiler. If you try to guess what the slowest part is, you will be wrong most of the time, even if you are very experienced. Always use a profiler before optimizing.
As for networking, ask yourself: Can I get the same result while sending less data? Can I send this data less frequently, and still have acceptable gameplay? Also, don't use net.WriteTable (or any other 'easy' method of networking a table). You can always send the data more efficiently without it.
[QUOTE=code_gs;51162978]Most optimally, you should use loops in Garry's Mod (or in general with LuaJIT) like this:
[img]http://wiki.garrysmod.com/favicon.ico[/img] [url=http://wiki.garrysmod.com/page/Global/pairs]pairs[/url]
Use with discontinuous tables or those with string keys. This guarantees for every element to be iterated thru in the table.
[code]local tbl = { [0] = false, 1, 7, nil, 10, ["stringkey"] = "data" }
for k, v in pairs(tbl) do
-- k can be a string or number
print(k, v)
end[/code]
pairs is essentially the same as using [img]http://wiki.garrysmod.com/favicon.ico[/img] [url=http://wiki.garrysmod.com/page/Global/next]next[/url] with a while loop.
[img]http://wiki.garrysmod.com/favicon.ico[/img] [url=http://wiki.garrysmod.com/page/Global/ipairs]ipairs[/url] or [URL="https://www.lua.org/pil/4.3.4.html"]Numeric for[/URL]
Use with numeric and sequential tables. This is most tables returned by the engine, including [img]http://wiki.garrysmod.com/favicon.ico[/img] [url=http://wiki.garrysmod.com/page/player/GetAll]player.GetAll[/url]. Using ipairs vs the numeric for is mostly personal notation preference, although, in my experience, numeric for has been slightly more efficient when iterating over larger tables. Also note that having a nil value in the middle of a numerical sequence will stop ipairs but not the count operator (#).
[code]local tbl = { false, 1, 7, nil, 10, "data" }
--[[ Prints:
1 false
2 1
3 7
4 nil
5 10
6 "data"
]]
for i = 1, #tbl do
print(i, tbl[i])
end
--[[ Prints:
1 false
2 1
]]
for k, v in ipairs(tbl) do
print(k, v)
end[/code]
As for your concerns of localising player.GetAll and similar engine table functions, LuaJIT handles most of that optimisation, although, you may do something like:
[code]local player_GetAll = player.GetAll[/code]
At the top of the file if you wish. Otherwise, make sure to just run the function once through your implementation and just store the table in a local variable if you want to use it more than once per Lua function.[/QUOTE]
Thanks. Never knew ipairs run faster than regular pairs. Also, I've been reading stuff about player.GetAll() and ents.GetAll() being extremely slow. Can you prove that?
Thanks :3
[QUOTE=igodsewer;51163023]Thanks. Never knew ipairs run faster than regular pairs. Also, I've been reading stuff about player.GetAll() and ents.GetAll() being extremely slow. Can you prove that?
Thanks :3[/QUOTE]
They're really not unless you're calling them a ridiculous amount of times per tick.
[QUOTE=Jcw87;51163002]Those optimizations are fairly small in the grand scheme of things. In most cases, you will only notice improvements on the nanosecond scale.
Before you attempt any optimization, you should use a profiler. If you try to guess what the slowest part is, you will be wrong most of the time, even if you are very experienced. Always use a profiler before optimizing.
As for networking, ask yourself: Can I get the same result while sending less data? Can I send this data less frequently, and still have acceptable gameplay? Also, don't use net.WriteTable (or any other 'easy' method of networking a table). You can always send the data more efficiently without it.[/QUOTE]
Interseting about networking tables. Is that because they contain markup? Do you mind explaining this?
Is there a way to send a networked table to user? Could I do stuff like
[code]
net.Start("tablelikesequence")
net.WriteString("meta data")
net.WriteInt(#tableToSend, 32)
for k, v in pairs(tableToSend) do
net.WriteString(k)
net.WriteInt(v, 32)
end
net.Send(ply)
[/code]
Is that a good workaround to avoid direct sending of tables? I'm asking because Im using net tables often in my scripts.
[QUOTE=igodsewer;51163050]Interseting about networking tables. Is that because they contain markup? Do you mind explaining this?
Is there a way to send a networked table to user? Could I do stuff like
[code]
net.Start("tablelikesequence")
net.WriteString("meta data")
net.WriteInt(#tableToSend)
for k, v in ipairs(tableToSend)
...
end
net.Send(ply)
[/code]
Is that a good workaround to avoid direct sending of tables? I'm asking because Im using net tables often in my scripts.[/QUOTE]
Only use net.WriteTable if the datatypes in it are ambiguous, and even then you should look for alternative solutions. Otherwise, the solution is to send the data per element (WriteString for a table full of strings, etc). WriteTable is bloated due to it having to send the data as well as the data type to interpret the data as.
[QUOTE=code_gs;51163064]Only use net.WriteTable if the datatypes in it are ambiguous, and even then you should look for alternative solutions. Otherwise, the solution is to send the data per element (WriteString for a table full of strings, etc). WriteTable is bloated due to it having to send the data as well as the data type to interpret the data as.[/QUOTE]
You are my hero :3 Ill rework most of my networking code. Ive got used to tables networking, theyre very easy to code.
[QUOTE=code_gs;51162978] note that having a nil value in the middle of a numerical sequence will stop ipairs but not the count operator (#).
[/QUOTE]
This is not true, I can't remember exactly how the # works with non sequential tables, although, I know putting a nil in the table will break the sequence.
Using LuaJIT
[quote]
> T = {1, 2, 3, nil, nil, 1}
> print(#T)
3
>
[/quote]
[quote]
> T = {1, 2, 3, 4, 5, nil, nil, 1, 2, 3, 4}
> print(#T)
11
>
[/quote]
It's somewhat "random"
[QUOTE=bigdogmat;51165037]This is not true, I can't remember exactly how the # works with non sequential tables, although, I know putting a nil in the table will break the sequence.
-snip-
It's somewhat "random"[/QUOTE]
The length operator is undefined for non-sequential tables, [del]unless there is a length defined (see [url]https://www.lua.org/pil/19.1.html[/url])[/del]. You can't easily predict what you'll get if the array portion of your table has holes in it - it depends on the implementation and the low-level optimizations.
The only thing that's guaranteed IIRC, is this: [lua]tab[#tab] ~= nil and tab[#tab + 1] == nil[/lua]
[editline]7th October 2016[/editline]
Actually, I may have been wrong about table.getn and table.setn affecting the length operator. You'd be wise to ignore that or test it yourself.
Sorry, you need to Log In to post a reply to this thread.