Difference between revisions of "Optimizing Plugins (SourceMod Scripting)"
m |
Joinedsenses (talk | contribs) (Update syntax. Remove decl notes (deprecated)) |
||
(9 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
==Introduction== | ==Introduction== | ||
− | + | This guide contains some general suggestions as to how to improve local performance in your code. However, '''take note'''. Do not use this as a guide to prematurely optimizing your program. You should focus on making your scripts easily readable and maintainable. Premature optimization can make your code more complex, introducing bugs that you otherwise wouldn't have had. | |
− | |||
− | |||
− | |||
− | + | If you do notice performance problems on your server, and you think they are introduced by your plugin, there are a few steps you can take. The best way to start is to get a profiler. You can use the [[SourceMod Profiler]] to tell you how much time is being spent in script functions. | |
− | + | However, if you are worried about your code, you can also try to estimate the cost of operations in your program. Anything that happens repeatedly in a small period of time - the contents of a loop, the body of a timer, an <tt>OnGameFrame</tt> hook - are good targets. | |
− | + | '''DISCLAIMER:''' The units of time in this article are comparative only. We estimate most SourcePawn operations as costing 1-10 cycles, where a cycle is measured in nanoseconds. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | =Estimating Cost= | |
− | |||
− | |||
− | + | The goal of estimating cost is to figure out two things: | |
+ | * How expensive an operation is, run only once. | ||
+ | * How many times the operation is repeated. | ||
+ | Let's try this with a simple example loop below. Most syntactic language features have a simple cost. Natives can be trickier. | ||
− | + | <sourcepawn>for (int i = 0; i < strlen(string); i++) | |
− | |||
− | |||
− | |||
{ | { | ||
− | + | if (string[i] == '"') | |
− | + | { | |
− | + | return i; | |
− | + | } | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | = | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
− | </ | + | </sourcepawn> |
− | + | First, let's determine how many times this loop will run. If the length of <tt>string</tt> is ''n'', then the loop will run ''n'' times. Next, let's see what each iteration of the loop costs: | |
+ | * <tt>strlen(string)</tt>: This has to count all of the characters in |string|. Let's say counting a character costs 1 unit of time. Therefore, |strlen(string)| will cost ''n'' units of time. | ||
+ | * <tt>if (string[i] == '"')</tt>: This contains an array load and comparison. Let's say those each cost 1 unit of time, totaling 2. | ||
+ | * <tt>i++</tt>: This increments a local variable. Let's say that costs 1 unit of time. | ||
− | < | + | Therefore, every iteration of the loop costs <tt>n + 3</tt> units of time. |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | </ | ||
− | + | That means this loop may cost up to <tt>(n * (n + 3))</tt> units of time. Now, if we know that <tt>n</tt> is always small - say, under 100 - that might not be a problem. But what happens if the string has 10,000 characters? Now, the loop will take over 100,000,000 units of time! If a "unit of time" is even as small as a nanosecond, that loop will take a whole tenth of a second, delaying the server by multiple frames! | |
− | < | + | This example is easy to fix. We can identify that <tt>strlen</tt> magnifies the cost of the loop, and rewrite it like this: |
+ | <sourcepawn>int length = strlen(string); | ||
+ | for (int i = 0; i < length; i++) | ||
{ | { | ||
− | + | if (string[i] == '"') | |
− | + | { | |
− | + | return i; | |
− | + | } | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
− | </ | + | </sourcepawn> |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | Now the cost of this loop is just: <tt>n</tt> times a very, very small amount of time. | |
− | + | =Avoid Large KeyValues= | |
+ | KeyValues is an n-ary structure using linked lists. This type of structure is extremely expensive to allocate and traverse. While it might be suitable for tiny pieces of information (that is, under 10KB of data or so), its complexity growth is very poor. | ||
− | + | If you load KeyValues data, you should make an effort to, at the very least, cache its Handle so you don't need to reparse the file every time. Caching its contents on a needed basis would be a bonus as well. | |
− | + | If you're trying to use a KeyValues file with thousands of entries and updating/loading it on events such as player connections or disconnections, you will find that the structure will grow to an unmanageably slow size. If that's the case, you should consider moving to something like SQLite or MySQL. | |
[[Category:SourceMod Scripting]] | [[Category:SourceMod Scripting]] |
Latest revision as of 17:49, 29 March 2020
Introduction
This guide contains some general suggestions as to how to improve local performance in your code. However, take note. Do not use this as a guide to prematurely optimizing your program. You should focus on making your scripts easily readable and maintainable. Premature optimization can make your code more complex, introducing bugs that you otherwise wouldn't have had.
If you do notice performance problems on your server, and you think they are introduced by your plugin, there are a few steps you can take. The best way to start is to get a profiler. You can use the SourceMod Profiler to tell you how much time is being spent in script functions.
However, if you are worried about your code, you can also try to estimate the cost of operations in your program. Anything that happens repeatedly in a small period of time - the contents of a loop, the body of a timer, an OnGameFrame hook - are good targets.
DISCLAIMER: The units of time in this article are comparative only. We estimate most SourcePawn operations as costing 1-10 cycles, where a cycle is measured in nanoseconds.
Estimating Cost
The goal of estimating cost is to figure out two things:
- How expensive an operation is, run only once.
- How many times the operation is repeated.
Let's try this with a simple example loop below. Most syntactic language features have a simple cost. Natives can be trickier.
for (int i = 0; i < strlen(string); i++) { if (string[i] == '"') { return i; } }
First, let's determine how many times this loop will run. If the length of string is n, then the loop will run n times. Next, let's see what each iteration of the loop costs:
- strlen(string): This has to count all of the characters in |string|. Let's say counting a character costs 1 unit of time. Therefore, |strlen(string)| will cost n units of time.
- if (string[i] == '"'): This contains an array load and comparison. Let's say those each cost 1 unit of time, totaling 2.
- i++: This increments a local variable. Let's say that costs 1 unit of time.
Therefore, every iteration of the loop costs n + 3 units of time.
That means this loop may cost up to (n * (n + 3)) units of time. Now, if we know that n is always small - say, under 100 - that might not be a problem. But what happens if the string has 10,000 characters? Now, the loop will take over 100,000,000 units of time! If a "unit of time" is even as small as a nanosecond, that loop will take a whole tenth of a second, delaying the server by multiple frames!
This example is easy to fix. We can identify that strlen magnifies the cost of the loop, and rewrite it like this:
int length = strlen(string); for (int i = 0; i < length; i++) { if (string[i] == '"') { return i; } }
Now the cost of this loop is just: n times a very, very small amount of time.
Avoid Large KeyValues
KeyValues is an n-ary structure using linked lists. This type of structure is extremely expensive to allocate and traverse. While it might be suitable for tiny pieces of information (that is, under 10KB of data or so), its complexity growth is very poor.
If you load KeyValues data, you should make an effort to, at the very least, cache its Handle so you don't need to reparse the file every time. Caching its contents on a needed basis would be a bonus as well.
If you're trying to use a KeyValues file with thousands of entries and updating/loading it on events such as player connections or disconnections, you will find that the structure will grow to an unmanageably slow size. If that's the case, you should consider moving to something like SQLite or MySQL.