Reference Type Variable Recycling - Is a New Reference Variable Created Every Loop in a Loop If Declared Therein

Reference type variable recycling - is a new reference variable created every loop in a loop if declared therein?

No, "variables" exist almost entirely for the sake of the programmer. You're not creating any additional work at run-time by declaring the variable inside the method.

In theory, the compiler will set aside space on the stack when a method is called for each variable declared in that method. So the presence of that variable in the method would be more important than its scope. No space is allocated on the heap unless the new keyword is used.

In practice, the compiler can identify variables that have such a short scope that they can be stored in a register on the CPU instead of needing space on the stack. For example:

var a = b[c];
a.ToString();
// never access "a" again.

... would be the same as:

b[c].ToString();

... because the compiler recognizes that it only needs to store the result of b[c] long enough to call a method on it, so it can just use a CPU register instead of using memory.

For this reason, declaring your variable inside the loop could actually cause the method to allocate less stack space for the variable, depending on the possible logic flow afterward. However, that gets into huge micro-optimization that doesn't make any sense for most people to care about.

Update

Since some people still seem to think that declaring a variable in a loop has some effect, I guess I need to provide proof. Type the following programs into LINQPad.

int j;
for(int i = 0; i < 5; i++)
{
j = i;
}

... and...

for(int i = 0; i < 5; i++)
{
int j = i;
}

Execute the code, then go to the IL tab to see the generated IL code. It's the same for both of these programs:

IL_0000:  ldc.i4.0    
IL_0001: stloc.0
IL_0002: br.s IL_0008
IL_0004: ldloc.0
IL_0005: ldc.i4.1
IL_0006: add
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: ldc.i4.5
IL_000A: blt.s IL_0004

So there's incontrovertible proof that this will make no difference at compile time. You'll get exactly the same compiled IL from both programs.

C# Strange behavior of CustomAttribute

This behavior has nothing to do with attributes. It's a classic "captured variable" problem. You're declaring the attr variable outside of your foreach loop, and then referring to it inside of a delegate, so every function in the dictionary will end up referring to the last value that attr ends up with after running through the foreach.

A simpler reproduction looks like this:

int x;
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
x = i;
actions.Add(() => Console.WriteLine(x));
}
foreach (var action in actions)
{
action();
}

Output:

2
2
2

If you move your declaration into the loop, you'll get the behavior you're looking for.

static void func()
{
foreach (var type in typeof(Program).Assembly.GetTypes())
{
var attrs = type.GetCustomAttributes(typeof(A)).ToList();
if(attrs.Any())
{
A attr = attrs.First() as A;
dic.Add(type, () => attr.SayHi());
}
}
}

C#, Declaring a variable inside for..loop, will it decrease performance?

No, variables are purely for the programmer's convenience. It doesn't matter where you declare them. See my answer to this duplicate question for more details.

How does this seemingly unconnected block (after an if statement) work?

The code you've posted would be better written as:

   if (a)
{
foo();
}
if (b)
{
boo();
}
moo();

Braces in C# have two purposes:

  1. They create a scope for variable declarations.
  2. They group statements together so that conditionals and loops and such can apply to several statements at a time.

Whoever wrote the code you've posted chose not to use them for the second purpose. if statements can be totally legitimate without using any braces, but they'll only apply to the statement that immediately follows them (like the call to foo() after the first if).

Because there is a legitimate use case for braces that has nothing to do with control flow, however, it is perfectly acceptable for someone to put braces in random places that have nothing to do with the if statements.

This code:

foo();
{
var a = boo();
}
{
var a = moo();
}

... is equivalent to this code:

foo();
var a = boo();
var b = moo();

... but you'll notice that I couldn't name the second variable a because it's no longer separated from the first variable by scoping braces.

Alternatively, could someone explain why this notation could be useful?

There are three possibilities I can think of:

  1. They wanted to reuse variable names in different parts of the method, so they created a new variable scope with the braces.
  2. They thought braces might be a nice visual aid to encapsulate all the logic that's found inside them.
  3. They were actually confused, and the program doesn't work the way they think it does.

