Unidad ECS

Autor: Pang Wei Wei
enlace: https: //www.zhihu.com/question/286963885/answer/452979420
Fuente: saber casi con
derechos de autor del autor. Para reimpresiones comerciales, comuníquese con el autor para obtener autorización. Para reimpresiones no comerciales, indique la fuente.
 

ECS resuelve 2 problemas:

1) Rendimiento;

2) Reducir el uso de memoria innecesario;

Ponga una imagen, escribí una prueba de demostración antes, para el uso de ecs, no use ecs, la diferencia de rendimiento en el caso de la optimización de instancias 3.

Se puede ver que si el objeto que renderiza es menor a 500, el rendimiento de ecs no mejora significativamente. Cuando excede 1000, el rendimiento de ecs tiene una ventaja significativa. Por debajo de 10000obj, la brecha de rendimiento es casi 100.

Entonces, para juegos con obj dentro de 200, hay poca diferencia entre usar ecs.

Además, ecs es un plan sistemático y estándar propuesto por unity. También podemos utilizar métodos tradicionales para lograr resultados similares más o menos, y ecs no es necesario.

 

La demostración es la siguiente figura (Instancia). De acuerdo con la demostración de rotación incorporada, se completan las versiones correspondientes de instanciación y método tradicional. Esta demostración es de 1000 cubos, con una esfera girando. Después de golpear el cubo, el cubo girará por un período de tiempo y se detiene gradualmente, por lo que necesita 1001 objetos para seguir actualizándose:

 

========= Complemente el artículo completo compartido dentro de la empresa:

 

Ya no necesita MonoBehaviour, Component y GameObject

En el pasado, MonoBehaviour tenía dos funciones: lógica y datos del juego. Creamos GameObject, agregamos MB (MonoBehaviour, lo mismo a continuación) y luego actualizamos la lógica del juego a través de Update. A menudo actualizamos algunos datos en Update y la implementación de MB es muy complicado. Muchas funciones innecesarias se heredan en un solo cerebro, lo que conduce a una tarea muy simple en la que es posible que necesitemos desperdiciar mucha memoria para lidiar con esas funciones innecesarias. Si usa MB de manera incontrolable, es básicamente una pesadilla de eficiencia del juego.

El componente anterior se hereda de MB. La mayoría de las veces, la función del componente es proporcionar datos, pero la matriz organizada por componente no es compatible con la memoria caché de la CPU, porque no organiza los datos que deben recalcularse y actualizarse. varias veces, por lo que la CPU puede tener una falta de caché al hacer cálculos.Si la lógica del juego requiere una gran cantidad de objetos para actualizar los datos, esta parte del consumo puede ser muy grande.

Al mismo tiempo, MB no puede resolver el problema del orden de ejecución. Para GameObjects creados dinámicamente, el orden de actualización de MB es incierto. A menudo, instalamos algunos MB Update y Destroy después de algunos MB para asegurar la dependencia mutua, pero el diseño de MB de Unity no tiene mejor Solución a este problema Ajustar el orden de ejecución del script es problemático e inútil (la prueba anterior no tiene ningún efecto en el MB creado dinámicamente y solo se puede utilizar para el MB estático en la escena).

Además, si hay una gran cantidad de GameObjects en el juego con una gran cantidad de Componente enlazado a él, entonces lleva mucho tiempo ejecutar una gran cantidad de Actualizaciones, y estas Actualizaciones solo pueden ejecutarse en el hilo principal y no pueden ser paralelo.

Con este fin, Unity 2018.2 introdujo un nuevo sistema ECS para resolver los problemas mencionados anteriormente.

Totalmente impulsado por datos

El objetivo central del diseño de ECS es deshacerse de la estructura tradicional de objetos MB, GameObject y Component, y cambiar a una organización de código totalmente basada en datos. Mediante estos cambios, se pueden resolver dos problemas:

1) Organice los datos de forma más eficaz y mejore la utilización de la memoria caché de la CPU;

2) Paralelización.

Echemos un vistazo breve a un ejemplo. Este es el ejemplo que viene con la muestra de ECS:

 

