Home > front end >  How to improve a select query performance?
How to improve a select query performance?

Time:06-29

I am working on a method that basically takes an array of Color (Unity Type) as parameter and replace all colors contained in a list by another color contained in another color array. For example you have 3 mask colors (red, blue green), so each green pixel will be replaced by texture 1, each red by texture 2, etc...

My previous method was messy, with a lot of if loops and took around 1,6ms to be performed, which I thought was really bad.

Finally I came up with another method using LINQ which is a lot cleaner, but surprisingly this query takes actually more than 4,5ms to be performed (for a 64x64 array) :

Color[] newTexture = texture.GetPixels()
    .Select((color, index) => masks.Contains(color) ? textures[Array.IndexOf(masks, color)][index] : color)
    .ToArray();

I tried parallelization but it is even worse:

Color[] newTexture = texture.GetPixels().AsParallel()
    .Select((p, i) => masks.Contains(p) ? textures[Array.IndexOf(masks, p)][i] : p)
    .ToArray();

Do you have any idea why this query takes so much time and how I could improve it ?

Thank you very much for your help.

CodePudding user response:

The unfortunate truth of LINQ is that it will pretty much never be faster than a for loop. It is because unlike languages like C and Rust where lambda expressions can be inlined/compiled-away, any time you invoke an Action object, or call MoveNext() on IEnumerable<T> etc. there is a VTable lookup. As such you shouldn't really use it for games, at least not where performance matters.

Aside from the inherent performance problems LINQ suffers from, you are needlessly copying the copy of the pixels and you are looking through the mask array twice.

You mentioned your for loop was messy? Would something like this work better?

// GetPixels() already gives you a copy, no need to copy it again
Color[] newTexture = texture.GetPixels();

// Microsoft's compilers make better code for spans than for arrays,
// maybe Mono does too?
Span<Color> textureSpan = newTexture;
Span<Color> maskSpan = masks;

for (int i = 0; i < textureSpan.Length; i  )
{
    // by checking if the index returned from IndexOf is not negative,
    // you can omit Contains()
    int maskIndex = maskSpan.IndexOf(textureSpan[i]);
    // only change the pixels that need to be changed
    if(maskIndex >= 0)
    {
        textureSpan[i] = textures[maskIndex][i];
    }
}

return newTexture;
  • Related