The first two possibilities assume they're actually doing more than calling foo() and moo() in the real production code.

In any case, I don't think any of these possibilities are good reasons to write code like this, and you are totally within your rights to want to rewrite the code in a way that's easier to understand.

Reuse of for loop iteration variable

To answer your question, you've actually declared one local variable in the first case, and two in the second. The C# compiler apparently does not reuse the local variables even though I think it would be permitted to do so. My guess is that this is just not a performance gain that is worth writing a complex analysis to handle and might not even be useful if the JIT is smart enough to handle it anyway. However, the optimization you are expecting to see is done, just not at the IL level. It is done by the JIT compiler in the emitted machine code.

This is a simple enough case where inspecting the emitted machine code is actually informative. The summary is that these two methods will JIT compile to the same machine code (x86 shown below, but x64 machine code is the same as well) and thus there is no performance gain from using fewer local variables.

A quick note on conditions, I took both of these fragments and put them into different methods. Then I looked at the disassembly in Visual Studio 2015, with a .NET 4.6.1 runtime, x86 Release build (i.e. optimizations on) and attaching the debugger after the JIT has compiled the methods (at least on invocation without the debugger attached). I disabled method inlining to keep things consistent between both methods. To view the disassembly, place a break point in the desired method, attach, go to Debug > Windows > Disassembly. Hit F5 to run to the break point.

Without further ado, the first method disassembles to

            for (i = 0; i < 10; i++)
010204A2 in al,dx
010204A3 push esi
010204A4 xor esi,esi
{
Console.WriteLine(i);
010204A6 mov ecx,esi
010204A8 call 71686C0C
for (i = 0; i < 10; i++)
010204AD inc esi
010204AE cmp esi,0Ah
010204B1 jl 010204A6
}

