User:Braindawg/performance
This page includes performance tips and tricks to improve your VScript performance. While some of these tricks can be transferred to other supported titles (such as ), they were all tested in . Your mileage may vary in other games.
Contents
Folding Functions
Folding functions in the context of VScript means folding them into the root table. It is recommended that you do this for functions that are commonly used in expensive operations.
Benchmark
::ROOT <- getroottable()
foreach(k, v in ::NetProps.getclass())
if (k != "IsValid" && !(k in ROOT))
ROOT[k] <- ::NetProps[k].bindenv(::NetProps)
foreach(k, v in ::Entities.getclass())
if (k != "IsValid" && !(k in ROOT))
ROOT[k] <- ::Entities[k].bindenv(::Entities)
BeginBenchmark()
for (local prop; prop = Entities.FindByClassname(prop, "prop_dynamic");)
NetProps.GetPropString(prop, "m_iName")
printf("unfolded took %.4f ms\n", EndBenchmark());
BeginBenchmark()
for (local prop; prop = FindByClassname(prop, "prop_dynamic");)
GetPropString(prop, "m_iName")
printf("folded took %.4f ms\n", EndBenchmark());
Result:
Configuration | Results |
---|---|
Unfolded
|
0.1439ms
|
Folded
|
0.0999ms
|
Constants
Folding Constants
Similar to folding functions, folding pre-defined Constant values into the constant table (or the root table) increases performance significantly.
Benchmark
local j = 0
BeginBenchmark()
for (local i = 1; i <= Constants.Server.MAX_EDICTS; i++)
j++
printf("unfolded constant took %.4f ms\n", EndBenchmark());
const MAX_EDICTS = 2048
local e = 0
BeginBenchmark()
for (local i = 1; i <= MAX_EDICTS; i++)
e++
printf("folded constant took %.4f ms\n", EndBenchmark());
Result:
Configuration | Results |
---|---|
Unfolded
|
0.1127ms
|
"Folded"
|
0.0423ms
|
Root table vs Constant table
Unlike values inserted into the root table, values inserted into the constant table are cached at the pre-processor level. What this means is, while accessing them is faster, it may not be feasible to fold your constants into the constant table if they are folded in the same script file that references them.
If you intend to insert values into the constant table, you must do this before any other scripts are executed, otherwise your script will not be able to read any values from it.
Benchmark
::ROOT_VALUE <- 2
const CONST_VALUE = 2
BeginBenchmark();
for (local i = 0; i <= 10000; i++)
i += CONST_VALUE
printf("CONST took %.4f ms\n", EndBenchmark());
BeginBenchmark();
for (local i = 0; i <= 10000; i++)
i += ROOT_VALUE
printf("ROOT took %.4f ms\n", EndBenchmark());
Result:
Configuration | Results |
---|---|
Constant
|
0.0767ms
|
Root
|
0.1037ms
|
String Formatting
Squirrel supports two main ways to format strings: Concatenation using the + symbol, and the format()
function.
format()
does not support formatting entity handles and other VScript-specific datatypes, however it does support formatting strings, integers, and floats. It is also significantly faster than concatenation.
ToKVString
the TOKVString()
VScript function takes a Vector/QAngle and formats the values into a string. For example, Vector(0, 0, 0).ToKVString()
would be "0 0 0"
On top of being less cumbersome to write, ToKVString()
is marginally faster than format()
. Interestingly though, when formatting multiple ToKVString()
outputs into a new string, concatenation may be faster.
Benchmark
local mins = Vector(-1, -2, -3);
local maxs = Vector(1, 2, 3);
local keyvalues = { responsecontext = "-10 -10 -10 10 10 10" }
BeginBenchmark();
for (local i = 0; i < 10000; i++)
keyvalues.responsecontext <- mins.x.tostring() + "," + mins.y.tostring() + "," + mins.z.tostring() + "," + maxs.x.tostring() + "," + maxs.y.tostring() + "," + maxs.z.tostring()
printf("concat took %.4f ms\n", EndBenchmark());
BeginBenchmark();
for (local i = 0; i < 10000; i++)
keyvalues.responsecontext <- format("%g,%g,%g,%g,%g,%g", mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z)
printf("format took %.4f ms\n", EndBenchmark());
BeginBenchmark();
for (local i = 0; i < 10000; i++)
keyvalues.responsecontext <- format("%s %s", mins.ToKVString(), maxs.ToKVString())
printf("kvstring took %.4f ms\n", EndBenchmark());
BeginBenchmark();
for (local i = 0; i < 10000; i++)
keyvalues.responsecontext <- mins.ToKVString() + " " + maxs.ToKVString()
printf("kvstring concat took %.4f ms\n", EndBenchmark());
Result:
Configuration | Results |
---|---|
concat
|
39.0847ms
|
format
|
24.0123ms
|
ToKVString
|
19.9377ms
|
ToKVString + concat
|
18.5166ms
|
Spawning Entities
in VScript, there are four common ways to spawn entities:
- CreateByClassname + DispatchSpawn
- SpawnEntityFromTable
- SpawnEntityGroupFromTable
- point_script_template entity + AddTemplate
CreateByClassname + DispatchSpawn vs SpawnEntityFromTable
In general, performance is not a major concern when spawning entities. In special circumstances though, you may need to spawn and kill a temporary entity in an already expensive function. A notable example of an entity that would need this is trigger_stun. This entity will not attempt to re-stun the same player multiple times, so it is not possible to spawn a single entity and repeatedly fire StartTouch/EndTouch on the same target.
In situations like this, CreateByClassname + DispatchSpawn is roughly 4x faster in comparison to SpawnEntityFromTable
.
Benchmark
BeginBenchmark();
trigger_stun = SpawnEntityFromTable("trigger_stun",
{
stun_type = 2,
stun_effects = true,
stun_duration = 3,
move_speed_reduction = 0.1,
trigger_delay = 0.1,
spawnflags = 1,
});
printf("table took %.4f ms\n", EndBenchmark());
BeginBenchmark();
trigger_stun = Entities.CreateByClassname("trigger_stun");
trigger_stun.KeyValueFromInt("stun_type", 2);
trigger_stun.KeyValueFromInt("stun_effects", 1);
trigger_stun.KeyValueFromFloat("stun_duration", 3.0);
trigger_stun.KeyValueFromFloat("move_speed_reduction", 0.1);
trigger_stun.KeyValueFromFloat("trigger_delay", 0.1);
trigger_stun.KeyValueFromInt("spawnflags", 1);
printf("manual took %.4f ms\n", EndBenchmark());
Result:
Configuration | Results |
---|---|
SpawnEntityFromTable
|
0.0428ms
|
CreateByClassname
|
0.0156ms
|
SpawnEntityGroupFromTable vs point_script_template
When spawning multiple entities at the same time, it is more efficient to use either SpawnEntityGroupFromTable
or a point_script_template entity. These options also have the added benefit of respecting parent hierarchy, so the parentname
keyvalue works as intended.
point_script_template is both more flexible and faster. SpawnEntityGroupFromTable has several major limitations in comparison to point_script_template, and is generally not recommended. See the VScript documentation for more details on how to use point_script_template.
Benchmark
//run these functions in EntFire to account for the delay.
function PerfBegin()
{
BeginBenchmark();
}
function PerfEnd()
{
printf("point_script_template took %.4f ms\n", EndBenchmark());
}
BeginBenchmark();
//spawn origins are right outside of bigrock spawn
SpawnEntityGroupFromTable({
[0] = {
func_rotating =
{
message = "hl1/ambience/labdrone2.wav",
volume = 8,
responsecontext = "-1 -1 -1 1 1 1",
targetname = "crystal_spin",
spawnflags = 65,
solidbsp = 0,
rendermode = 10,
rendercolor = "255 255 255",
renderamt = 255,
maxspeed = 48,
fanfriction = 20,
origin = Vector(278.900513, -2033.692993, 516.067200),
}
},
[2] = {
tf_glow =
{
targetname = "crystalglow",
parentname = "crystal",
target = "crystal",
Mode = 2,
origin = Vector(278.900513, -2033.692993, 516.067200),
GlowColor = "0 78 255 255"
}
},
[3] = {
prop_dynamic =
{
targetname = "crystal",
solid = 6,
renderfx = 15,
rendercolor = "255 255 255",
renderamt = 255,
physdamagescale = 1.0,
parentname = "crystal_spin",
modelscale = 1.3,
model = "models/props_moonbase/moon_gravel_crystal_blue.mdl",
MinAnimTime = 5,
MaxAnimTime = 10,
fadescale = 1.0,
fademindist = -1.0,
origin = Vector(278.900513, -2033.692993, 516.067200),
angles = QAngle(45, 0, 0)
}
},
})
printf("SpawnEntityGroupFromTable took %.4f ms\n", EndBenchmark());
BeginBenchmark()
local script_template = Entities.CreateByClassname("point_script_template")
script_template.AddTemplate("func_rotating", {
message = "hl1/ambience/labdrone2.wav",
volume = 8,
targetname = "crystal_spin2",
spawnflags = 65,
solidbsp = 0,
rendermode = 10,
rendercolor = "255 255 255",
renderamt = 255,
maxspeed = 48,
fanfriction = 20,
origin = Vector(175.907211, -2188.908691, 516.031311),
})
script_template.AddTemplate("tf_glow", {
target = "crystal2",
Mode = 2,
origin = Vector(175.907211, -2188.908691, 516.031311),
GlowColor = "0 78 255 255"
})
script_template.AddTemplate("prop_dynamic",{
targetname = "crystal2",
solid = 6,
renderfx = 15,
rendercolor = "255 255 255",
renderamt = 255,
physdamagescale = 1.0,
parentname = "crystal_spin2",
modelscale = 1.3,
model = "models/props_moonbase/moon_gravel_crystal_blue.mdl",
MinAnimTime = 5,
MaxAnimTime = 10,
fadescale = 1.0,
fademindist = -1.0,
origin = Vector(175.907211, -2188.908691, 516.031311),
angles = QAngle(45, 0, 0)
})
EntFireByHandle(script_template, "CallScriptFunction", "PerfBegin", -1, null, null)
EntFireByHandle(script_template, "ForceSpawn", "", -1, null, null)
EntFireByHandle(script_template, "CallScriptFunction", "PerfEnd", -1, null, null)
Result:
Configuration | Results |
---|---|
SpawnEntityGroupFromTable
|
0.2382ms
|
point_script_template
|
0.1100ms
|
Iterating through players
players are collected in a mapspawn.nut
::playerArray <- []
::Events <- {
function OnGameEvent_player_team(params)
{
local player = GetPlayerFromUserID(params.userid)
if (playerArray.find(player) != null) return
playerArray.append(player)
}
function OnGameEvent_player_disconnect(params)
{
local player = GetPlayerFromUserID(params.userid)
if (playerArray.find(player) == null) return
playerArray.remove(player)
}
}
__CollectGameEventCallbacks(Events)
::maxClients <- MaxClients().tointeger()
BeginBenchmark()
for (local player; player = Entities.FindByClassname(player, "player");)
{
printl(player)
}
printf("\nFindByClassname took %.4f ms\n", EndBenchmark())
BeginBenchmark()
for (local i = 1; i <= maxClients; i++)
{
local player = PlayerInstanceFromIndex(i)
if (player == null) continue
printl(player)
}
printf("\nIndex iteration took %.4f ms\n", EndBenchmark())
BeginBenchmark()
foreach(player in playerArray)
{
printl(player)
}
printf("\nPlayer array iteration took %.4f ms\n", EndBenchmark())
Result:
Configuration | Results |
---|---|
FindByClassname
|
0.1289ms
|
Index iteration
|
0.0856ms
|
Array iteration
|
0.0679ms
|