I’ve been working on a C# maze generator for a while which can generate mazes of like 128000x128000 pixels. All memory usage is optimized already so I’m currently looking at speeding the generation up.
A problem (well more off an interest point) I found was the following (just some example code to illustrate the problem):
This code runs in about 1.4 seconds on my machine when pixelChanged is null:
public void Go()
{
for (int i = 0; i < bitjes.Length; i++)
{
BitArray curArray = bitjes[i];
for (int y = 0; y < curArray.Length; y++)
{
curArray[y] = !curArray[y];
GoDrawPixel(i, y, false);
}
}
}
public void GoDrawPixel(int i, int y, Boolean enabled)
{
if (pixelChanged != null)
{
pixelChanged.Invoke(new PixelChangedEventArgs(i, y, enabled));
}
}
Where the following code runs actually 0.4 seconds faster
public void Go()
{
for (int i = 0; i < bitjes.Length; i++)
{
BitArray curArray = bitjes[i];
for (int y = 0; y < curArray.Length; y++)
{
curArray[y] = !curArray[y];
if (pixelChanged != null)
{
pixelChanged.Invoke(new PixelChangedEventArgs(i, y, false));
}
}
}
}
It seems that when just calling an “empty” method takes about 20% of the cpu this algorithm uses. Isn’t this strange? I’ve tried to compile the solution in debug and release mode but haven’t found any noticeable differences.
What this means is that every method call I have in this loop will slow my code down by about 0.4 seconds. Since the maze generator code currently consist of a lot of seperate method calls that excecute different actions this starts to get a substantial ammount.
I've also checked google and other posts on Stack Overflow but haven't really found a solution yet.
Is it possible to automatically optimize code like this? (Maybe with things like project Roslyn???) or should I place everything together in one big method?
Edit: I'm also interested in maybe some analysis on the JIT/CLR code differences in these 2 cases. (So where this problem actually comes from)
Edit2: All code was compiled in release mode
No, the method assetLoader. getSupportedExtensions() is called only once before the first iteration of the loop, and is used to create an Iterator<String> used by the enhanced for loop.
As others have said, the cost of the method call is trivial-to-nada, as the compiler will optimize it for you. That said, there are dangers in making method calls to instance methods from a constructor.
1) Function call overhead doesn't occur. 2) It also saves the overhead of push/pop variables on the stack when function is called. 3) It also saves overhead of a return call from a function. 4) When you inline a function, you may enable compiler to perform context specific optimization on the body of function.
In a nutshell: function calls may or may not impact performance. The only way to tell is to profile your code. Don't try to guess where the slow code spots are, because the compiler and hardware have some incredible tricks up their sleeves.
It is a problem, JIT has an inline optimization for methods (where the whole method code is actually injected inside the calling parent code) but this only happens for methods that are compiled to 32 bytes or less. I have no idea why the 32 byte limitation exists and would also like to see an 'inline' keyword in C# like in C/C++ exactly for these issues.
The first thing I would try would be making it static rather than instance:
public static void GoDrawPixel(PixelChangedEventHandler pixelChanged,
int x, int y, bool enabled)
{
if (pixelChanged != null)
{
pixelChanged.Invoke(new PixelChangedEventArgs(x, y, enabled));
}
}
this changes a few things:
callvirt
becomes call
- which avoids some minor overheadsldarg0
/ldfld
pair (this.pixelChanged
) becomes a single ldarg0
the next thing I would look at is PixelChangedEventArgs
; it might be that passing that as a struct is cheaper if it avoids a lot of allocations; or perhaps just:
pixelChanged(x, y, enabled);
(raw arguments rather than a wrapper object - requires a change in signature)
Is this in debug or release mode? Method calls are fairly expensive, but they may be inlined when you build/run it in release mode. It will not get any optimization from the compiler in debug mode.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With