for (i = 0; i < 10; i++)
010204B3 xor esi,esi
{
Console.WriteLine(i);
010204B5 mov ecx,esi
010204B7 call 71686C0C
for (i = 0; i < 10; i++)
010204BC inc esi
010204BD cmp esi,0Ah
010204C0 jl 010204B5
010204C2 pop esi
010204C3 pop ebp
010204C4 ret

The second method disassembles to

            for (int i = 0; i < 10; i++)
010204DA in al,dx
010204DB push esi
010204DC xor esi,esi
{
Console.WriteLine(i);
010204DE mov ecx,esi
010204E0 call 71686C0C
for (int i = 0; i < 10; i++)
010204E5 inc esi
010204E6 cmp esi,0Ah
010204E9 jl 010204DE
}

for (int i = 0; i < 10; i++)
010204EB xor esi,esi
{
Console.WriteLine(i);
010204ED mov ecx,esi
010204EF call 71686C0C
for (int i = 0; i < 10; i++)
010204F4 inc esi
010204F5 cmp esi,0Ah
010204F8 jl 010204ED
010204FA pop esi
010204FB pop ebp
010204FC ret

As you can see, aside from different offsets for the appropriate jumps, the code is identical.

These methods are quite simple so the work of keeping track of the loop counter is done with the esi register.

It is left as an exercise for the reader to verify in x64.

Declaring a variable inside or outside an foreach loop: which is faster/better?

Performance-wise both examples are compiled to the same IL, so there's no difference.

The second is better, because it more clearly expresses your intent if u is only used inside the loop.

Destroy old instance of object and create new one in a loop

You do not need to set ob and ob2 to null.

In your code above after each iteration of your for-loop you can no longer access the ob and ob2 objects from the previous iteration. This means that they will be garbage collected eventually.

However, if they implement IDisposable you should dispose them before the end of each iteration.

Is there any benefit to using static temporary variables in C# (Unity)?

I tend to agree with Marc Gravell on the main concept, but I see his answer more C# related than Unity related.

Yes. I agree that c#-wise mixing static values with thread could create issues...

BUT in your specific case OnTriggerEnter can run just on the main thread (such as many other unity related methods), so you have no such risk (unless you start using those fields outside of OnTriggerEnter method).

Anyway this is the situation:

SPEED PERFORMANCE

REFERENCE TYPES

  • Reference values would live in the heap anyway. Speed wise it's better to keep them as static, otherwise they would keep allocating/deallocating from the heap.

VALUE TYPES

  • For value type it's another story. If they are static you would store them permanently (in almost all situations) on the heap, while local value types would live (a rather short life...) in the stack
  • If you have them as static on the heap I expect, in general, that you would need a bit more time to access them
  • If you allocate them in the stack (as local values) it will be much quicker

MEMORY PERFORMANCE

  • For value types it is surely better to allocate them in the stack (so as local values).
  • Memory wise also reference types should be better as local values. You would keep them in memory just as much as needed. As Mark explained, with static fields, you would keep them in memory indefinitely.

Value types are surely better as local values. Reference type are more performant -speed wise- as static, but more performant -memory wise- as local.

In general, in your specific case I do not see any big change, even for large amount of calls.

You still could try benchmarking it with unity benchmark tool (since BenchmarkDotNet doesn't work for unity out of the box).

On my end...

I would use local values, much more readable.

Is setting by reference local variable equivalent to helper function I have created

The answer, as @Jeroen Mostert mentioned in the comments, is to extract the code into a separate method, so it is not part of the for loop:

private void setVertices(TMP_TextInfo textInfo, float boundsMinX, float boundsMaxX)
{
int vertexIndex = textInfo.characterInfo[i].vertexIndex;
int materialReferenceIndex = textInfo.characterInfo[i].materialReferenceIndex;
Vector3[] vertices = textInfo.meshInfo[materialReferenceIndex].vertices;
Vector3 vector = new Vector2((vertices[vertexIndex].x + vertices[vertexIndex + 2].x) / 2f, textInfo.characterInfo[i].baseLine);
vertices[vertexIndex] += -vector;
vertices[vertexIndex + 1] += -vector;
vertices[vertexIndex + 2] += -vector;
vertices[vertexIndex + 3] += -vector;
float num = (vector.x - boundsMinX) / (boundsMaxX - boundsMinX);
float num2 = num + 0.0001f;
float y = VertexCurve.Evaluate(num) * CurveScale;
float y2 = VertexCurve.Evaluate(num2) * CurveScale;
Vector3 lhs = new Vector3(1f, 0f, 0f);
Vector3 rhs = new Vector3(num2 * (boundsMaxX - boundsMinX) + boundsMinX, y2) - new Vector3(vector.x, y);
float num3 = Mathf.Acos(Vector3.Dot(lhs, rhs.normalized)) * 57.29578f;
float z = ((!(Vector3.Cross(lhs, rhs).z > 0f)) ? (360f - num3) : num3);
Matrix4x4 matrix = Matrix4x4.TRS(new Vector3(0f, y, 0f), Quaternion.Euler(0f, 0f, z), Vector3.one);
ref Vector3 reference = ref vertices[vertexIndex];
reference = matrix.MultiplyPoint3x4(vertices[vertexIndex]);
ref Vector3 reference2 = ref vertices[vertexIndex + 1];
reference2 = matrix.MultiplyPoint3x4(vertices[vertexIndex + 1]);
ref Vector3 reference3 = ref vertices[vertexIndex + 2];
reference3 = matrix.MultiplyPoint3x4(vertices[vertexIndex + 2]);
ref Vector3 reference4 = ref vertices[vertexIndex + 3];
reference4 = matrix.MultiplyPoint3x4(vertices[vertexIndex + 3]);
vertices[vertexIndex] += vector;
vertices[vertexIndex + 1] += vector;
vertices[vertexIndex + 2] += vector;
vertices[vertexIndex + 3] += vector;
}

TMP_TextInfo textInfo = m_TextComponent.textInfo;
int characterCount = textInfo.characterCount;
if (characterCount == 0)
{
continue;
}
float boundsMinX = m_TextComponent.bounds.min.x;
float boundsMaxX = m_TextComponent.bounds.max.x;
for (int i = 0; i < characterCount; i++)
{
if (textInfo.characterInfo[i].isVisible)
{
setVertices(textInfo, boundsMinX, boundsMaxX);
}
}


Related Topics



Leave a reply



Submit