Question

I'm making a simple particle system in C#/XNA, and since there would potentially be a high volume of method calls every second, I wanted to make sure I understood exactly how everything works.

I have a Particle and Emitter class such that:

public sealed class Emitter
{
    private struct Particle
    {
        public Vector2 Position;
        public Vector2 Velocity;
        public ushort Life;
        public bool Alive { get { return (Life > 0); } }
    }

    private readonly Particle[] _particles;

    public Emitter(ushort maxParticles)
    {
        _particles = new Particle[maxParticles];
    }
}

The Emitter has other logic that would create, update, and render the particles, but it is not relevant. However, my understanding is that if I were to invoke a method, it would copy the value on each call:

public static void UpdateParticle(Particle p)
{
    p.Position += p.Velocity;
}

If I made an Emitter with 100,000 particles (as unlikely as that would be), copying the particle just to update it seems like it would be doing a lot of unnecessary work. If the method used ref instead UpdateParticle(ref Particle p) { ... }, then it would just access the data directly and update it there, right?

However, with regards to Jon Skeet's answer for this question, he writes:

You almost never need to use ref/out. It's basically a way of getting another return value, and should usually be avoided precisely because it means the method's probably trying to do too much. That's not always the case (TryParse etc. are the canonical examples of reasonable use of out) but using ref/out should be a relative rarity.

Is this one of those "relatively rare" cases where it is the correct choice to use it?

Regarding my design choices for this class:

  • I did some research into a particle system a while ago. I can't remember the exact reasoning for making Particle a struct — something about accessing a contiguous chunk of memory being faster, or not having as many object references — but benchmarking proves that it does perform better than a class.

  • Particle is a private type, because the Emitter class is the only thing that will ever care about it. Ever. The client code should never care about individual particles, only that the Emitter is making pretty sparkles and rendering them.

  • _particles is a fixed size, acting like an object pool, because recycling the allocated memory should be more performance-conscious than invoking new().

Was it helpful?

Solution

Your Particle type will have a memory cost of at least 20 bytes.

If you don't use ref, your method will look like:

public static Particle UpdateParticle(Particle p)
{
    p.Position += p.Velocity;
    return p;
}

If you do this, you will add a Particle copy to pass the Particle to the UpdateParticle() method, and a Particle copy to return the copy. Because copy represents read & write a particle copy requires 40 bytes memory access (and 2 copies represents 80 bytes accessed).

Updating 100 000 particle without using ref require to copy near 8 000 000 bytes copied.

With common Core i5/i7 processors, memory bandwidth is between 15 and 40 GB/s. The memory access overhead of not using ref can be estimated to 8/20 000 = 0,4 ms. With my Core 2 with 6 Gb/s memory bandwidth : 8 / 6 000 = 1,3 ms)

To measure the delta you have to deal with at least 10 000 000 Particles (100 times more = 130 ms on my computer).

On my computer measures indicate near 600 ms for the copy version, and near 200 ms for the ref version.

I also added an Update() member method to the Particle struct and updating 10 000 000 particles with this method also takes near 200 ms.

Finally I tried to do the add operation directly in loop (without method calls). It took only near 150 ms (a 50 ms gain)

A total delta of 400 ms (a little less than a second half) is near 3 times more than the single memory bandwith overhead.

This additional overhead is due the JIT compiler.

Finally if the usage of ref is discouraged in .NET Framework, it is very interesting when you use relatively large structs.

In C++ you can obtain by far better performances. We can expect in the future that intelligent C# compiler will provide better performances (especially with automatic method inlining that can remove the memory copy and method call overheads).

In C# the strict isolation of reference types (classes) and value types (structs) is problematic when you deal with a huge number of instances: 1) using classes you face the long time of unitary heap memory allocations. 2) using struct you face the long time of automatic copies.

To design your program you have to make a balance between a good program design (implying many method calls and object copies) and the performances expected. To be able to optimize and change the design of your particles model, it could be a good idea to create a Particles class managing the particles collections in your system and providing optimized large set of particles operations.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top