UnityDots学习(三)

embedded/2025/1/13 12:10:32/

此篇记录Dots使用的步骤。

Demo逻辑是在场景内创建2000个物体。1000个是搜索,1000是被搜索的目标。

每个物体都随机朝某个方向移动。

在Update里。1000个搜索者会对1000被搜索的目标进行遍历查找到哪个目标离他最近,并且画出连线。

逻辑如下:

    //搜索者    public class Seeker : MonoBehaviour{public Vector3 Direction;public void Update(){transform.localPosition += Direction * Time.deltaTime;}}//目标者public class Target : MonoBehaviour{public Vector3 Direction;public void Update(){transform.localPosition += Direction * Time.deltaTime;}}//查找方法public class FindNearest : MonoBehaviour{public void Update(){// Find nearest Target.// When comparing distances, it's cheaper to compare// the squares of the distances because doing so// avoids computing square roots.Vector3 nearestTargetPosition = default;float nearestDistSq = float.MaxValue;foreach (var targetTransform in Spawner.TargetTransforms){Vector3 offset = targetTransform.localPosition - transform.localPosition;float distSq = offset.sqrMagnitude;if (distSq < nearestDistSq){nearestDistSq = distSq;nearestTargetPosition = targetTransform.localPosition;}}Debug.DrawLine(transform.localPosition, nearestTargetPosition);}}//场景初始驱动public class Spawner : MonoBehaviour{// The set of targets is fixed, so rather than // retrieve the targets every frame, we'll cache // their transforms in this field.public static Transform[] TargetTransforms;public GameObject SeekerPrefab;public GameObject TargetPrefab;public int NumSeekers;public int NumTargets;public Vector2 Bounds;public void Start(){Random.InitState(123);for (int i = 0; i < NumSeekers; i++){GameObject go = GameObject.Instantiate(SeekerPrefab);Seeker seeker = go.GetComponent<Seeker>();Vector2 dir = Random.insideUnitCircle;seeker.Direction = new Vector3(dir.x, 0, dir.y);go.transform.localPosition = new Vector3(Random.Range(0, Bounds.x), 0, Random.Range(0, Bounds.y));}TargetTransforms = new Transform[NumTargets];for (int i = 0; i < NumTargets; i++){GameObject go = GameObject.Instantiate(TargetPrefab);Target target = go.GetComponent<Target>();Vector2 dir = Random.insideUnitCircle;target.Direction = new Vector3(dir.x, 0, dir.y);TargetTransforms[i] = go.transform;go.transform.localPosition = new Vector3(Random.Range(0, Bounds.x), 0, Random.Range(0, Bounds.y));}}}

此段代码没有介入Dots任何优化,在我的电脑里跑帧率每秒4.3帧

对上面的进行改造介入job

    //查找功能用Job来实现    
