I'm working on a commercial game with Unity/C# and I'm reviewing and familiarizing myself with the code. And I started wondering about this, because there are a lot of cases where you're holding a List full of things like GameObjects, Components or ScriptableObjects ... for instance you might have a field like this in an RTS game class:
protected List<Unit> allUnits = new List<Unit>();
Now, suppose an event is triggered at runtime where you need to cycle through allUnits
and operate on certain ones or select only certain ones you want or even determine the number of them that satisfy a certain condition. Bear with me on this contrived example, but the 3 standard approaches are for-loop, for-each or a Linq extension method / query statement. Let's say I want to find all units that are not critically wounded or dead and are low on ammo. I can use the following ways to deal with this:
for ( int i = 0; i < allUnits.Count; i++ ) {
var next = allUnits[i];
if ( next.IsDead ) continue;
if ( next.IsCriticallyWounded ) continue;
if ( next.AmmoCount >= Magazine.MaxCapacity * 3 ) continue;
else
unitsLowOnAmmo.Add( next );
}
You could use the same logic in a foreach( var next in allUnits )
loop, so I won't repeat the same code again. But another approach would be like this, using Linq extensions:
unitsLowOnAmmo = allUnits.Where( u =>
!u.IsDead &&
!u.IsCriticallyWounded &&
!u.AmmoCount >= Magazine.MaxCapacity * 3 ).ToList();
You could also use this syntax to find everything under AI control, for example:
var aiUnits = (from Unit u in allUnits
where u.AIControlled
select u).ToList();
In another situation, you might need to find the total number of units satisfying a set of conditions, like I want to find all of the AI-controlled units that are critically wounded so they can maybe send medics or try to bring them to safety in a field hospital. So I could do it by using an accumulator, like for ( int i = 0, count = 0; i < allUnits.Count; i++ )
and then check for the wrong conditions and continue;
the loop otherwise let it fall through to a count++;
statement if it passes the filter. Or, obviously, I could use int count = List<T>.Count( u => cond1 && cond2 );
...
Now, obviously, the Linq stuff is much cleaner and more expressive, and the code is a bit easier to read and maintain, but we've all heard the wise masters of C# say "Linq has bad performance! Never use Linq!" over the years. And I'm wondering just how true or not true that prejudice against Linq is, and how the performance of these different approaches really differ and perform "in the field". I'm working on a very large project with about 24GB of assets, code and data and it's quite complicated, and there are lots of List instances in classes storing all kinds of stuff that regularly need to be iterated through and filtered or counted. In some cases it's frame to frame, and in other cases it's after a specific amount of time or upon an event or method call. Performance is already a major concern/issue in this project, and we want the game to be able to run on most people's computers.
Can anyone shed some light on performance comparisons and what the best approach would be to cycling through collections to filter, select and count them in the most performant way? Perhaps there's even a superior approach I didn't mention here that could be far better? I've read through some articles on here (and other sites) that didn't quite answer the question in enough detail for me to feel satisfied with it. I'm also just getting caught up on all the latest stuff in C# and .NET after being away a little while, and I'm not sure if there has been any changes to the framework (or language) that may completely change the things people used to say about Linq. I've heard that Microsoft is boasting of performance improvements in a lot of areas of .NET and wonder if any of those gains pertained to this situation. In any case, I just want to figure out the ideal approach to rapidly filtering, operating on and counting my big (and small collections) as quickly and with as little memory overhead as possible.