En este ejemplo, puede ver que aunque hay más de 1000 objetos en la pantalla, no hay 1000 GameObjects correspondientes. Cuando la esfera golpea un bloque, tendrá una atenuación posterior a la rotación y puede mantener una velocidad de fotogramas de 300-600 fps. En el pasado, necesitábamos crear 1000 GameObjects para lograr un efecto similar, y luego 1000 MB eran responsables de actualizar la información de transformación del GameObject. Implementé este método de acuerdo con este método, luego esta demostración es solo aproximadamente 1/3 de los fps.

 

Tenga en cuenta que la imagen de arriba creará más GameObjects, con fps entre 100-200 fps. Por supuesto, esta implementación no es óptima. También podemos usar Instancing para optimizar drawcall. Para comparar la versión de Instancing, implementé la siguiente comparación:

 

 

El fps es de aproximadamente 150-300 fps, puede ver que la creación de instancias es aproximadamente 1 veces el fps. Bajo la prueba de 1000 objs, la diferencia entre los diferentes métodos de implementación es aproximadamente 1-2 veces. Parece que la diferencia no es muy grande, por lo que Probé un obj más alto El número de fps, bajo la prueba de objs más alto, se obtiene la siguiente tabla:

 

Se puede ver que con un mayor número de obj, se reflejan las ventajas del método ECS. Por debajo de 10000 obj, ECS aún puede alcanzar un alto fps de 350, e incluso si Instance está optimizado, solo quedan 4 fps, que es casi 100. Veces.

Ahora volvemos al principio, ECS ha resuelto los siguientes dos problemas:

1) Organice los datos de forma más eficaz y mejore la utilización de la memoria caché de la CPU;

2) Paralelización.

¿Pero cómo solucionarlo?

Organice los datos de forma más eficaz y mejore la utilización de la memoria caché de la CPU

El método tradicional es poner los datos del objeto del juego en Componentes, por ejemplo, puede ser así (referencia 1):

using Unity.Mathematics;
class PlayerData// 48 bytes
{
 public string public int[]
 public quaternion
 public float3
 string name;       // 8 bytes  
 someValues; // 8 bytes
}
PlayerData players[];

Su diseño de memoria es así:

Y este diseño es extremadamente hostil para la caché de la cpu. Cuando intenta actualizar los datos de float3 en lotes, las predicciones de la caché de la cpu siempre fallan porque están asignadas a áreas discontinuas en la memoria y la cpu necesita varios saltos de direccionamiento. datos correspondientes y operar, si podemos almacenar continuamente los datos que necesitan actualizarse en lotes, esto aumentará en gran medida la tasa de aciertos de la caché de la CPU y aumentará la velocidad de cálculo.

De acuerdo con la especificación de diseño de ECS, el lote de datos actualizados se extrae y organiza en la memoria continuamente, de modo que los datos subsiguientes se puedan leer de una vez durante la lectura previa de la caché, lo que mejora la eficiencia de la operación de la CPU, como se muestra en la figura ( referencia 1):

 

En el cálculo real, el índice indirecto se utiliza para actualizar los distintos tipos de datos de todas las entidades, en lugar de actualizar todos los datos de cada entidad, como se muestra en la figura:

Como puede ver aquí, los cambios más importantes son:

En el cálculo de actualización de un sistema (equivalente al componente anterior), las posiciones de todas las entidades se juntan y actualizan en lotes, porque los datos float3 de estas posiciones son continuos en su interior y la operación de la cpu es la más rápida.

Paralelización

Después de extraer los datos por separado, la siguiente tarea es paralelizar estos cálculos de datos y hacer un uso completo de múltiples núcleos. En el pasado, casi el código lógico se ejecutaba en el hilo principal. Por supuesto, algunos equipos de proyecto se dieron cuenta de este problema y pusieron algo de lógica que no tiene nada que ver con la visualización en varios hilos en paralelo, pero no abstrajeron completamente los datos para Forman un conjunto completo.Marco de desarrollo y ECS resuelve este problema.

