[Easy Hybrid ECS Example] 1000 Cubes but no performance benefit aginst common movement?

Hi everyone,

today I tried to rotate 1000 cubes with the hybrid ECS (ComponentSystem) vs common GameObject movement. The code was rejected from the git hub demos.

A single cube owns an empty class for referencing.

I attached the script CubeMover.cs to the parent GameObject. In the CubeMover we collect all cubes are parented by referencing to the class CubeSingle . In #if true, we rotate all the cubes by the common way and count the performance. The result is about 0.64ms

Now toggle both #if statements, so the Hybrid ECS comes into action. The result is about 0.66ms.

How to collect several Gameobject in an array and run Hybrid ECS correctly?

Thanks!

using UnityEngine;
using Unity.Entities;

public class CubeMover : MonoBehaviour {
    // Container for all single Cubes
    public CubeSingle[] cubeSingles;

    // Collect all single Cubes
    private void Awake() {
        cubeSingles = this.GetComponentsInChildren<CubeSingle>();
    }

#if true
    System.Diagnostics.Stopwatch ti = new System.Diagnostics.Stopwatch();
    private void Update() {
        ti.Reset();
        ti.Start();
        foreach (var c in cubeSingles) {
            c.transform.Rotate(0, 1, 0);
        }
        Debug.Log("common " + ti.ElapsedTicks / 10000f);
    }
#endif
}

class CubeMoverSystem : ComponentSystem {
    struct Components {
        public CubeMover cubes;
    }

    System.Diagnostics.Stopwatch ti = new System.Diagnostics.Stopwatch();

    protected override void OnUpdate() {
#if false
        ti.Reset();
        ti.Start();
   
        // Collect all single Cubes
        foreach (var e in GetEntities<CubeMoverSystem.Components>()) {
            foreach(var t in e.cubes.cubeSingles) {
                t.transform.Rotate(0, 1, 0);
            }
        }
        Debug.Log("hybrid " + ti.ElapsedTicks / 10000f);
#endif
    }
}
  1. Hybrid probabably won’t give much perf increase.
  2. Any ECS/Jobs will probably not give much perf increase in editor. Need builds.
  1. ECS is in preview state. Expect everything to get better with the release.
1 Like

The question was not to should I “sit and wait”.

Anyway, I’m not sure if this is the correct way to access the game objects transform, because the collected game objects are spread all over the place in memory. Further, the compiler produce errors while using an array in

struct Components {
public Transform[ ] ArrayOfCubeTransforms;
}

Does anyone know how to collect several game objects and use the hybrid ECS correctly?
Thank you.

The example you gave won’t be getting you any real performance increase as you’re pretty much performing the exact same operation in each case: loop over GameObjects stored in the array of your monobehavior and call Rotate on their transform component.

There might be a bit of a performance increase if you used injection, such as this (untested and off the top of my inexperienced head):

public struct Data {
     public readonly int Length;
     public ComponentData<CubeSingle> Cubes;
     public ComponentData<Transform> Transforms;
}
[Inject] private Data cubeData;

protected override void OnUpdate()
{
    for(int i = 0; i < cubeData.Length; i++)
    {
        cubeData.Transforms[i].Rotate(0, 1, 0);
    }
}

I would also say applying a constant rotation via a transform method on 1000 cubes isn’t exactly the best example to explore the benefits of ECS, especially if you’re not using Jobs. There’s not much room for improvement given how little it’s actually doing. Profiling using a stopwatch probably isn’t the best way to accurately measure it either, you should be using Unity’s Profiler tool to accurately measure the performance.

Thanks. Currently I check how to render the complete game world in pure or hybrid ECS on PC. Rendering common ~8.000-10.000 static game objects cause the cpu load to about 15ms. Based on the info that pure ECS is able to render 100.000 in about the the same time, I’m wondering why pure ECS is not used internally to render common static scene game objects.

In other words, we are able to move and render >100.000 entity objects nice and cool, but stuck at static scene rendering > 10.000 cooking the cpu.

A beside question that is nowhere answered, would it possible to prepare different meshes with different materials by ONE entity manager? So my idea is to write a script that read all scene static game objects at Start() and prepare to render it completely with the hybrid or pure ECS system. While loading an additional scene at runtime, positions and rotations can be serialized previously.

Unfortunately your code example gives me an error in that small example.(all required packages are loaded 2018.2.0f2)

using UnityEngine;
using Unity.Entities;

public class CubeMover : MonoBehaviour {
    // Container for all single Cubes
    public CubeSingle[] cubeSingles;

    // Collect all single Cubes
    private void Awake() {
        cubeSingles = this.GetComponentsInChildren<CubeSingle>();
    }
}

