C
C#9mo ago
dengkeli

❔ Need guidance on generics, List, IEnumerable

So I have an implemented ECS in a game I'm working on. Admittedly I'm not the most seasoned c# programmer, but I believe I have gotten it to a state where it's usable and reasonably fast to do "queries" on. My question has to do with Returning generic lists. So with the following code has really been bugging me for a while:
public List<T> GetComponents<T>() where T : ECSComponent
{
Type componentType = typeof(T);
var componentList = components[componentType];
List<T> newList = new();
foreach (ECSComponent c in componentList)
{
newList.Add((T)c);
}
return newList;
}
public List<T> GetComponents<T>() where T : ECSComponent
{
Type componentType = typeof(T);
var componentList = components[componentType];
List<T> newList = new();
foreach (ECSComponent c in componentList)
{
newList.Add((T)c);
}
return newList;
}
This loop obviously sucks, but as I understand it and for good reason a cast here would be casting the list not the items and that's not possible. Cool. How about:
public List<T> GetComponents<T>() where T : ECSComponent
{
Type componentType = typeof(T);
var componentList = components[componentType];
var newList = componentList.Cast<T>().ToList();
return newList;
}
public List<T> GetComponents<T>() where T : ECSComponent
{
Type componentType = typeof(T);
var componentList = components[componentType];
var newList = componentList.Cast<T>().ToList();
return newList;
}
This seems better at a glace, but it seemed lest performant with large sets than just a foreach loop in the profiler. So I'm thinking "I don't need any of the List functionality of List on a return of this function I just need to be able to iterate and access the data. So why not return Just the IEnumerable.
public IEnumerable<T> GetComponents<T>() where T : ECSComponent
{
Type componentType = typeof(T);
CheckAddComponentList(componentType);
var componentList = components[componentType];
return componentList.Cast<T>();
}
public IEnumerable<T> GetComponents<T>() where T : ECSComponent
{
Type componentType = typeof(T);
CheckAddComponentList(componentType);
var componentList = components[componentType];
return componentList.Cast<T>();
}
Seems even better right?
11 Replies
dengkeli
dengkeli9mo ago
My question is: Which is more performant? I'm not competent enough to understand everything happening here, but I do know adding the constraint and doing an explicit cast (T) in the first example seemed orders of magnitude faster. I'm just not fully aware of the consequences of Cast<T> and ToList, and I don't really have a goof enough understanding to profile them myself. Thanks much.
Angius
Angius9mo ago
$benchmark
MODiX
MODiX9mo ago
Try BenchmarkDotNet if you want a sophisticated library for benchmarking https://github.com/dotnet/BenchmarkDotNet
Angius
Angius9mo ago
Benchmarking is really the only real answer here
dengkeli
dengkeli9mo ago
I kinda figured it would be a common problem with List<T> and someone might have the answer in their pocket honestly I was unable to find anything satisfying with google-fu. I guess I need to learn how to instrument it myself and figure it out
MODiX
MODiX9mo ago
Zeth
REPL Result: Success
class ECSComponent {}
class AComponent : ECSComponent {}

List<AComponent> components;
IEnumerable<ECSComponent> l = components;
class ECSComponent {}
class AComponent : ECSComponent {}

List<AComponent> components;
IEnumerable<ECSComponent> l = components;
Compile: 445.611ms | Execution: 32.178ms | React with ❌ to remove this embed.
dengkeli
dengkeli9mo ago
I'm going the other direction though? List<Position> positions = GetComponent<Position>() I don't need the random access really, but you said .Cast<T> is just doing the loop..... I might need to dig into other code. I know there are a couple of popular ECSs for c# so there might be something Well, the way its implemented, I really don't see how it could ever get that assumption wrong they are type checked at runtime and put into a Dict<Type,List<ECSComponent>> Here is the thing. It's not slow It's reasonable for what I need I guess. I am just really curious how to solve the problem mainly. c# is very interesting Well honestly the normal design for perf favors the storage by like type. Honestly I just like the pattern, not so much about perf. But it's kinda limited to about 5 k items before it cant be used in frame time As in under 60fps, at least with what I'm current doing in my update I'll take a look at the above. Honestly how it's stored is pretty flexible as long as the interface remains I don't know if I'm making sense I'm pretty much a newb The stackoverflow answers are interesting. I think ill noodle around with that a bit
Accord
Accord9mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
dengkeli
dengkeli9mo ago
public List<T> GetComponents<T>() where T : ECSComponent
{
Type componentType = typeof(T);
var componentList = components[componentType];
List<T> newList = new();
foreach (ECSComponent c in componentList)
{
newList.Add((T)c);
}
return newList;
}

public T[] GetComponentsArray<T>() where T : ECSComponent
{
Type componentType = typeof(T);
var componentList = components[componentType];
T[] newList = new T[componentList.Count];
for (int i=0; i<componentList.Count; i++)
{
newList[i] = (T)componentList[i];
}
return newList;
}
public List<T> GetComponents<T>() where T : ECSComponent
{
Type componentType = typeof(T);
var componentList = components[componentType];
List<T> newList = new();
foreach (ECSComponent c in componentList)
{
newList.Add((T)c);
}
return newList;
}

public T[] GetComponentsArray<T>() where T : ECSComponent
{
Type componentType = typeof(T);
var componentList = components[componentType];
T[] newList = new T[componentList.Count];
for (int i=0; i<componentList.Count; i++)
{
newList[i] = (T)componentList[i];
}
return newList;
}
Hate to necro this but I did figure out this much:
| Method | Mean | Error | StdDev |
|------------------- |----------:|---------:|---------:|
| ComponentsList | 165.50 us | 3.177 us | 2.972 us |
| GetComponentsArray | 45.25 us | 0.311 us | 0.291 us |
| Method | Mean | Error | StdDev |
|------------------- |----------:|---------:|---------:|
| ComponentsList | 165.50 us | 3.177 us | 2.972 us |
| GetComponentsArray | 45.25 us | 0.311 us | 0.291 us |
the second is significantly faster. and has almost the same functionality (for my purposes)
Accord
Accord9mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.