El problema: un solo hilo#
Por defecto, casi todo tu código de juego corre en un solo hilo (el principal). Los ordenadores modernos tienen muchos núcleos, pero un for normal solo usa uno. Para trabajo pesado y paralelizable (mover 10.000 partículas, calcular rutas, procesar terreno), el C# Job System reparte el trabajo entre varios núcleos de forma segura, y Burst lo compila a código máquina ultra rápido.
El Job System es potente pero complejo y con reglas estrictas. Antes de llegar aquí, exprime lo básico: cachear, pooling y reducir basura, reducir draw calls y medir con el Profiler. Los jobs son para cuellos de botella de CPU concretos y masivos, no para todo.
NativeArray: datos que los jobs pueden tocar#
Los jobs no pueden usar objetos gestionados normales (clases, arrays de C#) por seguridad de hilos. Usan colecciones nativas como NativeArray<T>, que viven en memoria no gestionada y que debes liberar tú.
using Unity.Collections;
NativeArray<float> datos = new NativeArray<float>(1000, Allocator.TempJob);
// ...usarla en un job...
datos.Dispose(); // ¡obligatorio liberar!Las colecciones nativas no las limpia el GC: si no llamas a Dispose(), Unity te avisará de una fuga de memoria. Cada NativeArray que creas, lo liberas.
Un job sencillo#
Un job es una struct que implementa IJob (o IJobParallelFor para paralelo) con un método Execute:
using Unity.Collections;
using Unity.Jobs;
using Unity.Burst;
[BurstCompile] // acelera el job con Burst
public struct SumarJob : IJobParallelFor
{
[ReadOnly] public NativeArray<float> entrada;
public NativeArray<float> salida;
public void Execute(int i) // se ejecuta para cada índice, en paralelo
{
salida[i] = entrada[i] * 2f;
}
}Y así lo lanzas y esperas:
var job = new SumarJob { entrada = a, salida = b };
JobHandle handle = job.Schedule(a.Length, 64); // 64 = tamaño de lote
handle.Complete(); // espera a que termine
// ahora 'b' tiene los resultadosSchedule: encola el job (no bloquea).Complete: espera el resultado. Idealmente, programa el job pronto en el frame y llama aCompletemás tarde, para que trabaje mientras haces otras cosas.
Burst: el acelerador#
[BurstCompile] le dice a Unity que compile ese job con Burst, que traduce tu C# a código máquina muy optimizado (usando instrucciones SIMD). Las ganancias pueden ser de 10× o más en cálculo puro. Burst solo funciona sobre jobs y código que cumple sus reglas (nada de objetos gestionados dentro).
Las reglas (por seguridad de hilos)#
- Tocar
transform,GameObjectni casi ninguna API de Unity (solo hilo principal). - Usar clases,
List, strings: solo tipos por valor y colecciones nativas. - Marca
[ReadOnly]lo que solo lees, para que Unity permita leerlo desde varios hilos a la vez.
El sistema de seguridad de Unity te avisará si rompes una regla: no son sugerencias, son obligatorias.
Cuándo merece la pena#
- ¿Cálculo pesado, repetitivo y paralelizable (miles de elementos)? → Job System + Burst
- ¿Necesitas tocar Transforms/física de Unity? → hilo principal (o soluciones específicas como jobs de transform)
- ¿Es un cuello de botella pequeño? → primero lo básico; a menudo basta
- ¿Escala masiva (decenas de miles de entidades)? → mira DOTS/ECS, que integra jobs y Burst
Errores frecuentes#
- Olvidar
Dispose()de las colecciones nativas (fugas). - Intentar usar API de Unity dentro de un job (no compila / peta).
- Llamar a
Complete()justo después deSchedule()(pierdes el paralelismo; sepáralos). - Usar jobs para todo, cuando el problema era basura o draw calls.
- No marcar
[ReadOnly]y que Unity impida el acceso paralelo.
Ponte a prueba#
Resumen#
El C# Job System reparte trabajo pesado y paralelizable entre varios núcleos de forma segura, usando colecciones nativas (NativeArray, con Dispose() obligatorio) y jobs struct con Execute. Burst ([BurstCompile]) los compila a código máquina rapidísimo. Reglas estrictas: nada de API de Unity ni objetos gestionados dentro. Úsalo para cuellos de botella de CPU masivos, después de exprimir lo básico. A gran escala, se integra en DOTS/ECS.