public class CubeMoverSystem : ComponentSystem {

    public struct Data {
        public readonly int Length;
        public ComponentData<CubeSingle> Cubes;
        public ComponentData<Transform> Transforms;
    }

    System.Diagnostics.Stopwatch ti = new System.Diagnostics.Stopwatch();

    [Inject] private Data cubeData;

#if true
    protected override void OnUpdate() {
        for (int i = 0; i < cubeData.Length; i++) {
            cubeData.Transforms[i].Rotate(0, 1, 0);
        }
    }
#endif
}

public ComponentData Cubes;
public ComponentData Transforms;

Error CS0246 The type or namespace name ‘ComponentData<>’ could not be found (are you missing a using directive or an assembly reference?)

public ComponentDataArray Cubes; doesn’t work as well because of the none null-able
Any ideas?

I think this example might help you with your implementation.
https://github.com/Unity-Technologies/EntityComponentSystemSamples/blob/master/Documentation/content/getting_started.md

Component data do not support reference types, which is why you get that error with cube singles.

1 Like

Cleared up a little and added a ‘purish’ ECS mode + burst:

No ECS (not defining UseEcs or UseBetterEcs): 0.70ms

Define UseEcs: 0.47ms

Define UseBetterEcs with burst: 0.05ms + 0.05ms for CopyTransformToGameObjectSystem
The actual rotation code takes around 0.015ms and the system overhead of 0.035ms is probably mostly gone in standalone as is CopyTransformToGameObjectSystem.

Additionally UseBetterEcs runs in a job and could even use a IJobParallelFor to use additional CPUs, which I did not do to be able to better compare the performance.

//#define UseEcs
#define UseBetterEcs

using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
using Unity.Jobs;
using Unity.Burst;

public class CubeMover : MonoBehaviour
{
    // Container for all single Cubes
    public CubeSingle[] cubeSingles;

    // Collect all single Cubes
    private void Awake()
    {
        cubeSingles = this.GetComponentsInChildren<CubeSingle>();
#if UseEcs || UseBetterEcs
        foreach (var cube in cubeSingles)
        {
            cube.gameObject.AddComponent<GameObjectEntity>();
#if UseBetterEcs
            var goe = cube.gameObject.GetComponent<GameObjectEntity>();
            goe.EntityManager.AddComponentData<CopyInitialTransformFromGameObject>(goe.Entity, default(CopyInitialTransformFromGameObject));
            goe.EntityManager.AddComponentData<CopyTransformToGameObject>(goe.Entity, default(CopyTransformToGameObject));
            goe.EntityManager.AddComponentData<Rotation>(goe.Entity, default(Rotation));
#endif
        }
#endif
    }

#if !UseEcs && !UseBetterEcs
    System.Diagnostics.Stopwatch ti = new System.Diagnostics.Stopwatch();
    private void Update()
    {
        ti.Reset();
        ti.Start();
        foreach (var c in cubeSingles)
        {
            c.transform.Rotate(0, 1, 0);
        }
        Debug.Log("common " + ti.ElapsedTicks / 10000f);
    }
#endif
}

#if UseEcs
class CubeMoverSystem : ComponentSystem
{
    struct Cubes
    {
        public readonly int Length;
        public ComponentArray<Transform> transform;
        public ComponentArray<CubeSingle> cubes;
    }
    [Inject] Cubes group;

    System.Diagnostics.Stopwatch ti = new System.Diagnostics.Stopwatch();

    protected override void OnUpdate()
    {
        ti.Reset();
        ti.Start();

        // Collect all single Cubes
        for (int i = 0; i < group.Length; i++)
        {
            group.transform[i].Rotate(0, 1, 0);
        }
        Debug.Log("hybrid " + ti.ElapsedTicks / 10000f);
    }
}
#endif

#if UseBetterEcs
class CubeMoverSystem : JobComponentSystem
{
    [BurstCompile]
    struct RotateJob : IJobProcessComponentData<Rotation>
    {
        public quaternion rotateBy;

        public void Execute(ref Rotation rotation)
        {
            rotation = new Rotation { Value = math.mul(rotation.Value, rotateBy) };
        }
    }

    protected override JobHandle OnUpdate(JobHandle dependsOn)
    {
        var job = new RotateJob
        {
            rotateBy = quaternion.eulerXYZ(0.0f, 3.14f / 180.0f, 0.0f),
        }.Schedule(this, dependsOn);
        return job;
    }
}
#endif
4 Likes

Thanks a lot! @julian-moschuering
This is an absolutely great example and uncovers so many hidden stuff!