So for a learning exercise I set out to make my own particle system in Unity. In no way is my goal to rival Shuriken, I just want to understand it better. To that end… how is Shuriken so fast 
The system I’ve got going is totally bare bones, just spawning particles and moving them based off some initial values. I’m not even adjusting the values over time (other than position of course). With similar particle count Shuriken is still much faster and I’m curious as to how they achieve this.
I’ve read that Unity is not multithreaded except for a few “under the hood” things. Would Shuriken one of those multithreaded things?
I would assume so since sometimes the profiler will spike ParticleSystem.WaitForThread.
Assuming it is multithreaded that would definitely be one thing I can look into trying. Any other suggestions?
Here is the bare bones version, I have another test version that draws planes at the particles but I stripped that part out for speed testing.
using UnityEngine;
using System.Collections;
public struct cParticle {
private Vector3 m_Pos;
public Vector3 position{
get{
if(m_Pos == null)
m_Pos = Vector3.zero;
return m_Pos;
}
set{
m_Pos = value;
}
}
private Vector3 m_Vel;
public Vector3 velocity{
get{
if(m_Vel == null)
m_Vel = Vector3.zero;
return m_Vel;
}
set{
m_Vel = value;
}
}
private float m_Timer;
float timer{
get{
if(m_Timer == null)
m_Timer = 0;
return m_Timer;
}
set{
m_Timer = value;
}
}
public void Update () {
position += velocity;
}
}
public class CustomParticleSystem : MonoBehaviour {
public int particleCount = 64;
cParticle[] myParts;
// Use this for initialization
void Start () {
myParts = new cParticle[particleCount];
for(int i=0; i<particleCount; i++){
myParts[i] = new cParticle();
myParts[i].velocity = new Vector3(Random.Range(-0.1f, 0.1f), Random.Range(-0.1f, 0.1f), Random.Range(-0.1f, 0.1f));
}
}
// Update is called once per frame
void Update () {
for(int i=0; i<particleCount; i++){
myParts[i].Update();
}
}
}
1 Like
Why are you using a struct and not a class? Struct’s are slower.
http://www.dotnetperls.com/struct-versus-class
at 4000 particles it doesn’t seem to make much difference, in fact on average it’s even a little faster… barely.
Probably for the same reason that the Particle struct in Unity is a struct. Structs are not necessarily slower; I used naked_chicken’s code and updating 1,000,000 particles in a loop took .022 seconds on my computer if cParticle is a struct, vs. .024 seconds if it’s a class. Also, structs get instantiated on the stack, not the heap, which can be beneficial. Furthermore, structs use less memory, which is good when you have lots of them in an array. It’s better to understand the differences between structs and classes and when to use one or the other; don’t rely on a random “classes are faster” quote from somewhere, since 1) that’s not true as a blanket statement; it depends on usage, and 2) it’s especially not true since Unity uses Mono, not .NET, and doesn’t necessarily have the same performance characteristics.
@naked_chicken: how are you determining that Shuriken is “much faster”? As mentioned above, updating a million of your particle structs takes about .022 seconds, which isn’t bad considering you probably won’t have that many.
(Although your Update function doesn’t take Time.deltaTime into account.) One thing, structs and primitive types can’t be null, so all of your “== null” comparisons should be removed; Vector3s and floats will never be null. One optimization that you can do is add the x, y, and z elements separately, which requires you to use the private variables (that’s not an issue from inside the struct):
public void Update () {
m_Pos.x += m_Vel.x;
m_Pos.y += m_Vel.y;
m_Pos.z += m_Vel.z;
}
That brings the total down from .022 seconds to .0067 seconds.
–Eric
1 Like
Wow Eric, that’s great. Thanks. As you said, adding the x,y, and z helped a lot but it is still a bit slower than the Shuriken particle system. But I suppose I’m saying that with limited/flawed logic. I’ve got a Shuriken system in the same scene with the same number of particles (4000 each) and my system runs at 0.04. The Shuriken systems ParticleSystem.Update is running at 0.02 but there is also the ParticleSystem.WaitForThread which runs at 0.16. As I said before, I’m pretty naive as to how the Shuriken system actually works (part of why I’m doing this little exercise).
Thanks again for the help, learning this stuff is pretty fun but kinda tough with how much random info is scattered across the web 
On a side note if you wouldn’t mind, why did adding the xyz separately help? I’ve only been scripting for about a year now and I’ve honestly never come across that little bit of knowledge. Suppose I should add a few math lessons to my schedule
Kahn Academy here i come.
1 Like
If you add two Vector3s then it has to call a function to add the x/y/z elements and return the result, and function calls add overhead. If you add the x/y/z elements yourself then there isn’t any overhead.
–Eric
ah, that makes sense. Thanks.
That article seems to be slightly retarded… it basically benchmarks a very specific use-case and completely ignores the actual differences between the two types. Furthermore, what it is benchmarking isn’t what we’re testing!
For example, here’s another 2s benchmark:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication10
{
class Program
{
struct A
{
public int I;
}
class B
{
public int I;
}
const int elements = 1000000;
static void Main(string[] args)
{
while (true)
{
var sw = Stopwatch.StartNew();
var array = new A[elements];
for (int x = 0; x < 1000; x++)
for (int i = 0; i < elements; i++)
array[i].I += 1;
sw.Stop();
Console.WriteLine("Completed {0} Benchmark in {1}ms", "A", sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew();
var arrayB = new B[elements];
for (int i = 0; i < elements; i++)
arrayB[i] = new B();
for (int x = 0; x < 1000; x++)
for (int i = 0; i < elements; i++)
arrayB[i].I += 1;
sw.Stop();
Console.WriteLine("Completed {0} Benchmark in {1}ms", "B", sw.ElapsedMilliseconds);
Console.ReadLine();
}
}
}
}
Where the struct comes out faster…
1 Like