# Is there a difference?

I have been doing extra lines of code, I think, for no reason, but I am not sure

Example 1:
What I do

``````int arrayLength = terrainDimension.x * terrainDimension.z;

for(int i = 0; i < arrayLength; i++)
{
DoSomething(i);
}
``````

What i see in examples:

``````for(int i = 0; i < terrainArray.Length; i++)
{
DoSomething(i);
}
``````

I think this is from some misunderstanding I had long ago about a problem I had, where i thought if you use terrainArray.Length, the iterator had to look up the length every time, and it was much faster to cache that value before the loop.

Example2:
What I do

``````float foo;
for(int i = 0; i < terrainArrayLength; i++)
{
foo = MathStuff(i);
}
``````

What I see

``````for(int i = 0; i < terrainArrayLength; i++)
{
float foo = MathStuff(i);
}
``````

In this case, I do this if I want to reduce garbage collection, as I think a single value being overwritten requires four bytes of GC as required to four times arrayLength. I dont always do it, like during start up since I dont care about the tiny GC spike as the game loads, but during runtime, I do.

I actually dont know if I am correct, but if I am, why ever declare a variable inside a loop?

Yes, you can grab terrainArray.Length once and use it, and it might technically be faster. Not much. However, spending your time on micro-optimizations like this is wasteful. Itâ€™s like picking all the little pebbles that were stuck in the treads of your car tires before you drive to work in the morning. Sure, technically, it makes your car weigh less and is therefore more efficient, but by such a tiny amount it makes no measurable difference.

Measure the WORST PERFORMING part of your code (with the profiler) and focus ALL your attention on coming up with a better way to get that code to work faster. Always focus on the worst, until itâ€™s not the worst anymore. Then focus on the new worst. And so on. If youâ€™re not working on the worst part of your code, youâ€™re wasting your time.

Also, local variables donâ€™t create garbage memory allocations that need later garbage collections. Donâ€™t worry about those at all. When they go out of scope, theyâ€™re gone, and it does not take the CPU any extra time to dismiss them.

1 Like

I tend to write it like this:

``````for(int i = 0, iend = terrainArray.Length; i < iend; i++)
{
DoSomething(i);
}
``````
3 Likes

When i generate a map, i iterate over a million values at least twenty times, maybe 50 - 100.

During runtime, every few frames, one of the dozens of strategic AIs scan the entire million map points.

The habit of caching array length.lvalue first costs me three seconds code time, it is more id like to reduce the extra line of code if i could, which QA jesus showed how above.

Id guess the savings is somewhat relative to that minor three second extra line of code, but understand, even if it only saves 1/60th of a second each map generation, that i have to generate that map every iteration. That means every hour i save half a second, every day, that could be six to eight seconds.

Now, add up all the other, so called pointless micro optimizations, and everything counts in large amounts, and i am saving maybe10 - 20 minutes a day in iteration time.

So why not learn to always use the fastest by habit?

It sounds like your worst-performing piece of code might be the need to scan an entire million point map. Even for a strategy game, you can use different approaches to cut that kind of workload significantly.

Yes, until late stage game, where most AIs see most or all of the map(s), so for now, i keep it at worst case scenario from the beginning to understand the impacts.

Anyways,

Every time i ask an optimization question, i get a response here like yours. No matter how significant the optimization.

So again, i ask to all who constantly repeat such so-called wisdom,

Why not learn to always use the fastest by habit?

You really really REALLY donâ€™t want to work in a codebase that has had that happen to it.

Itâ€™s horrible.

Optimize to a point, then no more. Donâ€™t do stupid stuff. Donâ€™t do stuff more than you need to. But after that, stop when the code is clear and concise.

Remember: over-optimized code is always harder to work with because it either assumes more, checks less, combines and conflates more, or is otherwise unusually organized.

The CPU is not the only consumer of code. YOU need to â€śconsumeâ€ť it again every time you contemplate changes.

2 Likes

I probably just lost all that iteration optimization saved for the next year, by reading and responding to this thread.

1 Like

There ARE some small habits that will enhance performance. Avoid Find*() when you can. Avoid GetComponent*() when you can. Cache component references when you canâ€™t avoid searches.