[BurstCompile]public struct FindNearestJob : IJob{// All of the data which a job will access should // be included in its fields. In this case, the job needs// three arrays of float3.// Array and collection fields that are only read in// the job should be marked with the ReadOnly attribute.// Although not strictly necessary in this case, marking data  // as ReadOnly may allow the job scheduler to safely run // more jobs concurrently with each other.// (See the "Intro to jobs" for more detail.)[ReadOnly] public NativeArray<float3> TargetPositions;[ReadOnly] public NativeArray<float3> SeekerPositions;// For SeekerPositions[i], we will assign the nearest // target position to NearestTargetPositions[i].public NativeArray<float3> NearestTargetPositions;// 'Execute' is the only method of the IJob interface.// When a worker thread executes the job, it calls this method.public void Execute(){// Compute the square distance from each seeker to every target.for (int i = 0; i < SeekerPositions.Length; i++){float3 seekerPos = SeekerPositions[i];float nearestDistSq = float.MaxValue;for (int j = 0; j < TargetPositions.Length; j++){float3 targetPos = TargetPositions[j];float distSq = math.distancesq(seekerPos, targetPos);if (distSq < nearestDistSq){nearestDistSq = distSq;NearestTargetPositions[i] = targetPos;}}}}}//数据都用NativeArray来替代。在Update里调用上面写的Jobpublic class FindNearest : MonoBehaviour{// The size of our arrays does not need to vary, so rather than create// new arrays every field, we'll create the arrays in Awake() and store them// in these fields.NativeArray<float3> TargetPositions;NativeArray<float3> SeekerPositions;NativeArray<float3> NearestTargetPositions;public void Start(){Spawner spawner = Object.FindFirstObjectByType<Spawner>();// We use the Persistent allocator because these arrays must// exist for the run of the program.TargetPositions = new NativeArray<float3>(spawner.NumTargets, Allocator.Persistent);SeekerPositions = new NativeArray<float3>(spawner.NumSeekers, Allocator.Persistent);NearestTargetPositions = new NativeArray<float3>(spawner.NumSeekers, Allocator.Persistent);}// We are responsible for disposing of our allocations// when we no longer need them.public void OnDestroy(){TargetPositions.Dispose();SeekerPositions.Dispose();NearestTargetPositions.Dispose();}public void Update(){// Copy every target transform to a NativeArray.for (int i = 0; i < TargetPositions.Length; i++){// Vector3 is implicitly converted to float3TargetPositions[i] = Spawner.TargetTransforms[i].localPosition;}// Copy every seeker transform to a NativeArray.for (int i = 0; i < SeekerPositions.Length; i++){// Vector3 is implicitly converted to float3SeekerPositions[i] = Spawner.SeekerTransforms[i].localPosition;}// To schedule a job, we first need to create an instance and populate its fields.FindNearestJob findJob = new FindNearestJob{TargetPositions = TargetPositions,SeekerPositions = SeekerPositions,NearestTargetPositions = NearestTargetPositions,};// Schedule() puts the job instance on the job queue.JobHandle findHandle = findJob.Schedule();// The Complete method will not return until the job represented by// the handle finishes execution. Effectively, the main thread waits// here until the job is done.findHandle.Complete();// Draw a debug line from each seeker to its nearest target.for (int i = 0; i < SeekerPositions.Length; i++){// float3 is implicitly converted to Vector3Debug.DrawLine(SeekerPositions[i], NearestTargetPositions[i]);}}}

运行效率提升很明显:

并且在Profiler能看到该job的运行情况

对此提升的解释为:

1.Update从原来的每个Seeker里调用改成到只在Spawner里调。次数从1000次变为1次

2.这点提升最多,数据的获取是从3级缓存里提取非堆内存提取。因为数据是连续非离散的,这提高了访问效率。

既然这个能单线程跑,那么一定也能多线程跑起来从而进一步提升效率

对Job进行改造:

    //job逻辑是一样的,但是接入接口不一样,此为IJobParallelFor[BurstCompile]public struct FindNearestJob : IJobParallelFor{[ReadOnly] public NativeArray<float3> TargetPositions;[ReadOnly] public NativeArray<float3> SeekerPositions;public NativeArray<float3> NearestTargetPositions;public void Execute(int index){float3 seekerPos = SeekerPositions[index];float nearestDistSq = float.MaxValue;for (int i = 0; i < TargetPositions.Length; i++){float3 targetPos = TargetPositions[i];float distSq = math.distancesq(seekerPos, targetPos);if (distSq < nearestDistSq){nearestDistSq = distSq;NearestTargetPositions[index] = targetPos;}}}}//find这块,执行并发public class FindNearest : MonoBehaviour{NativeArray<float3> TargetPositions;NativeArray<float3> SeekerPositions;NativeArray<float3> NearestTargetPositions;public void Start(){Spawner spawner = GetComponent<Spawner>();TargetPositions = new NativeArray<float3>(spawner.NumTargets, Allocator.Persistent);SeekerPositions = new NativeArray<float3>(spawner.NumSeekers, Allocator.Persistent);NearestTargetPositions = new NativeArray<float3>(spawner.NumSeekers, Allocator.Persistent);}public void OnDestroy(){TargetPositions.Dispose();SeekerPositions.Dispose();NearestTargetPositions.Dispose();}public void Update(){for (int i = 0; i < TargetPositions.Length; i++){TargetPositions[i] = Spawner.TargetTransforms[i].localPosition;}for (int i = 0; i < SeekerPositions.Length; i++){SeekerPositions[i] = Spawner.SeekerTransforms[i].localPosition;}FindNearestJob findJob = new FindNearestJob{TargetPositions = TargetPositions,SeekerPositions = SeekerPositions,NearestTargetPositions = NearestTargetPositions,};// Execute will be called once for every element of the SeekerPositions array,// with every index from 0 up to (but not including) the length of the array.// The Execute calls will be split into batches of 100.//把任务拆成100个小任务,可能分到不同的cpu执行JobHandle findHandle = findJob.Schedule(SeekerPositions.Length, 100);findHandle.Complete();for (int i = 0; i < SeekerPositions.Length; i++){Debug.DrawLine(SeekerPositions[i], NearestTargetPositions[i]);}}}

帧率又进行了提升:

在Profiler里能看到已经分配到别的cpu执行调用了

最后是对排序那块进行部分优化。使用二分法,不过看起来性能没多少提升。甚至下降

[BurstCompile]public struct FindNearestJob : IJobParallelFor{[ReadOnly] public NativeArray<float3> TargetPositions;[ReadOnly] public NativeArray<float3> SeekerPositions;public NativeArray<float3> NearestTargetPositions;public void Execute(int index){float3 seekerPos = SeekerPositions[index];// Find the target with the closest X coord.int startIdx = TargetPositions.BinarySearch(seekerPos, new AxisXComparer { });// When no precise match is found, BinarySearch returns the bitwise negation of the last-searched offset.// So when startIdx is negative, we flip the bits again, but we then must ensure the index is within bounds.if (startIdx < 0) startIdx = ~startIdx;if (startIdx >= TargetPositions.Length) startIdx = TargetPositions.Length - 1;// The position of the target with the closest X coord.float3 nearestTargetPos = TargetPositions[startIdx];float nearestDistSq = math.distancesq(seekerPos, nearestTargetPos);// Searching upwards through the array for a closer target.Search(seekerPos, startIdx + 1, TargetPositions.Length, +1, ref nearestTargetPos, ref nearestDistSq);// Search downwards through the array for a closer target.Search(seekerPos, startIdx - 1, -1, -1, ref nearestTargetPos, ref nearestDistSq);NearestTargetPositions[index] = nearestTargetPos;}void Search(float3 seekerPos, int startIdx, int endIdx, int step,ref float3 nearestTargetPos, ref float nearestDistSq){for (int i = startIdx; i != endIdx; i += step){float3 targetPos = TargetPositions[i];float xdiff = seekerPos.x - targetPos.x;// If the square of the x distance is greater than the current nearest, we can stop searching.if ((xdiff * xdiff) > nearestDistSq) break;float distSq = math.distancesq(targetPos, seekerPos);if (distSq < nearestDistSq){nearestDistSq = distSq;nearestTargetPos = targetPos;}}}}public struct AxisXComparer : IComparer<float3>{public int Compare(float3 a, float3 b){return a.x.CompareTo(b.x);}}


http://www.ppmy.cn/embedded/153556.html

相关文章

《分布式光纤测温:解锁楼宇安全的 “高精度密码”》

在楼宇建筑中&#xff0c;因其内部空间庞大&#xff0c;各类电器设施众多&#xff0c;如何以一种既高效又稳定&#xff0c;兼具低成本与高覆盖特性的方式&#xff0c;为那些关键线路节点开展温度监测&#xff0c;是目前在安全监测领域一项重点研究项目&#xff0c;而无锡布里渊…

QT 端口扫描附加功能实现 端口扫描5

上篇QT 下拉菜单设置参数 起始端口/结束端口/线程数量 端口扫描4-CSDN博客 在扫描结束后设置Scan按钮为可用&#xff0c;并提示扫描完成 在 MainWindow 类中添加一个成员变量来跟踪正在进行的扫描任务数量&#xff1a; 在 MainWindow 的构造函数中初始化 activeScanTasks&…

深入理解 Java 设计模式之策略模式

一、引言 在 Java 编程的世界里&#xff0c;设计模式就如同建筑师手中的蓝图&#xff0c;能够帮助我们构建出更加健壮、灵活且易于维护的代码结构。而策略模式作为一种经典的行为型设计模式&#xff0c;在诸多实际开发场景中都发挥着至关重要的作用。它能够让算法的定义与使用…

物联网无线芯片模组方案,设备智能化交互升级,ESP32-C3控制应用

无线交互技术的核心在于实现设备之间的无缝连接和数据传输。在智能家居系统中&#xff0c;各种智能设备如智能灯泡、智能插座、智能门锁等&#xff0c;都通过无线网络相互连接&#xff0c;形成一个互联互通的生态。 用户可以通过语音助手、手机APP或其他智能终端&#xff0c;远…

每日一题(二):判断一个字符串是否是另一个字符串的排列

一、题目 实现一个算法来识别一个字符串str2是否是另一个字符串str1的排列。 排列的解释如下&#xff1a;如果将str1的字符拆分开&#xff0c;重新排列后再拼接起来&#xff0c;能够得到str2&#xff0c;那么就说字符串str2是字符串str1的排列。 要求&#xff1a;不忽略大小写。…

Linux(18)——提高命令行运行效率

目录 一、创建和执行 shell 脚本&#xff1a; 1、命令解释器&#xff1a; 2、执行 Bash Shell 脚本&#xff1a; 3、从 shell 脚本提供输出&#xff1a; 二、对特殊字符加引号&#xff1a; 1、反斜杠 &#xff08;\&#xff09;&#xff1a; 2、单引号 &#xff08; &…

使用正则表达式读取文本数据【Python】

使用正则表达式读取文本数据 假如我们需要处理的数据具有很强的规律性, 例如下面这样, 数据基本上都是一个独立的一行, 并且每个数据都有名称标志. RUN OU 1.903784OV 1.862293OW 1.860681OUINV 548.000793STOP index 1V 0.000000W 0.000000E_theta 0.000000UINV 0.…

Linux高并发服务器开发 第十二天(阻塞/非阻塞 fcntl函数 位图 lseek函数 传入传出参数)

目录 1.阻塞和非阻塞 2.fcntl 函数 3.位图 4.lseek 函数 5.传入参数传出参数 5.1传入参数 5.2传出参数 5.3传入传出参数 1.阻塞和非阻塞 - 阻塞、非阻塞是 设备文件、网络文件具备的属性&#xff08;不是read、write的属性&#xff09;。 - 产生阻塞的场景&#xff1…