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

为Rhino动画实现重复并行循环,规避Parallel.For提升性能

Rhino光线追踪动画多核优化方案

核心循环的线程复用实现

方案1:使用Parallel.For(推荐,低开销且高效)

你担心Parallel.For会频繁创建新线程是误解——它默认复用.NET线程池中的线程,不会每次启动新线程,25000+次迭代的调度开销几乎可以忽略。直接替换核心循环即可:

// 替换原单线程for循环
Parallel.For(0, sphereNum, new ParallelOptions 
{ 
    MaxDegreeOfParallelism = Environment.ProcessorCount // 用满CPU核心,也可根据需求调整
}, i =>
{
    if (lengthtraveled > spheres[i].pathlength)
    {
        do
        {
            double interimlength = lengthtraveled - spheres[i].pathlength;
            var direction = spheres[i].nextdirection;
            var start = spheres[i].nextstart;
            var hits = Embree.Intersect(start, direction);
            
            spheres[i].pathlength += hits.Ray.tfar;
            spheres[i].step = direction.ToVector3d();
            spheres[i].step.Unitize();
            points[i].Location = spheres[i].nextstart.ToPoint3d() + (spheres[i].step * interimlength);
            spheres[i].nextstart = start + direction * hits.Ray.tfar;
            
            hits.Hit.Normalize();
            spheres[i].nextdirection = Vector3.Reflect(direction, hits.Hit.normal);
        }
        while (lengthtraveled > spheres[i].pathlength);

        spheres[i].step *= spherestep;
    }
    else
    {
        points[i].Location += spheres[i].step;
    }
});

关键注意点:必须确认Embree.Intersect方法是线程安全的。如果Embree实例不支持多线程调用,需要为每个线程创建独立的EMBContainer实例,避免竞态问题。

方案2:自定义固定线程池(完全控制线程复用)

如果需要严格控制线程数量(比如适配低性能多核设备),可以创建固定数量的后台线程,用同步信号量协调每一帧的计算:

1. 初始化线程池(在Animate函数开头添加)

int threadCount = Math.Max(2, Environment.ProcessorCount - 1); // 留一个核心给UI
var frameStartSignal = new ManualResetEventSlim(false);
var frameCompleteCountdown = new CountdownEvent(threadCount);
var threads = new Thread[threadCount];
int chunkSize = sphereNum / threadCount;

for (int t = 0; t < threadCount; t++)
{
    int startIdx = t * chunkSize;
    // 最后一个线程处理剩余所有索引
    int endIdx = t == threadCount - 1 ? sphereNum : (t + 1) * chunkSize;
    
    threads[t] = new Thread(() =>
    {
        while (!cts.IsCancellationRequested)
        {
            // 等待主线程的计算启动信号
            if (!frameStartSignal.Wait(cts.Token)) break;
            
            // 处理分配的索引范围
            for (int i = startIdx; i < endIdx; i++)
            {
                // 原循环逻辑,和方案1一致
                if (lengthtraveled > spheres[i].pathlength)
                {
                    do
                    {
                        double interimlength = lengthtraveled - spheres[i].pathlength;
                        var direction = spheres[i].nextdirection;
                        var start = spheres[i].nextstart;
                        var hits = Embree.Intersect(start, direction);
                        
                        spheres[i].pathlength += hits.Ray.tfar;
                        spheres[i].step = direction.ToVector3d();
                        spheres[i].step.Unitize();
                        points[i].Location = spheres[i].nextstart.ToPoint3d() + (spheres[i].step * interimlength);
                        spheres[i].nextstart = start + direction * hits.Ray.tfar;
                        
                        hits.Hit.Normalize();
                        spheres[i].nextdirection = Vector3.Reflect(direction, hits.Hit.normal);
                    }
                    while (lengthtraveled > spheres[i].pathlength);

                    spheres[i].step *= spherestep;
                }
                else
                {
                    points[i].Location += spheres[i].step;
                }
            }
            
            // 标记当前线程完成计算
            frameCompleteCountdown.Signal();
            // 重置信号等待下一帧
            frameStartSignal.Reset();
        }
    });
    threads[t].IsBackground = true;
    threads[t].Start();
}

2. 触发帧计算(替换原单线程for循环)

// 启动所有线程计算当前帧
frameStartSignal.Set();
// 等待所有线程完成计算
frameCompleteCountdown.Wait(cts.Token);
// 重置计数器准备下一帧
frameCompleteCountdown.Reset();

3. 资源清理(动画结束时添加)

// 在return前添加
frameStartSignal.Dispose();
frameCompleteCountdown.Dispose();
foreach (var thread in threads)
{
    thread.Join(100);
}

刷新率适配优化建议

  • 动态调整步长:当前每100帧调整一次spherestep,可以改为每20-30帧调整一次,让帧率变化更平滑。
  • 减少重绘开销:将doc.Views.Redraw()改为只重绘当前活跃视图:
    doc.Views.ActiveView?.Redraw();
    
  • 预计算方向向量:StartingVectors()生成的向量可以提前归一化,避免后续重复调用Unitize()。
  • Embree性能优化:如果Embree支持,启用硬件加速(比如SIMD),或者减少每次Intersect调用的计算量(比如限制射线长度)。
  • 避免不必要的计算:对于已经完成路径的点(spheres[i].done),可以跳过后续循环中的处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 11:50:13