There ARE some bigger habits that will enhance performance greatly, but add extra complexity to your code. Avoid APIs that return a whole List when all you need is to find the best T in the list. Use preallocated arrays instead of APIs that allocate new arrays on every call. Donâ€™t use SendMessage() when you can subscribe to an event.

But from your earlier question about whether a local variable like `foo` is creating a garbage collection problem (itâ€™s definitely not, in either variation of code you showed), it seems like youâ€™re not very familiar with how C# code translates into actual machine instructions. Changing algorithms will ALWAYS have a bigger impact than changing little stuff like whether you dereference .Length more often. If you have a thousand units moving on a million-space map, itâ€™s likely better to process the map area around the thousand units than it is to reprocess every space on the map.

And as Kurt pointed out, code that is overly â€ścleverâ€ť at wringing every CPU tick will be harder to maintain and modify. Your best use of your time is writing new functionality that works â€śgood enoughâ€ť and getting that out to players.

I dont do any of that already, except GetComponent calls during generation, and thatâ€™s because i find editor assignment of objects to be tedious and bug-inducing during changes.

I get the impression, most of this anti-optimization current here is from people who design games that are heavy on editor work and light on procedural generation, and light (or average) on AI complexity.

There are only a few people here who get that, and just answer the question, sometimes in precise detail.

And both your answers, fall flat in this case. There is nothing more complex about either of the four code snippets.

Thatâ€™s an assumption, as indicated by the word â€ślikelyâ€ť, and i dont have the time to explain everything happening, but it is far more than arm vs core and metal spots on the map.

Iâ€™m pretty sure the compiler will optimize this:

``````for(int i = 0; i < terrainArray.Length; i++)
{
DoSomething(i);
}
``````

To this:

``````int length = terrainArray.Length;
for(int i = 0; i < length; i++)
{
DoSomething(i);
}
``````

So itâ€™s not even micro optimization, but redundancy.

1 Like

I kind of thought it might anyway, as that number will be constant, for the life of the loop.

Itâ€™s not a huge deal either way, except everytime i do it, i think, â€śis there a difference?â€ťâ€¦ Hence the thread.

1 Like

I agree with the general idea that you shouldnâ€™t worry about micro optimization in normal scenarios over just using standard practices. However, I never practice what I preach there.

I have a BenchmarkDotNet project full of micro benchmarks testing exactly this kind of stuff. I can share some results when Iâ€™m at my computer. However, one important lesson that Iâ€™ve learned is that the results can potentially be affected by every factor of the environment. It matters if Iâ€™m targeting .Net 6 or .Net 7. It matters that Unity doesnâ€™t exactly match either of those with itâ€™s runtime. It matters whether youâ€™re compiling for Mono or IL2CPP in Unity. The list goes on and on, so itâ€™s hard to ever get a perfectly definitive answer.

That being said, I did want to share my general thoughts on what benchmarking different loop expressions has told me.

My view on the matter is that hoisting the array Length doesnâ€™t matter in a for loop. However, hoisting the Count property of a List does improve performance. You can cache the Count either outside of the loop, or within the loop declaration, it doesnâ€™t matter. However, that performance improvement is negligible in 99% of circumstances, and if it becomes relevant, it is important to assess whether that loop is definitely the best way to be handling something. (I still usually hoist the Count of a List into a local count variable in the for loop declaration just out of habit though, just because.)

Also, if youâ€™re just iterating, and not changing the contents, a foreach loop over an array has the best performance. I believe itâ€™s a compiler optimization. So, itâ€™s not really running a foreach loop the way you expect for other enumerables. But, you should never use a foreach loop over a List. Sticking to only using for loops over both will give the best overall performance if you canâ€™t remember which loop to use on which collection.

