Para los desarrolladores, los subprocesos son un tema importante que afecta el rendimiento del juego. Así es como funciona la programación de tareas en Silicio de manzana juegos.
Las demandas de GPU y CPU son algunas de las cargas de trabajo con mayor uso intensivo de computación en las computadoras modernas. En cada fotograma se deben procesar cientos o miles de trabajos de GPU.
Para que tu juego se ejecute en Apple Silicon de la manera más eficiente posible, necesitarás optimizar tu código. La máxima eficiencia es el nombre del juego aquí.
Apple Silicon presentó nuevas GPU y RAM integradas para un acceso y rendimiento rápidos. Apple Fabric es un aspecto de la arquitectura M1-M3 que permite el acceso a CPU, GPU y memoria unificada, todo sin tener que copiar memoria a otras tiendas, lo que mejora el rendimiento.
Núcleos
Cada CPU Apple Silicon incluye núcleos de eficiencia y núcleos de rendimiento. Los núcleos de eficiencia están diseñados para funcionar en un modo de consumo de energía extremadamente bajo, mientras que los núcleos de rendimiento están diseñados para ejecutar código lo más rápido posible.
Los subprocesos, es decir, las rutas de ejecución del código, se ejecutan automáticamente en ambos tipos de núcleos.
En tiempo de ejecución, varias capas de software interactúan con varios núcleos de CPU para organizar la ejecución del programa.
- El kernel y el programador de XNU
- El núcleo del micronúcleo Mach
- El programador de ejecución
- La capa del sistema operativo UNIX portátil POSIX
- Grand Central Dispatch, o GCD (tecnología de subprocesos específica de Apple basada en bloques)
- NSObjetos
- La capa de aplicación
Los NSObjects son objetos de código central definidos por el sistema operativo NeXTStep que Apple adquirió cuando compró Steve Jobs segunda empresa NeXT en 1997.
Los bloques GCD funcionan ejecutando una sección de código, que al finalizar utiliza devoluciones de llamada o cierres para terminar su trabajo y proporcionar algún resultado.
POSIX incluye hilos que son caminos independientes para la ejecución del código. El objeto NSThread de Apple es una clase de subprocesos múltiples que incluye pthreads junto con otra información de programación. Puede utilizar NSThreads y su clase prima NSTask para programar tareas que se ejecutarán en los núcleos de la CPU.
Todas estas capas funcionan en conjunto para proporcionar ejecución de software para el sistema operativo y las aplicaciones.
Pautas
Al desarrollar tu juego, hay varias cosas que deberás tener en cuenta para lograr el máximo rendimiento.
Primero, su objetivo general de diseño debe ser aligerar la carga de trabajo colocada en los núcleos de la CPU y las GPU. El código que se ejecuta más rápido es el código que nunca tiene que ejecutarse.
Reducir el código y maximizar la programación de ejecución es de suma importancia para que tu juego funcione sin problemas.
Apple tiene varias recomendaciones que puedes seguir para obtener la máxima eficiencia de la CPU. Estas pautas también se aplican a las Mac basadas en Intel.
Tiempo muerto y programación
En primer lugar, cuando no se utiliza un núcleo de GPU específico, queda inactivo. Cuando se activa para su uso, hay un pequeño tiempo de activación, lo cual supone un pequeño coste. Apple lo muestra así:
A continuación, existe un segundo tipo de costo, que es la programación. Cuando un núcleo se activa, el programador del sistema operativo tarda un poco de tiempo en decidir en qué núcleo ejecutar una tarea, luego tiene que programar la ejecución del código en el núcleo y comenzar la ejecución.
Semáforos o la señalización de subprocesos también debe configurarse y sincronizarse, lo que lleva una pequeña cantidad de tiempo.
En tercer lugar, existe cierta latencia de sincronización a medida que el programador determina qué núcleos ya están ejecutando tareas y cuáles están disponibles para nuevas tareas.
Todos estos costos de configuración afectan el rendimiento de su juego. A lo largo de millones de iteraciones durante la ejecución, estos pequeños costos pueden acumularse y afectar el rendimiento general.
Puede utilizar la aplicación Apple Instruments para descubrir y realizar un seguimiento de cómo estos costos afectan el rendimiento del tiempo de ejecución. Apple muestra un ejemplo de un juego en ejecución en Instrumentos como este:
En este ejemplo, surge un patrón de subproceso de inicio/espera en el mismo núcleo de CPU. Estas tareas podrían haberse ejecutado en paralelo en varios núcleos para obtener un mejor rendimiento.
Esta pérdida de paralelismo se debe a tiempos de ejecución de código extremadamente cortos que, en algunos casos, son casi tan cortos como el tiempo de activación de una CPU de un solo núcleo. Si la ejecución de ese código corto pudiera retrasarse un poco más, podría haberse ejecutado en otro núcleo, lo que habría provocado que la ejecución se ejecutara más rápido.
Para resolver este problema, Apple recomienda utilizar la granularidad de programación de trabajos correcta. Es decir, agrupar trabajos extremadamente pequeños en otros más grandes para que el tiempo de ejecución colectiva no se acerque o supere los tiempos de activación del núcleo y de programación.
Hay siempre un pequeño costo de programación de subprocesos cada vez que se ejecuta un subproceso. Ejecutar varias tareas pequeñas a la vez en un subproceso puede eliminar parte de la sobrecarga del programador asociada con la programación de subprocesos porque puede reducir el recuento general de programación de subprocesos.
A continuación, prepare la mayoría de los trabajos para que se ejecuten de inmediato antes de programarlos para su ejecución. Cada vez que se inicia la programación de subprocesos, normalmente algunos de ellos se ejecutarán, pero otros pueden terminar siendo movidos fuera del núcleo si tienen que esperar a que se programen para su ejecución.
Cuando los hilos se mueven fuera del núcleo, se crea un hilo. bloqueando. La señalización y la espera en subprocesos en general pueden provocar una reducción del rendimiento.
Activar y pausar subprocesos repetidamente puede ser un problema de rendimiento.
Paralelizar bucles for anidados
Durante la ejecución del bucle de código anidado for-next, programar los bucles externos con una granularidad más gruesa (es decir, ejecutarlos con menos frecuencia) deja las partes internas de los bucles sin interrupciones. Esto puede mejorar el rendimiento general.
Esto también reduce la latencia de la caché de la CPU y reduce los puntos de sincronización de subprocesos.
Grupos de trabajos y el núcleo
Apple también recomienda usar grupos de trabajo para aprovechar los subprocesos de trabajo para un mejor rendimiento. Un subproceso de trabajo es un subproceso que se está ejecutando o que está programado activamente para ejecutarse próximamente y que realiza algún trabajo durante la ejecución del marco.
En los grupos de trabajos, los subprocesos de los trabajadores roban la programación de trabajos de otros subprocesos. Dado que existe un costo de programación de subprocesos para todos los subprocesos, el robo de trabajos hace que sea mucho más barato iniciar un trabajo en el espacio del usuario que en el espacio del kernel del sistema operativo donde se ejecuta el programador.
Esto elimina la sobrecarga de programación en el kernel.
El sistema operativo núcleo es el núcleo del sistema operativo donde se lleva a cabo la mayor parte del trabajo en segundo plano y de bajo nivel. El espacio de usuario es donde realmente se ejecuta la mayor parte de la ejecución del código de aplicaciones o juegos, incluidos los subprocesos de trabajo.
El uso del robo de trabajos en el espacio del usuario evita la sobrecarga de programación del kernel, lo que mejora el rendimiento. Recuerde: el fragmento de código más rápido posible es el fragmento de código que nunca tiene que ejecutarse.
Evite señalar y esperar
Cuando reutiliza trabajos existentes en lugar de crear otros nuevos, al reutilizar un subproceso o un puntero de tarea, está utilizando un subproceso ya activo en un núcleo activo. Esto también reduce los gastos generales de programación del trabajo.
Además, asegúrese de activar los subprocesos de trabajo solo cuando sea necesario. Asegúrese de que haya suficiente trabajo listo para justificar la activación de un subproceso para ejecutarlo.
ciclos de CPU
A continuación, querrás optimizar los ciclos de la CPU para que no se desperdicie ninguno en tiempo de ejecución.
Para hacer esto, primero evite promover subprocesos de un E-core a un P-core. Los E-cores funcionan más lento para ahorrar energía y duración de la batería.
Puede hacerlo evitando los ciclos de espera ocupados que monopolizan un núcleo de CPU. Si el programador tiene que esperar demasiado en un núcleo ocupado, puede cambiar la tarea a otro núcleo: un E-core si es el único disponible.
El yield
y setpri()
La programación de llamadas determina con qué prioridad se ejecutan los subprocesos y cuándo ceder el paso a otras tareas.
Usando yield
en las plataformas Apple le dice efectivamente a un núcleo que ceda el paso a cualquier otro subproceso que se ejecute en el sistema. Este comportamiento vagamente definido puede crear cuellos de botella en el rendimiento que son difíciles de detectar en tiempo de ejecución en Instruments.
yield
El rendimiento varía según las plataformas y los sistemas operativos y puede provocar largos retrasos en la ejecución, hasta 10 ms. Evitar el uso de yield
o setpri()
siempre que sea posible, ya que hacerlo puede enviar temporalmente la ejecución de un núcleo de CPU determinado a cero por un momento.
Además, evite el uso sleep(0)
– ya que en las plataformas Apple, no tiene significado y no es una operación.
Escalar el número de hilos
En general, desea utilizar la cantidad correcta de subprocesos para la cantidad de núcleos de CPU. Ejecutar demasiados subprocesos en dispositivos con un número bajo de núcleos puede ralentizar el rendimiento.
Demasiados subprocesos crean cambios de contexto centrales que son costosos.
Muy pocos subprocesos causan el problema inverso: muy pocas oportunidades para paralelizar subprocesos para la programación en múltiples núcleos.
Consulta siempre el diseño de la CPU en el momento del lanzamiento del juego para ver en qué tipo de entorno de CPU estás ejecutando y cuántos núcleos hay disponibles.
Su grupo de subprocesos siempre debe escalarse según el número de núcleos de CPU, no según el número total de subprocesos de las tareas.
Incluso si el diseño de su juego requiere una gran cantidad de subprocesos de trabajo para una tarea determinada, nunca se ejecutará de manera eficiente si hay demasiados subprocesos y muy pocos núcleos para ejecutarlos simultáneamente.
Puedes consultar un iOS o Mac OS dispositivo que utiliza UNIX sysctlbyname
función. El hw.nperflevels
sysctlbyname
El parámetro devuelve información sobre la cantidad de núcleos de CPU generales que tiene un dispositivo.
Utilizar instrumentos
En la aplicación Instrumentos de Apple, hay una Rendimiento del juego Plantilla que puedes usar para ver y medir el rendimiento del juego en tiempo de ejecución.
También hay una Seguimiento del estado del hilo Característica en Instrumentos que se puede utilizar para rastrear la ejecución de subprocesos y los estados de espera. Puede utilizar TST para rastrear qué subprocesos permanecen inactivos y durante cuánto tiempo.
Resumen
La optimización de juegos es un tema muy complejo y apenas hemos mencionado algunas técnicas que puedes utilizar para maximizar el rendimiento de la aplicación. Hay mucho más que aprender; prepárese para pasar varios días dominando el tema.
En muchos casos, aprenderá mejor mediante prueba y error utilizando Instrumentos para rastrear cómo se comporta su código y modificarlo cuando aparezcan cuellos de botella en el rendimiento.
En general, los puntos clave a tener en cuenta al programar trabajos de juegos en sistemas Apple multinúcleo son:
- Mantenga las tareas lo más pequeñas posible
- Agrupe tantas tareas pequeñas como sea posible en subprocesos únicos
- Reduzca la sobrecarga, la programación y la sincronización de subprocesos tanto como sea posible
- Evite los ciclos de inactividad/activación del núcleo
- Evite cambios de contexto de subprocesos
- Utilice la agrupación de trabajos
- Activar hilos solo cuando sea necesario
- Evite usar sleep(0) y ceda el paso cuando sea posible
- Utilice semáforos para la señalización de subprocesos.
- Escalar el número de subprocesos al número de núcleos de CPU
- Utilizar instrumentos
Apple también tiene un WWDC video con derecho Ajuste la programación de trabajos de CPU para juegos Apple Silicon que analiza la mayoría de los temas anteriores y mucho más.
Al prestar atención a los detalles de programación del código de su juego, puede obtener el mayor rendimiento posible de sus juegos de Apple Silicon.