多线程下的网格生成及性能分析

news/2024/7/4 10:46:57/文章来源:https://www.cnblogs.com/JimmyZou/p/18280836

前言

概述

  • 通过多线程方式实现上千个对象的网格生成,并观察运行效率。

  • 多线程通过Thread来进行,每个线程中执行GenerateMeshData方法,在方法中对不同种类的网格进行顶点和三角面序列的计算。首先设置简单立方体,之后改为柏林噪声下生成的复杂地形

主线程限制

Unity设计之初就是依靠单线程执行所有对象的生命周期的,所以有些地方无法支持多线程,此处大致进行举例:

  • Unity API调用:许多Unity的功能和API只能在主线程上调用,例如实例化、销毁、修改游戏对象、修改组件属性等。

  • 渲染相关操作:与渲染相关的操作,例如修改材质、设置渲染目标、更新纹理等,通常需要在主线程上执行。

  • 用户界面操作:与用户界面相关的操作,例如处理输入事件、更新UI元素等,也需要在主线程上执行。

因为这篇文章中,需要对网格进行生成,所以会通过mesh.vertices和mesh.triangles进行赋值,然而mesh的获取也是必须在主线程下进行的,所以每个线程中执行的GenerateMeshData方法实际上是计算所有顶点,存放到公共数组中。最后到主线程中,再对每个网格进行点和三角形的赋值,并对所有的网格进行合批,通过一个MeshRenderer来显示(见实现思路图)

运行效果

实现过程

实现思路

完整代码

