VS C++中方法前的星星是什么意思?

6 月 30 日,「Unity 大咖作客」邀请到知乎社区 Unity 引擎领域专家放牛的星星和 Unity 大中华区技术经理金晓宇,跟大家聊了聊性能优化

放牛的星星从定义、维度、常用工具等方面讲解性能优化,无论是新手小白还是技术老手都受益匪浅。深入浅出的讲述方式让观众们纷纷把“666”“感谢浩哥”打在了公屏上。

金晓宇以 GPU 的性能优化为主题带来了硬核干货分享,大家全程紧盯屏幕,生怕错过技术要点。

本文为大家摘录了性能优化专场的精彩内容,完整录播已上传至 B 站。

大家好,我是放牛的星星,非常荣幸为大家带来一期以性能优化为主题的技术分享。本次的主要内容是探讨如何优化 Unity 的移动游戏。

本次分享有以下四个主题:第一是探讨性能优化的相关信息;第二是探讨性能优化的相关维度;第三是了解一下常用的性能优化工具;第四是拓展一下在性能优化路上的更多可能性。

究竟什么是性能优化呢?我的个人总结,性能优化实际上是纠错使用、相互平衡以及充分利用软硬件资源的过程

我们看纠正错误使用,实际上是解决几个层面上的问题。比如说对语言特性的不了解,对引擎特性的不了解,以及对目标平台特性的不了解。

对于语言特性的不了解可以简单举一个例子,因为 Unity 使用的脚本是 C#,C# 里面一个导致性能问题的方式就是装箱。装箱实际上是 C# 将一个值类型向一个引用类型转变的过程。比如说我们现在看到的示例。

这个 string.format 的时候,实际上是将一个 int 值装箱成了 string 值,然后才能拼接成字符串。当一个引用类型被处理的时候,也就是说我离开了这个作用域,需要 GC 的时候,就会产生一些垃圾,就会造成一个不必要的性能损耗。

第二个是对引擎特性不了解,实际上我们可以表现在一些常用的脚本上面,比如说如果用 Unity 创建一个默认的 MonoBehaviours 的时候,里面会有一个空的 update 的方法。假如你不删除这个 update 方法的话,虽然它是一个空方法,但是仍然会有比较高的性能损耗。下面这幅图上是我从 Unity 官方网站上找到的一个案例。

实际上是执行了一个一万次的空方法调用,从上往下看只有在最下面的时候,那么一点点损耗的时间是用来做 update 里面实际上的空逻辑方法的。大部分时间都是在 C++ 往 C# 层调用的时候,这个过程当中产生的。所以说即使我们不要用的话,也需要把 update 函数去删掉,如果不删掉的话可能会造成性能问题,这也是常规的大家会忽略的引擎的一些特性所造成的性能问题。

还有一种是对目标平台特性的不了解,实际上我们都知道不同的硬件平台所支持的 GPU 的纹理格式是不相同的。如果没有选对平台上的纹理格式,或者直接使用 RGBA 或 RGB 纹理格式,实际上在内存和带宽会导致非常大的性能问题。我们如果了解这些目标平台特性的话,实际上就会为这个平台选取最合适的纹理格式。

第二个点就是相互平衡。相互平衡就是用一种资源去换取另外一种资源,比如说 loading 速度比较慢,或者加载速度比较慢,实际上是可以把加载好的这些资源进行缓存,然后用内存去换取 IO 的读取速度。比如说像 CPU 执行比较长,或计算耗时比较高的时候,也可以把结果缓存起来,用内存换取 CPU 执行时间。当然像一些比如说骨骼节点、蒙皮计算等等,如果 CPU 消耗时间比较长,也可以用动态骨骼的方案或者说 GPU skinning 的方式,将 CPU 损耗开销到内存和 GPU 上面去。这些都是一个相互平衡的案例。

实际上在游戏开发过程中,这种相互平衡是处处可见的,大家需要根据自己的性能瓶颈的卡点处来决定这些方案的执行。

充分利用软硬件的行为,最典型的是 Unity 的 DOTS,基于数据栈的编程方式。这个方面,我们是充分利用了多核的特性。还有 GPU Driven,它是近段时间或近年来频繁听到的一个词。根据我的个人理解,GPU Driven 不是很具体的方案或者完整的解决流程,而是一种思想——我们应尽可能地充分利用现代 GPU 的性能特性,尽可能将工作从 CPU 端释放出来,以 GPU 算力为准,然后充分利用 GPU 的算力。最后是像 Unity 的多线程渲染等等,这些技术实际上都是在充分利用现代硬件的特性。