Also, you didnâ€™t ask, but there may be a teeny, tiny negligible difference in using a post-increment or pre-increment operator in a for loop. I thought a pre-increment operator might perform better because it usually does in most scenarios. Using ++i is usually faster than i++ in general code because post increment needs to make a temporary variable. (Iâ€™m no IL or compiler expert.) But, it seems like all of my tests tend to run i++ for loops like a nanosecond faster. It could be my imagination, or noise in the results, but I know Iâ€™ve seen info suggesting the compiler has special recognition of a signature like the standard for(int i = 0; i < someVar; i++). So, then itâ€™s able to provide a compiler optimization that gives the best results. Iâ€™ve seen other examples I canâ€™t quite remember off the top of my head deep in .Net code and GitHub issues where it really does matter the way you write something so the compiler can recognize the syntax and provide an optimization. Which brings up another good point. Sometimes attempting to optimize because you think it should run faster that way, can actually make it run slower.

2 Likes

Yes, the runtime matters a lot, and here with Unity, weâ€™re still stuck with Mono. To take your example for a foreach loop on a List, in .NET 8, they implemented some optimizations that made it 3x-4x times faster than .NET 7.0 (and I wonder how many x Unityâ€™s Monoâ€¦) which brings it on par with Arrays.

Hereâ€™s a good article about loops optimizations in .NET, and some of them are totally unexpected.

3 Likes

When it comes to optimizing, rather than ask on a forum whether something is beneficial you should just test to see for yourself whatâ€™s beneficial. I wouldnâ€™t even trust the guys that wrote the compiler with their advice. Compilers are complicated.

When iterating over something a billion times even the smallest micro optimization can make a big difference. So itâ€™s definitely worth testing all the different possibilities.

2 Likes

Thanks coderonnie.

Another reason i asked, every time i fear declaring variables outside the loop, or using arrayLength, could add a millionth of a second, because compilers do tricky things.

In that case, my philosophy would be, dont do it, and use array.Length, even though the effect is negligible, why even add the extra millionth of a second? Learn the fastest, and use the fastest. Thatâ€™s my philosophy. How much it saves is irrelevant to me.

But, its like hyppocratic oath, â€śfirst, do no harmâ€ť

1 Like

Well if this is how you write code, then donâ€™t ask anybody. Just write your code, as long as it works and you can understand it.

1 Like

Yes, but, I asked because setting all that up would take massive time, effort, and possibly be full of mistakes i dont know i am making while setting up the testsâ€¦ So my best option is to defer to people who have done the tests, knowing they know more than me on the subject, for sure.

Yep. The only test that really matters is profiling a Release build of your app.

Well, unfortunately youâ€™re not correct First of ALL local variables inside a method never allocate memory on the heap. They always live on the stack. Even reference type variables. Of course if you create objects which are stored in a reference type variable, that object lives on the heap and would allocate garbage, but the variable itself would not.

Now primitive types or struct types directly live in the variable itself. So declaring or assigning a value to a primitive type variable would never generate garbage.

Well, there could be many reasons. Though in most cases it would not make much of a difference. However in some cases it actually does. Funny enough the main reason why you would have to declare the variable inside the loop has to do with closures and objects that generate garbage ^^. Though a much more plausible and straight forward reason is: keep your scope clean. If you have a temporary variable that is only needed inside that loop, it should be declared inside that loop. That way the same name could be reused later in the same method. A good example is the loop variable itself. Itâ€™s actually declared inside the scope of the loop and only exists in that loop. When the loop has finished the name (and memory) would be free again to be reused.

Some more on local variables

What most people donâ€™t realise is that the memory for local variables inside a method is reserved on the stack the moment the method is called. That memory is â€śreleasedâ€ť once the method finishes. All the memory for all local variables is allocated at the method entry and that allocation is actually free since it is just a shift of the stack pointer. So the method knows how much stack memory it needs.

The variable inside the loop only needs the memory once because the same bit of memory is reused the next iteration. No allocations will actually happen. Note that the compiled code doesnâ€™t even remember the names of local variables. Local variables are just offsets / indices into the stack. The variable inside a loop always uses the same reserved memory slot.

Thereâ€™s an exception which I mentioned above and that is when you create a closure inside a loop. A closure can â€ścaptureâ€ť a variable. Technically the compiler works different in that case because instead of declaring a local variable, the compiler generates an actual closure object on the heap which will contain the variable. The point of a closure is that the variable can be used anonymously even outside the method where it was declared in. Though this is a very special case.

3 Likes