using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using UnityEngine;public class MeshGeneratorMultiThreading : MonoBehaviour
{public int numThreads = 4;private List<Thread> threads;private List<Mesh> meshes;private MeshFilter meshFilter;private List<Vector3[]> verticesList;private List<int[]> trianglesList;private Stopwatch stopwatch;private string elapsedTime;private void Start(){stopwatch = new Stopwatch();stopwatch.Start();threads = new List<Thread>(numThreads);meshes = new List<Mesh>(numThreads);verticesList = new List<Vector3[]>(numThreads);trianglesList = new List<int[]>(numThreads);meshFilter = GetComponent<MeshFilter>();meshFilter.mesh = new Mesh();// 创建多个线程,开始计算生成网格数据for (int i = 0; i < numThreads; i++){meshes.Add(new Mesh());int threadIndex = i;Thread thread = new Thread(() => { GenerateMeshData(threadIndex); });threads.Add(thread);thread.Start();}// 阻塞等待所有的网格数据计算完成foreach (Thread thread in threads){thread.Join();}// 计算完成后应用于所有网格for (int i = 0; i < numThreads; i++){meshes[i].vertices = verticesList[i];meshes[i].triangles = trianglesList[i];}// 合并CombineMeshes();stopwatch.Stop();elapsedTime = $"Elapsed time: {stopwatch.Elapsed.TotalSeconds} seconds";}private void GenerateMeshData(int threadIndex){//生成立方体网格var (vs, ts) = GenerateCubeMeshData(threadIndex);// 将生成的网格数据添加到列表中lock (verticesList){verticesList.Add(vs);trianglesList.Add(ts);}}private (Vector3[] vertices, int[] triangles) GenerateCubeMeshData(int threadIndex){Vector3[] vertices = new Vector3[8];vertices[0] = new Vector3(-1, -1, -1);vertices[1] = new Vector3(1, -1, -1);vertices[2] = new Vector3(1, 1, -1);vertices[3] = new Vector3(-1, 1, -1);vertices[4] = new Vector3(-1, -1, 1);vertices[5] = new Vector3(1, -1, 1);vertices[6] = new Vector3(1, 1, 1);vertices[7] = new Vector3(-1, 1, 1);int sqrThreadNum = (int)Mathf.Sqrt(numThreads);for (var i = 0; i < vertices.Length; i++){vertices[i] += new Vector3((threadIndex / sqrThreadNum) * 3, (threadIndex % sqrThreadNum) * 3, 0);}int[] triangles = new int[36]{0, 2, 1, 0, 3, 2, // Front face1, 2, 5, 5, 2, 6, // Right face4, 5, 6, 4, 6, 7, // Back face0, 7, 3, 0, 4, 7, // Left face0, 5, 4, 0, 1, 5, // Bottom face2, 3, 6, 6, 3, 7  // Top face};return (vertices, triangles);}private void CombineMeshes(){// 合并所有生成的网格CombineInstance[] combine = new CombineInstance[meshes.Count];for (int i = 0; i < meshes.Count; i++){combine[i].mesh = meshes[i];combine[i].transform = Matrix4x4.identity;}meshFilter.mesh.CombineMeshes(combine, true, true);}private void OnGUI(){GUI.Label(new Rect(10, 10, 500, 20), elapsedTime);}
}

性能对比

通过StopWatch记录运行时间,分为多种对比情况:

(1)一个线程只绘制一个立方体,总共绘制6400个立方

多线程下,花费1.3s; 单线程下花费0.03s。反而多线程花费的更多了,因为开了6400个线程,线程开启的开销远远大于每个线程计算所花费的开销。

(2)一个线程只绘制一片地形(1000*1000 Unit),总共绘制100个地形

多线程下,花费3.4s; 单线程下花费8.9s。这里可以看出差距,但是差距只有常数倍

这是正常的,原因分析:

理想情况下,N核CPU在多线程下的效率应该是 1/N,N本身就是常数,所以差距是常数倍也是正常的。
但是我的电脑是8核的,为什么结果比8要小很多。因为通常情况下,线程数都是大于CPU核心数的,那一个核心就会(并发)处理多个线程,每个线程在切换的时候就要同时保存加载上下文,这是主要耗时的地方。

注:由于Unity的一个Mesh最多只支持65000个顶点,所以(2)未对网格进行设置、合批和渲染。

其他

MainThreadDispatcher(在子线程中将方法发送到主线程执行)

概述

编写过程中,写了一些其他的脚本,最终虽未用到,但是还是记录下来。MainThreadDispatcher主要是用于在子线程中能够方便调用主线程代码的(Unity API等)。

但是,都知道子线程是无法调用大多Unity API的,所以只能寻求别的方法。这里就是将这些方法作为委托对象存放到一个队列中,等待MainThreadDispatcher的主线程的Update生命周期时,对这些委托进行执行。

完整代码

using System.Collections.Generic;
using System.Threading;
using UnityEngine;namespace JimDevPack.Common.MultiThread
{public class MainThreadDispatcher : MonoBehaviour{private static MainThreadDispatcher instance;private static readonly Queue<System.Action> actions = new Queue<System.Action>();// 运行开始时,就执行的方法[RuntimeInitializeOnLoadMethod]private static void Initialize(){if (instance == null){GameObject dispatcherObject = new GameObject("MainThreadDispatcher");instance = dispatcherObject.AddComponent<MainThreadDispatcher>();DontDestroyOnLoad(dispatcherObject);}}void Update(){while (actions.Count > 0){actions.Dequeue()?.Invoke();}}public static void RunOnMainThread(System.Action action){if (action == null){throw new System.ArgumentNullException(nameof(action));}actions.Enqueue(action);}}
}

Task和Thread使用场景

https://mp.weixin.qq.com/s?__biz=MzI0MTU0ODQwMQ==&mid=2247485189&idx=1&sn=4f53f980da4de559c3bf7903b998bb0a&chksm=e908aa1bde7f230d59a2dba625123069c140eeac155791bcd7a093f68f4135e82afd133f837e&scene=27

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/736630.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

公众号软件链接

链接:https://wwt.lanzouq.com/b0174n6pc密码:gb5dhttps://wwt.lanzouq.com/b0174pdab密码:b357韩科信息咨讯每日科技资讯,致力于新颖、古怪网站分享与实用技巧!谢谢大家支持,您的"在看"、“分享”是我们不断进步之动力! 获取资源扫码关注我们 A world of wonde…

android 安装CA证书

android 安装CA证书 提取证书计算证书的hash值 C:\Users\czl\Downloads>"C:\Program Files\Git\usr\bin\openssl.exe" x509 -inform PEM -subject_hash_old -in charles.pem b682a732 -----BEGIN CERTIFICATE----- MIIFRjCCBC6gAwIBAgIGAY1GZhqIMA0GCSqGSIb3DQEB…

全网最适合入门的面向对象编程教程:07 类和对象的Python实现-类型注解-提高代码可读性的利器

本文对类型注解的定义、使用原因进行了基本介绍,同时对使用typing模块实现类型提示和类型检查进行了基本讲解,通过使用类型注解可以让开发者更清晰地了解函数和变量的预期类型。全网最适合入门的面向对象编程教程:07 类和对象的 Python 实现-类型注解-提高代码可读性的利器 …

训练记录(Jul.)

7/2 AT_abc217_f *提高+/省选- 设 \(f_{l, r}\) 为把 \([l, r]\) 消去的方案数。 P3607 [USACO17JAN] Subsequence Reversal P *省选/NOI- CF1922F *2500 状态很好想,之前是因为转移成环所以没补,结果发现也可以没环。 \(f_{l, r, x}\) 表示把 \([l, r]\) 推平成 \(x\) 的…

Ubuntu24.04下Docker安装与配置

安装docker 官方教程加上代理 sudo apt-get updatesudo apt-get install ca-certificates curlsudo install -m 0755 -d /etc/apt/keyringscurl指定代理 sudo curl -x http://127.0.0.1:7897 -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.…

idea中配置Maven

主要修改两个地方:如上图。 注意: 1、如果本地仓库地址不变化,只有一个原因,就是maven/conf/settings.xml配置文件编写错误!仔细检查即可! 2、一定保证User settings file对应之前修改的settings.xml的路径,若 不一致,选中Override复选框,手动选择配置文件

linux tinydrm vs fbtft 性能对比测试

linux tinydrm vs fbtft 性能对比测试 本文将通过若干组对照试验,然后根据实验得出的数据,计算在使用fbdev的情况下,tinydrm相较于fbtft的提升幅度。免责声明:本人时间、精力有限,实验中的测试数据量较小,所以不建议将本文中的数据用于比较严谨的场景。测试环境开发板 树…

七月二日 python完成简单围棋游戏

第二天 最近,数据结构的小学期,这两天主要是完成我自己的课题设计完成围棋游戏。 【题目43】围棋游戏 设计一个简易的围棋游戏。盘面有纵横各十九条等距离、垂直交叉的平行线,共构成361个交叉点(以下简称为"点")。棋子分黑白两色。对局双方各执一色棋子,黑先白…

【vscode】vscode通过端口访问本地html页面(Live Server)

在学习mapbox过程中,发现有的图片,如果直接浏览器路径打开html文件,图片不出来,报错,得用服务的形势来访问!!!相信坚持的力量,日复一日的习惯.

基于VLC可见光通信的室内光通信信道信噪比分析matlab仿真

1.算法运行效果图预览2.算法运行软件版本 matlab2022a3.部分核心程序Pr = POW_all.*H; % 接收功率(毫瓦) POW_r = Pr./1000; % 接收功率(瓦) Pr_dbm = 10.*log10(POW_r); % 接收功率(dBm)%信噪比(SNR Ib = 202e-6; % 背景光子通量 No = 2…

JAVA文件IO流

基本的目录、文件操作,常用的IO输入输出流类介绍和使用。一、目录及文件操作Java中File类(文件类)以抽象的方式代表文件名和目录路径名,File对象则代表了磁盘中实际存在的文件和目录。File类不仅仅提供灵活的构造方法,同时还可以用于文件和目录的创建、文件的查找和文件的删…

[JLU] 数据结构与算法上机题解思路分享-第三次上机

这是吉林等通知大学数据结构与算法上机题的题解思路,没有精妙的解法,只是一个记录罢前言 首先,请务必自己尽全力尝试实现题目,直接看成品代码,思维就被拘束了,也很容易被查重。 这里只是思路解析的博客,代码仓库在 JLU_Data_Structures_Record 希望你能在这里找到你想要…

Google 发布了最新的开源大模型 Gemma 2,本地快速部署和体验

Google 重磅发布了最新版大语言模型Gemma 2,其中 90亿 参数版本适合我们个人在笔记本本地部署,老牛同学通过本文和大家一起,通过2种方式快速部署和体验,当然这2种快速部署模型的方式,也同样适用于其他大模型……Gemma 2 是 Google 最新发布的开源大语言模型。它有两种规模…

ros - slam - microros - 两轮差速模型运动学 - 运动学逆解

上一节我们推导并在代码中实现了运动学正解,本节我们来学习下运动学逆解,实现给定线速度和角速度,计算出轮子达到怎样的转速才能达到这个速度。 一、逆解推导我们直接用正解结果进行求逆解即可。 二、编写代码继续在上一节中的代码Kinematics.cpp中完善即可。void Kinematic…

vscode 中code-runner插件 py配置

"code-runner.executorMap": {"javascript": "node","python": "$pythonPath $fullFileName",}本文来自博客园,作者:__username,转载请注明原文链接:https://www.cnblogs.com/code3/p/18280704

vscode code-runner配置

"code-runner.executorMap": {"javascript": "node","python": "$pythonPath $fullFileName",}本文来自博客园,作者:__username,转载请注明原文链接:https://www.cnblogs.com/code3/p/18280704

[IOT2050 question] Unable to listen on http://127.0.0.1:1880/ 端口被占用错误

1. 背景第一次连接node-red的时候,一直出现错误Unable to listen on http://127.0.0.1:1880/。如下:2. 原因分析估计是早前利用iot2050setup小工具把node-red设置为开机自动启动项了,导致1880端口一直被占用。3. 验证首先查看端口是否真的被占用,利用sudo netstat -ltup命令…

实战篇——SQL注入sqli-labs-master靶场实战二

实战篇——SQL注入sqli-labs-master靶场实战(2) SQL注入的高级利用 (1) 宽字节注入 有时后端会对用户输入的特殊字符进行转义处理,这时普通的注入方式就会失效。对于成对的单引号,可以通过十六进制编码的方式绕过转义;而对于单个的单引号,当数据库的编码格式为GBK时,就要用…

代码随想录算法训练营第四十五天 | 打家劫舍

198.打家劫舍 题目链接 文章讲解 视频讲解dp[j]: 表示投到第j家最多能偷dp[j]的钱 递推公式: dp[j] = max(dp[j-2] + nums[j], dp[j-1]) 初始化:dp[0] = nums[0], dp[1] = max(dp[0], dp[1]) 遍历顺序:从小到大 打印dp数组class Solution { public:int rob(vector<int>…