You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

游戏开发中无堆内存分配的Parallel.ForEach替代方案求荐

无堆分配并行区块生成解决方案

针对你遇到的Parallel.ForEach堆分配导致GC卡顿问题,结合递归架构、长耗时任务的限制,以下是C#原生优化方案和适配的第三方工具:

C#原生无分配优化

1. 手动实现线程池+值类型任务

Parallel.ForEach的主要分配来自任务对象、闭包捕获和分区器开销。手动维护线程池,用值类型任务结构体传递参数,完全避免堆分配:

// 定义值类型任务,无堆分配
public struct ChunkGenTask
{
    public int X;
    public int Z;
    public NativeArray<int> BlockData; // 用Unity.Collections.NativeArray存储输出(无堆分配)

    public void Run()
    {
        // 区块生成逻辑:所有临时数据用栈分配或复用池资源
        for (int i = 0; i < BlockData.Length; i++)
        {
            BlockData[i] = GenerateBlock(X, Z, i); // 无分配逻辑
        }
    }
}

// 自定义线程池维护
public static class ChunkWorkerPool
{
    private static readonly Queue<ChunkGenTask> _taskQueue = new();
    private static readonly ManualResetEvent _taskSignal = new(false);
    private static readonly List<Thread> _workers = new();

    static ChunkWorkerPool()
    {
        int workerCount = Environment.ProcessorCount - 1; // 留一个线程给主线程
        for (int i = 0; i < workerCount; i++)
        {
            var thread = new Thread(WorkerLoop);
            thread.IsBackground = true;
            thread.Start();
            _workers.Add(thread);
        }
    }

    private static void WorkerLoop()
    {
        while (true)
        {
            _taskSignal.WaitOne();
            ChunkGenTask task;
            lock (_taskQueue)
            {
                if (_taskQueue.Count == 0)
                {
                    _taskSignal.Reset();
                    continue;
                }
                task = _taskQueue.Dequeue();
            }
            task.Run();
            // 回收NativeArray到资源池(如果需要复用)
        }
    }

    public static void Enqueue(ChunkGenTask task)
    {
        lock (_taskQueue)
        {
            _taskQueue.Enqueue(task);
            _taskSignal.Set();
        }
    }
}

2. 复用内存与对象池

  • 用ArrayPool<T>/MemoryPool<T>复用临时数组,替代每次创建新数组:
// 租赁复用数组
var tempHeightMap = ArrayPool<float>.Shared.Rent(ChunkWidth * ChunkDepth);
try
{
    // 生成高度逻辑,使用tempHeightMap
}
finally
{
    // 归还数组到池
    ArrayPool<float>.Shared.Return(tempHeightMap);
}
  • 递归逻辑中的临时对象(如区块上下文)用自定义值类型池存储,避免装箱和堆分配。

3. 避免闭包与委托分配

Parallel.ForEach的闭包会生成匿名类实例,改用静态方法传递所有参数:

// 静态无分配逻辑方法
private static void GenerateChunkStatic(int x, int z, NativeArray<int> output)
{
    // 生成逻辑
}

// 调用时直接传递参数,避免闭包
foreach (var chunkPos in chunkPositions)
{
    ChunkWorkerPool.Enqueue(new ChunkGenTask { X = chunkPos.x, Z = chunkPos.z, BlockData = outputData });
}

适配的第三方工具

Unity.Collections + 手动调度

虽然不能用Unity Job System,但可以用NativeArray/NativeList等无分配容器存储数据,配合自定义线程池处理,完全规避堆分配。注意手动管理Native资源的生命周期,避免内存泄漏。

UniTask优化

UniTask本身可以减少Task的分配开销,结合UniTask.Run的ForceAsync选项,配合对象池复用UniTask的上下文:

// 复用UniTask的状态对象,避免每次创建
private static readonly ObjectPool<UniTaskCompletionSource> _utPool = new(() => new UniTaskCompletionSource());

public UniTask GenerateChunkAsync(int x, int z)
{
    var tcs = _utPool.Get();
    ChunkWorkerPool.Enqueue(new ChunkGenTask 
    { 
        X = x, 
        Z = z, 
        OnComplete = () => 
        {
            tcs.TrySetResult();
            _utPool.Return(tcs);
        }
    });
    return tcs.Task;
}

关键注意事项

  • 递归调用时,所有传递的上下文必须是值类型或复用的引用类型,禁止在递归栈中创建新的堆对象。
  • 用Span<T>/ReadOnlySpan<T>处理临时数据,完全在栈上操作,避免堆分配。
  • 通过Unity Profiler的Memory面板跟踪分配,重点排查递归逻辑中的临时对象和委托创建。

内容的提问来源于stack exchange,提问作者runevision

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 23:04:51