接下来看一下对性能优化还关注哪些维度?从我个人的感觉来说,我们可以把一级性能维度归咎为 CPU、GPU、内存、IO 的四个层面。关于一级性能维度,后面我还会详细讲,这里就大概列一下。

下面我们要关注一下性能优化的行业现状大概是什么样的样子呢?

目前我个人的经验,就是现在的行业现状,第一个是缺认。缺认知的意思,实际上又分为两个层面,第一个是项目或者是公司缺乏一个性能优化的认知,并没有把性能优化从立项期就根植到项目的骨髓里面去,往往很多项目在上线前临时抱佛脚进行一次突击式优化,这样实际上是治标不治本的。因为优化的成本越高后期,成本就越高。我们如果能够从一开始就对游戏开发有性能优化的认知,从最开始就开始小步迭代性能的话,到最后上线之前是不会有非常大的性能问题的。

如果真正到了那个时候有非常大的性能问题的时候,再去发现问题,然后再去调整的时候,你会发现很多掣肘的地方,包括方案不能改,妥协的地方没有办法动,这个是非常被动的。

第二个是开发人员缺乏认知。性能优化的认知是建立在开发人员有一定经验的基础上面,才能去知道我写的每一段代码,或者我写的解决方案存在哪些问题,或者说有哪些好处。大部分大家在写代码的过程中以实现为主,对性能的考量并不是特别多,这也是过往我的经验当中发现比较多的类似的问题。

其次是缺标准。缺标准的意思是在性能优化过程中没有完善的标准或者体系,能够让所有的项目去遵从这个事情,或者说能够让项目看一眼,或者学习一段时间,就能够批量或者流水线的方式去解决性能问题。目前来说,是没有一个这样的标准。不管可能是因为公司,或者是项目本身的人才质量,也可能是公司本身的一些环境等等导致的。包括性能优化,实际上也是近几年才被各个公司所重视的,所以在这个层面上面是没有一个统一的标准。包括对于不同类型的游戏来说,也不能以一概全。

第三是缺人才。性能优化是建立在有一定工作经验基础上的,相对来说还是比较吃经验的。我会在最后面的会去总结一下人才的相对标准,这一块可以放到后面去说。

现在我们既然没有标准,是否有可行性的岗位实践和职位现状呢?我这边总结了一点点,大概总结了四个职位信息。

第一是程序。因为大部分的时候代码或执行效率都是由程序来实现的,最大的问题实际上也是在程序方面需要去解决的。这个时候,程序这个职位是必不可少的。

第二是 TA。TA 是负责渲染或图形层面的内容,在图形层面也会产生比较大的性能问题,比如说 Overdraw、剔除等等,跟图形相关的一部分是需要 TA 帮助解决的。

第三是 QA。性能的 QA,实际上是需要了解整个性能这一条链路,或者管线上面所关注的性能指标。然后通过程序或 TA 提供的工具,能够监控和发现性能问题,然后能够验收或者回归解决完的性能问题,这是一个性能 QA 的标准。

第四是 PM。为什么我们会提到 PM 呢?因为很多性能问题的解决方案可能是需要美术参与,比如说特效的性能问题,可能要更改特效的执行流程,或者是特效的制作方式。比如说我们的纹理格式、切图方式等等跟美术相关的部分,实际上大部分的美术人员对于性能并不是特别敏感,基本上都是由项目这一块执行完了标准之后交给美术,美术按照规格去做图。一旦标准改变了之后,就需要美术人员去改动标准,或者是按照新的标准去做事情,这个时候因为美术的体量比较大,他是需要 PM 去跟进这个事情的。这个是职位现状。

放牛的星星后续还分享了性能优化的相关维度、常用的性能优化工具以及性能优化方面的更多可能性,完整录播尽在 B 站「Unity官方」频道。

大家好,我是金晓宇,今天要跟大家分享的主题是 GPU 的性能优化。下面让我们先来看一下分享的概述:

首先会先介绍 Shader 常用的优化和要注意的点;接着跟大家分享或者介绍一下 GPU Occupancy&latency 是什么,以及对性能会做出哪些影响;最后会针对移动端的 Tile-based Rendering 给出一些分析及优化的建议。

首先是针对 Shader 指令的一些优化。

我们使用最合适的数据类型是比较好的,可以使编译器和驱动去优化代码。比如左边绿色方框的代码,int4 foo 这是一个 Shader 里的函数,它的输入是一个 int4 x,然后返回是一个 x + 1。通常来说,这段代码经过编译器优化之后,通常只需要一个指令就可以完成。右边的 int4 红色代码,和左边唯一不一样的地方,就是 return x + 1.0,这个代码是十分不友好的,通常需要 8 个指令才能完成。因为 1.0 需要转换为 foo4,然后再从 foo4 转化为 int4,正常需要 8 个指令来完成。可见一点点的隐式转换就可以大大地增加指令的消耗,我们应该尽可能避免隐式转换的操作。