ECS abre el sistema de trabajo en la parte inferior y proporciona el sistema de trabajo c # en la parte superior, que es un conjunto de sistema de programación de múltiples subprocesos. Si diferentes datos no dependen unos de otros, solo necesita colocar estos datos en varios subprocesos a través del sistema de trabajo c # para la computación en paralelo, como:

public class RotationSpeedSystem : JobComponentSystem
    {
        struct RotationSpeedRotation : IJobProcessComponentData<Rotation, RotationSpeed>
        {
            public float dt;

            public void Execute(ref Rotation rotation, [ReadOnly]ref RotationSpeed speed)
            {
                rotation.Value = math.mul(math.normalize(rotation.Value), quaternion.axisAngle(math.up(), speed.Value * dt));
            }
        }

        protected override JobHandle OnUpdate(JobHandle inputDeps)
        {
            var job = new RotationSpeedRotation() { dt = Time.deltaTime };
            return job.Schedule(this, 64, inputDeps);
        } 
    }

Si diferentes datos tienen dependencias y es necesario calcular otros datos para completar el cálculo, puede establecer dependencias de tareas, y el sistema de trabajos de c # completará automáticamente la llamada de dichas tareas y la clasificación de dependencias.

Modo mezclado

Ya hay una gran cantidad de códigos desarrollados de manera tradicional. Si desea disfrutar de la eficiencia que brinda ECS, es necesario transformar significativamente los códigos existentes. ¿Existe una manera relativamente simple de mantener los códigos existentes sin cambios y mejorar la eficiencia? , la respuesta es que ECS proporciona un modo híbrido compatible.

Por ejemplo, el siguiente código (referencia 2):

using Unity.Entities;using UnityEngine;
class Rotator : MonoBehaviour{
 // The data - editable in the inspector public float Speed;
}
class RotatorSystem : ComponentSystem{
 struct Group
    {
 // Define what components are required for this  // ComponentSystem to handle them. public Transform Transform;
 public Rotator   Rotator;
    }

 override protected void OnUpdate()
 {
 float deltaTime = Time.deltaTime;

 // ComponentSystem.GetEntities<Group>  // lets us efficiently iterate over all GameObjects // that have both a Transform & Rotator component  // (as defined above in Group struct). foreach (var e in GetEntities<Group>())
        {
            e.Transform.rotation *= Quaternion.AngleAxis(e.Rotator.Speed * deltaTime, Vector3.up);
        }
    }
}

La modificación principal es mover la función Actualizar de MB a la función OnUpdate de ComponentSystem, y agregar una estructura de grupo para intercambiar datos entre MB y ComponentSystem, y agregar un componente GameObjectEntity al GameObject original. El propósito de este componente es extraer todos los demás componentes del GameObject y crear una entidad, de modo que pueda atravesar el GameObject correspondiente en el ComponentSystem a través de la función GetEntities.

Unity creará un ComponentSystem correspondiente para todos los GameObjects con los componentes GameObjectEntity montados cuando se inicie, por lo que aún puede usar el método GameObject.Instantiate anterior para crear GameObjects. Es solo que la función Actualizar del MB original se reemplaza con la función OnUpdate de ComponentSystem.

Con esta modificación, puede combinar algunas de las capacidades de ECS mientras mantiene los resultados del código original. En resumen:

El modo mixto puede separar los datos y el comportamiento, puede actualizar los datos del objeto en lotes, evitar la llamada al método virtual de cada objeto (abstraído a una función) y aún puede continuar usando el inspector para observar las propiedades y los datos del GameObject.

Sin embargo, esta modificación no mejoró nada por completo, el tiempo de creación y carga no mejoró, el acceso a los datos aún no era amigable con la caché, y los datos no se organizaron continuamente en la memoria, ni se paralelizaron.

Solo se puede decir que esta modificación es para acercarse a la refactorización de código por fases realizada por ECS puro.Al final, se debe utilizar ECS puro para lograr la mayor mejora de rendimiento.

Referencias relacionadas

1) Unity comparte oficialmente ppt, ECS y Job System.

2)https://github.com/Unity-Technologies/EntityComponentSystemSamples/

Supongo que te gusta

Origin blog.csdn.net/Momo_Da/article/details/112906063
Recomendado
Clasificación