C# Is either method faster? (Less expensive) Variables

Hi I was just wondering while working on a C# script if one of two methods for variable retrieval is faster than the other. When trying to get the average color from a texture I have two slightly different ways of going about it, mainly just either storing a variable or retrieving it.

This is what I am doing. Example 1:

 Color[] Colors = texture.GetPixels();
 int pixelCount = Colors.Length;
 int r =0; int g = 0; int b = 0; int a = 0;
 for (int i = 0; i < pixelCount; i++)
 {
      Color c = Colors*;*

r += c.r;
g += c.g;
b += c.b;
if (includeAlpha)
a += c.a;
}
if (a != 0)
a /= pixelCount;
return new Color (r/pixelCount, g/pixelCount, b/pixelCount, a);
Example 2:
Color[] Colors = texture.GetPixels();
int pixelCount = Colors.Length;
int r =0; int g = 0; int b = 0; int a = 0;
for (int i = 0; i < pixelCount; i++)
{
r += Colors*.r;*
g += Colors*.g;*
b += Colors*.b;*
if (includeAlpha)
a += Colors*.a;*
}
if (a != 0)
a /= pixelCount;
return new Color (r/pixelCount, g/pixelCount, b/pixelCount, a);
So there you can see the only change is in the looping ‘for’ statement. By storing the color in a variable or accessing it for every integer value, is one a better option than the other? If the reasoning could also be explained I would appreciate it.

I’m almost certain that after compiler optimization you end up with the same code. If the compiler is stupid, use the first version. But if you’re concerned about performance, you definitely need two different loops, one with and one without alpha. Conditional branches are performance killers and that “if (includeAlpha)” for each pixel iteration makes my toenails curl.

This is probably a micro optimization and most likely won’t effect your games performance, however, the only way to find out is by profiling!
You can imagine example 1 looking like this, where there is only one allocation for the color variable:

Color[] Colors = texture.GetPixels();
int pixelCount = Colors.Length;
int r =0, g = 0, b = 0, a = 0;
Color c = new Color();                   // now initialized outside the loop
for (int i = 0; i < pixelCount; i++)
{
     c = Colors*;*

r += c.r;
g += c.g;
b += c.b;
if (includeAlpha)
a += c.a;
}
if (a != 0)
a /= pixelCount;
return new Color (r/pixelCount, g/pixelCount, b/pixelCount, a);
Edit: Note that it doesn’t make much of a difference whether you declare a variable inside or outside a for loop (see [c# - Reference type variable recycling - is a new reference variable created every loop in a loop if declared therein? - Stack Overflow][1])
So in practice it shouldn’t matter, but if you’re not sure which to go with, I personally prefer creating a variable for any array element that will be used multiple times (example 1).
Really this shouldn’t concern you until the profiler tells you that this particular piece of code needs to be optimized (if there is even any difference between the two examples).
[1]: c# - Reference type variable recycling - is a new reference variable created every loop in a loop if declared therein? - Stack Overflow

Here is what i’d do :

float pixelCount = (float)(texture.height*texture.width); //for byte2float in the return statement
int r = 0, g = 0 ,b = 0, a = 0;

if(includeAlpha)    //Instead of comparing every frame compare just once
{
    foreach(Color32 c in texture.GetPixels32()) //To prevent doing pointer arithmetic for every []
    {
        r += c.r;
        g += c.g;
        b += c.b;
        a += c.a;
    }
}
else
{
    foreach(Color32 c in texture.GetPixels32()) //To prevent doing pointer arithmetic for every []
    {
        r += c.r;
        g += c.g;
        b += c.b;
    }
}

return new Color(r/pixelCount, g/pixelCount, b/pixelcount, includeAlpha ? a/pixelCount : 0);

Plus like this you don’t store a copy of the Array, freeing some memory usage.

Also be carefull because Color uses float values from 0 to 1 whereas Color32 uses byte values from 0 to 255. And considering you used integers to store the sums you wouldn’t have an accurate result.

Disclaimer : I’m more of a C++ Dev so c# compiler behaviour are a bit foreign to me, but like doublemax said, you’d most likely end up with the same code doing all sorts of optimizations.

hold the phone.

it turns out to be pretty important to consider whether you’re using GetPixels() or GetPixels32().
The former returns Color structs, which have each component as a float.
The latter returns Color32 structs, where each component is a 8-bit byte.

In my tests, the act of calling GetPixels/32() itself is non-negligible.
For a texture with 11561940 texels (3645 x 3172):

  • GetPixels() (floats)
    between 0.1 and 0.17 seconds.
  • GetPixels32()
    between 0.04 and 0.05 seconds.

so, GetPixels32() significantly faster than GetPixels().

then for doing the arithmetic,
i tried three methods:

a. the inner loop used Color’s += operator. This was the slowest.

b. the inner loop adds floating-point components to floating-point accumulators. Second fastest.

c. the inner loop adds 8-bit byte components to 32-bit int accumulators. Fastest.

a. 0.038 micro-seconds per pixel for the accumulation portion.

b. 0.016 micro-seconds per pixel for the accumulation portion.

c. 0.013 micro-seconds per pixel for the accumulation portion.

i also looked a bit into using Mono.simd to do the adds in parallel, but got tired. That’s probably the best approach tho. The challenge will be casting the array of Color structs into an array of Vector4f structs.

so, here’s the winning code from my tests.
i’ve left in the timing diagnostics.

  void doItAsBytes() {
    string s = "as bytes   : ";

    Texture2D t = theImage.sprite.texture;

    float t0 = Time.realtimeSinceStartup;
    Color32[] cs = t.GetPixels32();

    int r = 0;
    int g = 0;
    int b = 0;
    int a = 0;

    float t1 = Time.realtimeSinceStartup;

    for (int n = cs.Length - 1; n >= 0; --n) {
      Color32 c = cs[n];
      r += c.r;
      g += c.g;
      b += c.b;
      a += c.a;
    }

    float count = cs.Length;
    Color32 avg = new Color32((byte)(r / count), (byte)(g / count), (byte)(b / count), (byte)(a / count));

    float t2 = Time.realtimeSinceStartup;

    float dtFetch = t1 - t0;
    float dtSum = t2 - t1;
    float microSPP = dtSum * oneM / count;

    theButton.targetGraphic.color = avg;

    Debug.Log(s + "fetched " + cs.Length + " texels in " + dtFetch + " seconds. ");
    Debug.Log(s + "averaged " + cs.Length + " texels in " + dtSum + " seconds. " + microSPP.ToString("0.000") + "µS per pixel");
    Debug.Log(s + "total µS per pixel " + ((dtFetch + dtSum) / count * oneM).ToString("0.000"));
    if (verbose) {
      Debug.Log(s + "avg = " + avg.ToString());
    }
  }