建议尽可能把标量数据 pack 成向量,可以提高硬件 GPU 的读取效率。而指令的缓存是经常被忽略的地方,而它可以影响 GPU 上 warp 或者 wavefront 运行的一个线程数量,应该尽可能缩短 Shader 着色器的代码。而且一个较短的 Shader 着色器,更有可能在高速缓存中命中。

下面来谈精度,首先是顶点属性的精度。并不是所有的顶点属性都需要 FP32 的高精度,比如说颜色、法线,资产管道应该将数据保持在所需最低的精度,这样做可以减少带宽,并且提高性能。

而且 Mali GPU 可以在数据加载的时候,将属性免费转换为 FP32。这样使用较低的数据精度、数据类型,不会产生额外处理器的开销。

所以给大家这几个建议,比如顶点 position 需要额外的精度,可以使用 FP32 来计算顶点位置;其他属性使用低精度,只有在需要的时候才提高到高精度;还有不要把 FP32 数据上传到 buffer,然后再作为一个低精度属性来在 Shader 里读取。这样做会浪费内存的存储和带宽,因为额外的精度就被丢弃了。

下面再来谈一下 VS 输出数据 varying 的精度,我觉得尽可能减少 varying 的精度是很重要的。因为这样做可以减少 varying 所需的内存、存储、带宽的数量。而且也会间接地影响 Shader 占用的寄存器的数量。特别的、更重要的是移动端 GPU,因为在执行 FS 或 PS 之前,都会被写回到 System Memory 里,相比于 IMR 架构来说会有更高的带宽的消耗。

再就是确保每一个 varying 的数据,都能在 FS 里用到或者 PS 里用到,换句话说,不要输出给 FS 没有用到的 varying 数据。

一般从经验上来说,以下的数据可以用低精度,比如法线和切线,他们用 FP16 是足够的,因为他们都是 nomalized,他们的取值范围在 -1 到 1 之间,用低精度是够的。顶点色等颜色对精度并不敏感。还有小于等于 512×512 的纹理的 uv,这样就是足够的。

还有一些数据建议用高精度,世界坐标就不用说了,还有一个是大纹理的 uv 或者 wrapmode 是 repeat 的 uv。因为当这个 uv 取较大的一些值的时候,可能需要更高的精度。

接下来再聊一聊 buffer 和寄存器,我们知道当使用寄存器过多的时候就会降低 warp 的并行性,warp 是 Nvidia 显卡 GPU 的概念,之所以会这样,因为 SM(Streaming Multiprocesser)寄存器数量是固定的,寄存器会平均分配给运行的一些 warp。当 Shader 需要的寄存器越多的时候,生成的 warp 就越少,可以切换的 warp 就越少。我们在等待指令完成的时候,比如说在等待 Texture Fetch 的时候可以做的工作就越少,GPU 可以做的工作就越少。

举个例子,在 URP 里,多光源的 shading 就改为了一个 pass 来执行。相对于多 pass 的 shading 可能并不能带来多少性能的提升。因为如果是 GPU Bound 的话,更多的光源就占用了更多的 buffer,使用了更多的寄存器,间接就影响了 warp 的数量,运行时并行性就会下降。

根据我个人的理解,多光源单 pass shading 主要降低的是 CPU 消耗,而不是 GPU 的。

另外是如果寄存器使用太多的话,会造成一个寄存器溢出的现象。寄存器溢出后,会导致 GPU 从 System Memory 里来读取,比如说 Uniform Buffer 之类的,这就加重了 fetch data 的消耗。

我们怎么来减少寄存器的使用量呢?寄存器的使用量通常由以下因素决定的,比如说 Uniform Buffer 的数量、变量数量,包括临时变量,还有 varying 的使用的数量。所以说大家在编写 Shader 的时候,可以尽可能减少这些东西的用量。

接下来再说一下 Texture Fetch,我列了几条大家平常用到的。第一个是尽可能避免随机访问。第二是访问 3D 纹理的代价通常会较高,这也是比较符合我们直觉的。第三是尽可能减少 Texture Fetch,这条只是一般来讲都会提高性能。第四是压缩纹理,这个是一定要做的。第五是使用 mipmap。平均来看,各向异性 filter 的消耗大概是各向同性的两倍。

