My LPC creations:
I have been programming in LPC using MudOS since 1996. The first mudlib I worked with was Nightmare 4, but I soon moved on to a MUD with a mudlib based on the older Nightmare 3 mudlib. That MUD eventually allowed a group of us to use their mudlib to form our own MUD, and I have been tinkering with it for over a decade. Over that time, I have looked at nearly every publicly available LPC mudlib and driver, but I still come back to my lightly modified MudOS v22.2b14 driver with our mudlib that barely resembles its origins.
At some point, I became very interested in the runtime performance of the LPC code I was writing. In fact, maximizing performance became such an obsession that the readability of my LPC code suffers. Regardless, I'm unlikely to break my habit at this point, so the following is the list of micro-optimizations that I use in my code. Hopefully being aware of the patterns will marginally improve readability:
string x = a[0]["key1"]; string y = a[0]["key2"];
Can be replaced with:
mapping tmp; string x = (tmp = a[0])["key1"]; string y = tmp["key2"];
string x = func(); string y = x + "y";
Can be replaced with:
string x; string y = (x = func()) + "y";
mixed y = x ? func() : 0;
Can be replaced with:
mixed y = x && func();
And:
mixed y = x ? x : func();
Can be replaced with:
mixed y = x || func();
This technique can be combined with assignment inlining for lazy initialization. For example:
mixed y;
if (!x)
x = func();
y = func2(x);
Can be replaced with:
mixed y = func2(x || (x = func()));
Don't initialize to 0 - By default, all local variables are initialized to the undefined value. For example:
int i;
for (i = 0; i < end; i++)
x += func(i);
Can be replaced with:
int i;
for (; i < end; i++)
x += func(i);
Or more optimally with assignment inlining of the ++ operator:
int i;
while (i < end)
x += func(i++);
for (int i = 0, size = sizeof(arr); i < size; i++)
if (!arr[i])
return 0;
Can be replaced with:
foreach (x in arr)
if (!x)
return 0;
In fact, the foreach construct is fast enough that an independent variable can be maintained within the loop faster than the same for loop. For example:
for (int i = 0, size = sizeof(arr); i < size; i++)
if (!arr[i])
return i;
Can be replaced with:
foreach (x in arr)
{
if (!x)
return i;
i++;
}
Avoid allocation - When possible, avoid allocation of strings, arrays, and mappings. This is particularly important within loops. For example:
mixed *arr = ({});
while ((value = func()))
arr += ({value});
Can be replaced with C-style reallocation:
mixed *arr = allocate(16);
int used;
while ((value = f()))
{
if (used == sizeof(arr))
arr += allocate(used);
arr[used++] = value;
}
arr = arr[0..--used];
Additionally, MudOS uses reference counted garbage collection for all value types except int and float. If either of these value types are used, then the reallocation step can be done more simply:
mixed *arr = allocate(16);
int used;
while ((value = f()))
{
if (used == sizeof(arr))
arr += arr;
arr[used++] = value;
}
arr = arr[0..--used];
Avoid function calls - When possible, avoid function calls. If necessary, use looping control structures and reallocated local arrays to simulate the function call stack. The MudOS preprocessor is nearly as full-featured as a C preprocessor, and function-like macros can be used as a replacement to function calls in order to avoid code duplication.
Trailing 0 string byte - MudOS implements the index operator on strings such that an index value equal to the string length returns 0. This can be used to avoid additional checking when analyzing strings. For example:
if (strlen(str) && str[0] == '#')
func(str);
Can be safely replaced with:
if (str[0] == '#')
func(str);
Use switch - The switch control structure performs very well, and will outperform all multi-valued comparisons. For example:
if (x == "foo" || x == "bar")
func();
Can be replaced with the following if x is known to be a string or 0:
switch (x)
{
case "foo":
case "bar":
func();
}
Additionally, case ranges can be used in place of checking an upper and lower bound. For example:
if ((x >= 'a' && x <= 'z')
|| (x >= 'A' && x <= 'Z')
|| (x >= '0' && y <= '9')
|| x == '_')
func();
Can be replaced with the following if x is known to be an int:
switch (x)
{
case 'a'..'z':
case 'A'..'Z':
case '0'..'9':
case '_':
func();
}