-
|
|
Why use a pool for structs?
|
With a pool, what I do is have an array T[] pool. I initialize the array. I keep a count of how many items i've taken.
For T Get()
I return pool[count]. Then count++
For Return(int index)
I just write over the value.
pool[index] = pool[count];
count--;
I COULD swap them, but this is where I get skeptic.
Why swap them, when they aren't the same reference.
I don't think I'm making sense here, but wouldn't it be just as efficient to have something like this:
struct Particle
{
Particle Default;
etc...
}
and whenever I need to spawn a particle I just get the Default, and whenever the particle expires, just leave it.
I'm assuming that pools are used to overcome constructor overhead, and that just forgetting about a particle won't leave it for the garbage collector...
What I need, is a pool, that allows for multiple 'partions'. I want each emitter to be able to deal with its own particles, or is that asking for too much?
Maybe I've been planning this particle system wrong, though.
Now I've just been rambling... Sorry :(
This is the spot where I'm supposed to identify myself as a unique individual, right?
|
|
-
-
- (8275)
-
premium membership
MVP
-
Posts
6,127
|
Re: Why use a pool for structs?
|
Structs are always copied in C#, because they are value types (just like, say, "int"). A pool of structs pretty much never makes sense. However, if the struct, in turn, has references to value types (arrays, class instances, etc) that you want to re-use, re-using them "all at once" with a pool might make sense.
The way you describe it, though, doesn't sound useful. The "return" will just end up copying the data in memory anyway, so you could just as well do "foo = new FooStruct()" and it would be a lot more efficient.
Note that "new" in C# does *not* automatically mean memory allocation. "new" on a value type will just call the constructor of that value type on whatever memory the value type should live in (stack, field, etc). A value type does not turn into a reference type until it gets cast to (object), at which point it's put on the heap in a process called "boxing." However, once boxed, the value type is immutable; you can't access the boxed value without casting back to value type, and the act of casting to value type copies it back to the point where you cast it. The boxed value is still intact af that point.
Jon Watte, Direct3D MVP Tweets, occasionallykW X-port 3ds Max .X exporter kW Animation source code
|
|
-
-
- (66)
-
premium membership
-
Posts
19
|
Re: Why use a pool for structs?
|
I think a pool could be useful for cases when you have tons of shortlived things.
If those "things" are structs, then no -- there is no case where a pool will help for a value type. That's the whole thing about value types. If you don't yet understand the difference between value types and reference types, then please read up on that first (or just read everything I wrote in the previous message).
|
|
-
-
- (12845)
-
Team XNA
-
Posts
8,526
|
Re: Why use a pool for structs?
|
Pete Hufnagel:I would go with structs for this so you get one big allocation instead of a bunch of small ones (easier on the garbage collector on the xbox).
Structs are value types, so they never require a heap allocation and thus never stress the garbage collector.
Like Jon says, pooling structs is not generally a useful thing to do. If you want a temporary struct, just allocate it on the stack in the usual way.
If your structs are so big that copying them around becomes a significant overhead, that's a sign you should probably be using classes instead of structs (at which point object pooling does become a useful technique). Rule of thumb: when choosing between class and struct, 99.999% of the time you will be better off with a class.
XNA Framework Developer -
blog - homepage
|
|
-
-
- (66)
-
premium membership
-
Posts
19
|
Re: Why use a pool for structs?
|
Shawn Hargreaves:Structs are value types, so they never require a heap allocation and thus never stress the garbage collector.
The way I understood it, arrays are on the heap, so an array of value types would be one big allocation on the heap. An array of reference types would be an array of pointers on the heap, pointing to other things on the heap, so there would be many more things on the heap for the GC to worry about (one for the array, and one for every non-null entry).
So it seems like if you want the fewest number of heap allocations (which is a good goal on the XBox from what I understand), and don't mind taking more memory than you might need (the array of reference types only holds a null for empty slots), then the array of value types (structs) would be best. Does that make sense? Is there something I'm missing?
|
|
-
-
- (12845)
-
Team XNA
-
Posts
8,526
|
Re: Why use a pool for structs?
|
That's exactly right. But you don't need (or want) an object pool when you are using arrays of value types! The point of an object pool is to hold on to any object instances you aren't currently using, so you can reuse them at some later time without bothering to allocate new objects. But it makes no sense to do that with value types. When an object is copied by value, you don't gain anything by copying its bits from one array to another, then copying those bits back at some later point. That's just moving data around for no purpose.
XNA Framework Developer -
blog - homepage
|
|
-
|
|
Re: Why use a pool for structs?
|
Thank you! Now, what can i do to reduce the memory cost of each struct and increase the efficiency of my particle engine?
For example:
| [StructLayout(LayoutKind.Sequential, Pack=1)] |
| public struct Particle : IParticle |
| { |
| public Vector2 Position; |
| public Vector2 Velocity; |
| public Color Color; |
| public float Energy; |
| |
| public bool Alive |
| { |
| get |
| { |
| return Energy > 0; |
| } |
| } |
| |
| public Particle(Vector2 position, Vector2 velocity, Color color, float energy) |
| : this() |
| { |
| Position = position; |
| Velocity = velocity; |
| Color = color; |
| Energy = energy; |
| } |
| } |
Is this right??? Is there anything else I can do?
This is the spot where I'm supposed to identify myself as a unique individual, right?
|
|
-
|
|
Re: Why use a pool for structs?
|
Take away the inteface and make Alive a field, or get rid of it and just test Energy > 0 directly. If you need the interface because you want to manage different particles with different fields through a standard interface then there's not a lot I can see that you can change.
Just remember accessing your particle through an interface will cause the particle to be boxed - which you definately don't want - but I can't remember if this happens for both explicit and implicit interface members or just explicit.
Game hobbyist hell-bent on coding a diabolical Matrix
|
|
-
-
- (12845)
-
Team XNA
-
Posts
8,526
|
Re: Why use a pool for structs?
|
cable729:Now, what can i do to reduce the memory cost of each struct and increase the efficiency of my particle engine?
I don't see much room to reduce the amount of memory you are using there. But I'm surprised that memory for storing particles would even be an issue. How many of these things do you have? :-)
To increase efficiency, you should start by profiling your app to find where the bottlenecks are. Otherwise we're just guessing, which is pretty much useless. Once you have a capture from a CPU sampling profiler, post back here with whatever pieces of code turn out to be the bottlenecks, and I'm sure someone can suggest how to improve those pieces.
If it turns out that updating or drawing your particles is expensive, you could consider offloading that work to the GPU.
XNA Framework Developer -
blog - homepage
|
|
-
-
- (12845)
-
Team XNA
-
Posts
8,526
|
Re: Why use a pool for structs?
|
Craig Martin:Just remember accessing your particle through an interface will cause the particle to be boxed
... unless you're calling the interface method via a generic type parameter that has a constraint to this interface, which is one of the more common reasons to put interfaces on value types, and does not cause boxing.
XNA Framework Developer -
blog - homepage
|
|
-
|
|
Re: Why use a pool for structs?
|
Another question, should I add an Update method to IParticle?
Right now, I have
| |
| public override void Update(GameTime gameTime) |
| { |
| for (int i = 0; i < Particles.Length; i++) |
| { |
| Particles[i].Position += Particles[i].Velocity; |
| Particles[i].Energy -= (float)gameTime.ElapsedGameTime.TotalSeconds; |
| } |
| } |
Should I change IParticle from
| |
| public interface IParticle |
| { |
| bool Alive { get; } |
| } |
to this?
| |
| public interface IParticle |
| { |
| bool Update(); |
| } |
And update each of the particles in the loop, and if they return false, it means they're dead?
Because otherwise, classes overriding Update would have to either rewrite the previous class's code, or go through another iteration of the particles.
Or should I do something like this?
| |
| public void Update(GameTime gameTime) |
| { |
| for (int i = 0; i < Particles.Length; i++) |
| UpdateParticles(gameTime, i); |
| } |
| |
| protected virtual void UpdateParticles(GameTime gameTime, int index) |
| { |
| Particles[index].Position += Particles[index].Velocity; |
| Particles[index].Energy -= (float)gameTime.ElapsedGameTime.TotalSeconds; |
| } |
They all sacrifice efficiency (calling thousands of methods) for ease of coding.
Which is the best route?
This is the spot where I'm supposed to identify myself as a unique individual, right?
|
|
-
-
- (703)
-
premium membership
-
Posts
506
|
Re: Why use a pool for structs?
|
| |
| public void Update(GameTime gameTime) |
| { |
| for (int i = 0; i < Particles.Length; i++) |
| UpdateParticles(gameTime, i); |
| } |
| |
| protected virtual void UpdateParticles(GameTime gameTime, int index) |
| { |
| Particles[index].Position += Particles[index].Velocity; |
| Particles[index].Energy -= (float)gameTime.ElapsedGameTime.TotalSeconds; |
| } |
I don't see what that gets you. How does that help things? You just moved code from one function to another.
Is it just so subclasses of your particle system don't need to write the for loop again?
In that case, I would ask: what sorts of different things are you expecting subclassers to do? If it's just something like changing how the Energy decreases each frame, maybe that is something that could be encapsulated as a value in the particle itself.
|
|
-
|
|
Re: Why use a pool for structs?
|
I'm starting to think that using classes would be better. I almost completely give up the advantages of OOP.
Is the performance of classes vs structs neglible if I use a pool for classes?
Which do you recommend?
This is the spot where I'm supposed to identify myself as a unique individual, right?
|
|
-
-
- (12845)
-
Team XNA
-
Posts
8,526
|
Re: Why use a pool for structs?
|
I think you are over-thinking this problem. Here's what I would do:
- Implement whatever effect you want using the simplest possible code to get the desired result
- Once everything is working the way you want, observe how fast it runs
- Is this fast enough?
- If not, profile the app to find out which part is making it slow, then post back here with your profiler results and someone can surely tell you how to speed up the slow part
There is a very good chance that your first simplistic version of the code will actually turn out to run perfectly quick enough, so you may not need to do any more work at all.
And even if this first simple code does run too slow, it is very unlikely that you or anyone will be able to guess at this early point exactly why it will run too slow! Without an implementation to measure, you're liable to waste a lot of time trying to optimize based on guesses that might not have anything to do with which things actually turn out to be making the code slow!
XNA Framework Developer -
blog - homepage
|
|
-
-
- (8275)
-
premium membership
MVP
-
Posts
6,127
|
Re: Why use a pool for structs?
|
Note that OOP is *not* the law. It's not even a requirement for maintainable, well-functioning, high-performance code. In fact, sometimes it goes against the requirement for high-performance code. In the case of particles, it's almost always the case that particles will not be "smart" -- assuming you run on the CPU, you really want your data to be arranged such that it's cache friendly for the particle system to simply rip through the array of particle instances and update each of them, and then generate output geometry (vertex buffer) from there.
Because virtual dispatch, function pointers, switch() statements and even if() statements may slow things down, a particle system will generally define some number of affectors on the state of a particle, and each particle system configuration will then just change how the affectors apply. If you want, you can make affectors class instances, and put them in the particle system class (using containment), but evenso, you'd end up with a virtual dispatch per affector per particle in the standard case, with is hardly optimal. It might be a little better if you define affectors as "passes," and apply one affector, then the next, across all particles, and have the affector loop over the particles itself.
I would really try to stay away from trying to make things "as object oriented as possible" in this case, because doing so will likely fight at cross purposes to good software engineering. Keep an array of particle state structs that's sized to the max number of particles allowed in the system. Keep a separate counter for how many particles are "alive." When a particle dies, swap in the last live particle for the position of the now-dead particle, and decrement the counter. This means that you only need to visit the part of the array that's between 0 and the number of live particles, which is a good thing from a cache point of view.
Jon Watte, Direct3D MVP Tweets, occasionallykW X-port 3ds Max .X exporter kW Animation source code
|
|
-
|
|
Re: Why use a pool for structs?
|
Pools are a great way to pre-allocate contiguous memory. If you have
engine components that are guaranteed to be used in game, it is a good
idea to allocate them before resources. This can be configurable
depending on what platform you are on.
A particle system is a great sample of a type that will benefit from
pooling. An array of value types is a contiguous allocation. An
array of reference types may not be.
Pooling and contiguous allocations become more important especially as
memory limits become an issue. On lower memory systems, total available
memory may be large, but memory fragmentation may make some allocations
impossible.
There may also be a speed benefit. CPU caching favors contiguous memory
for tight loops.
For your purposes, perhaps you need a pool of pools?
Marshall
|
|
|