最后来聊聊 Branching,也就是 Shader 里面的一些 if 之类的。首先我们都知道 if 语句会增加 GPU 的开销,主要做的就是降低 warp 的并行性,降低了 SM 的吞吐量,所以我们应该尽可能减少动态分支语句。所谓的动态分支语句在运行时才能确定是哪个分支的语句。我们通常可以用一些函数,比如 max、min 减少一些比较小的分支语句,相信大家都已经比较熟了。

还有一种分类是静态分支语句,这种一般是可以接受的。何为静态分支语句呢?就是我们知道 Shader 的执行一般是以线程组为单位来运行的,所以当 warp 走的是一个分支的话,这个分支就不会出现一个既执行分支 A,又执行分支 B 的情况。此时对于 SM 吞吐量的影响就比较小了。对于最常用的静态分支,就是 uniform 变量作为条件的动态语句了,因为 uniform 变量在每一个

最后一个,就是 early quit 的分支语句有时候可以提高性能,比如说在计算点光源的光照时,可以首先计算一下着色点到光源的距离。如果大于光源的影响范围的话,就可以提前退出。虽然这个语句肯定是会影响 SM 吞吐量的,但是由于多数的 warp 走的是同一个分支的话,如果可以尽早退出的话,还是有可能提高性能的。

接下来再聊一下 Loop,也就是循环。一般针对 Loop,通常用 unroll 来展开循环,可以减少 flow control 的数量。不过我们要展开的话,Loop 的循环在编译器决定的,编译器才能够通过 unroll 的标记来展开循环,就可以减少 Shader 代码中 control 的数量。

但是它也有一点副作用,因为它会增加寄存器的用量。如果在循环里会采样一些 Texture,比如说 ray marching 的时候,他会倾向于把 Texture Fetch 放在 Shader 的顶部,就可以间接增加寄存器的用量。如果循环次数不多的话,我建议还是手动展开会比较好。最重要、最好的就是确认一下展开和不展开前后的一个实际性能消耗,对比一下是最好的。

下面介绍一个对于 ray marching 类似的循环结构,每个循环可以多次进行优化。比如说 ray marching 中每个循环可能会采样一次 depths,这里的问题就是说如果在循环里面的 warp 有可能并不能完全掩盖 Texture Fetch 的延迟。关于延迟,我后面会花一点时间来介绍一下。所以一个比较常用的办法,就是每个循环采样两次 depth,通过把两次 Fetch 语句紧挨在一起,然后可以利用编译器的优化把 Texture Fetch batch 起来,这些 Fetch 就可以并行地来执行。

原本两次 Fetch 的延迟优化为一倍的延迟。当然在实践中还是要 profiling,如果 profiling 优化有用的话,可以甚至更激进一点,每个循环去步进更多步进比如说四次,然后一点一点去优化到最优的性能。

本期「Unity 大咖作客」完整版已经上传至 B 站,欢迎大家关注 B 站「Unity官方」频道:

}

◆ 要免费下载配套课件、源代码、计算机二级C语言题库和模拟系统软件?加C语言qq群:,或读者综合群:,群文件即可下载

◆ 要免费观看课件样例视频?点击文末的“阅读原文”链接。

计算机二级级C语言题库免费下载,及题库更新第一手信息、最新版下载,加C语言群:,或读者综合群:

◆ 二级Office的题库?请关注本公众号,后台(不是文章留言)回复:题库

清华大学出版社优选教材《C语言其实很简单》已第7次重印(/),在官网页面右上角的搜索框中输入“C语言其实很简单”,点放大镜按钮搜索。然后进入本书介绍页面,在资源下载处即可下载,如下图:

在“课件下载”和“网络资源”处下载均可。

长按将下面链接复制到浏览器,免费试读电子书:

/read//.html (左侧图书封面图片右下角点击“免费在线读”)

本书是为零基础的C语言初学者量身定做的,特别适合非计算机专业的读者自学C语言。本书尽量避免使用专业术语,利用大量贴近生活的实例,用通俗易懂的方式 讲解C语言的基本概念和基本编程方法,并提供许多独特的小窍门、小技巧、小口诀等,使读者在轻松的环境中花费很少的时间就能掌握C语言,并应用自如。 

本书兼顾了全国计算机等级考试二级C语言程序设计考试大纲的相关要求,可以作为等级考试辅导教材和培训班教材使用。对于大、中专院校师生、各类C语言应试 备考人员、广大C语言编程爱好者,都具有很好的学习参考价值。

《C语言其实很简单》清华大学出版社,2015年7月第一版
印刷日期:2015.07第1次印刷,第7次印刷

全国新华书店及各大网店有售。

}

我要回帖

更多关于 c++星号什么意思 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信