本文将会详细阐述一下这次大会Metal相关的新特性,以及对于闲鱼技术和整个淘系技术来说,这些新特性带来了哪些技术启发与思考。
前言
Metal 是一个和 OpenGL ES 类似的面向底层的图形编程接口,通过使用相关的 api 可以直接操作 GPU ,最早在 2014 年的 WWDC 的时候发布。Metal 是 iOS 平台独有的,意味着它不能像 OpenGL ES 那样支持跨平台,但是它能最大的挖掘苹果移动设备的 GPU 能力,进行复杂的运算,像 Unity 等游戏引擎都通过 Metal 对 3D 能力进行了优化, App Store 还有相应的运用 Metal 技术的游戏专题。
闲鱼团队是比较早在客户端侧选择Flutter方案的技术团队,当前的闲鱼工程里也是一个较为复杂的Native-Flutter混合工程。作为一个2C的应用,性能和用户体验一直是闲鱼技术团队在开发中比较关注的点。而Metal这样的直接操作GPU的底层接口无疑会给闲鱼技术团队突破性能瓶颈提供一些新的思路。
下面会详细阐述一下这次大会Metal相关的新特性,以及对于闲鱼技术和整个淘系技术来说,这些新特性带来了哪些技术启发与思考。
Metal相关新特性
▐ Harness Apple GPUs with Metal
这一章其实主要介绍的是Apple GPU的在图形渲染上的原理和工作流,是一些比较底层的硬件原理。当我们使用Metal进行App或者是游戏的构建的时候,Metal会利用GPU的tile-based deferred rendering (TBDR)架构给应用和游戏带来非常可观的性能提升。这一章主要就是介绍GPU的的架构和能力,以及TBDR架构进行图像渲染的原理和流程。总之就是号召开发者们使用Metal来构建应用和游戏。因为这个session没有涉及到上层的软件开发,就不对视频的具体内容进行赘述了。详情可见:Harness Apple GPUs with Metal
▐ Optimize Metal apps and games with GPU counters
这一章主要介绍了Xcode中的GPU性能分析工具Instrument,这个工具现在已经支持了GPU的性能分析。然后从多个方面分析了GPU的性能瓶颈,以及性能瓶颈出现时的优化点。总体来说就是通过性能分析工具来优化我们的App或者游戏,让整个画面更加流畅。整个章节主要分为五个部分:
✎ 总体介绍
这个环节主要是快速回顾了一下Apple的GPU的架构和渲染流程。然后因为很多渲染任务都需要在不同的硬件单元上进行,例如ALU和TPU。他们对不同的吞吐量有着不同的度量。有很多GPU的性能指标需要被考虑,所以推出了GPU性能计数器。这个计数器可能测量到GPU的利用率,过高和过低都会造成我们的渲染性能瓶颈。关于计数器的具体使用,参考官方的video效果会更好:Optimize Metal apps and games with GPU counters(6:37~9:57),主要使用了Instrument工具,关于工具的全面详细的使用可以参考WWDC19的session videoGetting Started with Instruments
✎ 性能瓶颈分析
这一章主要介绍了造成GPU性能瓶颈的各个方面以及它们的优化点。主要分为六个方面,如下图所示:
1.Arithmetic(运算能力)
GPU中通常通过ALU(Arithmetic Logic Unit)来处理各种运算,例如位操作,关系操作等。他是着色器核心的一部分。在这里一些复杂的操作或者是高精度的浮点运算都会造成一些性能瓶颈,所以给出以下建议来进行优化:
如上图所示,我们可以使用近似或者是查找表的方式来替换复杂的运算。此外,我们可以将全精度的浮点数替换为半精度的浮点数。尽量避免隐式转换,避免32位浮点数的输入。以及确保所有的着色器都使用Metal的“-ffast-math”来进行编译。
2.Texture Read and Write
GPU通过Texture Processing Unit来处理纹理的读写操作。当然在读写的过程中也会遇到一些性能瓶颈问题。这里从读和写两个部分分别来给出优化点:
如上图所示,我们可以尝试使用mipmaps。此外,可以考虑更改过滤选项。例如,使用双线性代替三线性,降低像素大小。确保使用了纹理压缩,对Asset使用块压缩(如ASTC),对运行时生成的纹理使用无损纹理压缩。
如上图所示,我们应该注意到像素的大小,以及每个像素中唯一MSAA样本的数量。此外,可以尝试一些优化一些逻辑写法。
✎ Tile Memory Load and Store
图块内存是一组存储Thread Group和ImageBlock数据的高性能内存。当从ImageBlock或是Threadgroup读取或写入像素数据时,比如在使用Tile着色器时或者是计算分派时,可以访问到Tile内存。那当使用GPU性能计数器发现这个方面的性能瓶颈时,我们可以如下图所示进行优化。
考虑减少threadgroup的并行,或者是SIMD/Quadgroup操作。此外,确保将线程组的内存分配和访问对齐到16字节。最后,可以考虑重新排序内存访问模式。
✎ Buffer Read and Write
在Metal中,缓冲区只被着色器核心访问。在这个地方发现了性能瓶颈。我们可以如下图所示进行优化:
可以更大力度的压缩打包数据,例如使用例如packed_half3这样小的类型。此外,可以尝试向量化加载和存储。例如使用SIMD类型。避免寄存器溢出,以及可以使用纹理来平衡工作负载。
✎ GPU Last Level Cache
如果在这个方面,我们的GPU性能计数器显示一个过高的值。我们可以如下图这样优化:
如果纹理或者是缓存区也同样显示一个过高的值,我们可以把这个优化放到第一优先级。我们可以考虑减小工作集的大小。如果Shader正在使用Device Atomics,我们可以尝试重构我们的代码来使用Threadgroup Atomics。
✎ Fragment Input Interpolation
分段输入插值。分段输入在渲染阶段由着色器核心进行插值。着色器核心有一个专用的分段输入插值器。这个是比较固定和高精度的功能。我们能优化的点不多,如下图所示:
尽可能的移除传递给分段着色器的顶点属性。
▐ 内存带宽
内存带宽也是影响我们GPU性能的一个重要因素。如果在GPU性能计数器的内存带宽模块看到一个很高的值。我们就应该如下图所示来进行优化:
如果纹理和缓存区也同样显示比较高的值,那优化优先级应该排到第一位。优化方案也是较少Working Set的大小。此外,我们应该只加载当前渲染过程需要的数据,只存储未来渲染过程需要的数据。然后就是确保使用纹理压缩。
▐ Occupancy
如果我们看到整体利用率比较低,这意味着Shader可能已经耗尽了一些内部资源,比如tile或者threadgroup内存。也可能是线程完成执行的速度比GPU创建新线程的速度快。
▐ 避免重复绘制
我们通过GPU计数器可以统计到重复绘制的区域,我们应该高效使用HSR来避免这样的重绘。我们可以如图所示的顺序来进行绘制。
Build GPU binaries with Metal
这一章主要给开发者们介绍了一种使用Metal的编程工作流,可以通过优化Metal的渲染编译模型来增强渲染管线,这个优化可以在应用程序启动,特别是首次启动时大大减少PSO(管线状态对象)的加载时间。可以让我们的图形渲染更加的高效。整个章节主要分为四个部分:
▐ Metal的Shader编译模型概述
众所周知,Metal Shading Language是Apple为开发者提供的Shader编程语言,Metal会将编程语言编译成为一个叫做AIR的中间产物,然后AIR会在设备上进一步编译,生成每个GPU所需的特定的机器码。整个过程如下图所示:
上述过程在每个管线的生命周期中都会发生,当前Apple为了加速管线的重新编译和重新创建流程,会缓存一些Metal的方法变体,但是这个过程还是会造成屏幕的加载耗时过长。而且在当前的这个编译模型中,应用程序不能在不同的PSO(管线状态对象)中重用之前生成的机器码子程序。
所以我们需要一种方法来减少这个整个管线编译(即源代码->AIR->GPU二进制代码)的时间成本,还需要一种机制来支持不同PSO之间共享子程序和方法,这样就不需要将相同的代码多次编译或者是多次加载到内存中。这样开发者们就可以使用这套工具来优化App首次的启动体验。
▐ Metal二进制文件介绍
Metal二进制文件就是解决上述需求的方法之一,现在开发者们可以直接使用Metal为二进制文件来控制PSO的缓存。开发者可以收集已编译的PSO,然后将它们存储到设备中,甚至可以分发到其他兼容的设备中(同样的GPU和同样的操作系统),这种二进制文件可以看做一种Asset。下面是一些例程和示意图:
//创建一个空的二进制文件
let descriptor = MTLBinaryArchiveDescriptor()
descriptor.url = nil
let binaryArchive = try device.makeBinaryArchive(descriptor:descriptor)
//Populating an archive
// Render pipelines
try binaryArchive.addRenderPipelineFunctions(with: renderPipelineDescriptor)
// Compute pipelines
try binaryArchive.addComputePipelineFunctions(with: computePipelineDescriptor)
// Tile render pipelines
try binaryArchive.addTileRenderPipelineFunctions(with: tileRenderPipelineDescriptor)
//重用已编译的方法
// Reusing compiled functions to build a pipeline state object from a file
let renderPipelineDescriptor = MTLRenderPipelineDescriptor()
// …
renderPipelineDescriptor.binaryArchives = [ binaryArchive ]
let renderPipeline = try device.makeRenderPipelineState(descriptor:
renderPipelineDescriptor)
//序列化
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let archiveURL = documentsURL.appendingPathComponent(“binaryArchive.metallib”)
try binaryArchive.serialize(to: NSURL.fileURL(withPath: archiveURL))
//反序列化
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let serializeURL = documentsURL.appendingPathComponent(“binaryArchive.metallib”)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
言尽于此,完结
无论是一个初级的 coder,高级的程序员,还是顶级的系统架构师,应该都有深刻的领会到设计模式的重要性。
- 第一,设计模式能让专业人之间交流方便,如下:
程序员A:这里我用了XXX设计模式
程序员B:那我大致了解你程序的设计思路了
- 第二,易维护
项目经理:今天客户有这样一个需求…
程序员:明白了,这里我使用了XXX设计模式,所以改起来很快
- 第三,设计模式是编程经验的总结
程序员A:B,你怎么想到要这样去构建你的代码
程序员B:在我学习了XXX设计模式之后,好像自然而然就感觉这样写能避免一些问题
- 第四,学习设计模式并不是必须的
程序员A:B,你这段代码使用的是XXX设计模式对吗?
程序员B:不好意思,我没有学习过设计模式,但是我的经验告诉我是这样写的
从设计思想解读开源框架,一步一步到Spring、Spring5、SpringMVC、MyBatis等源码解读,我都已收集整理全套,篇幅有限,这块只是详细的解说了23种设计模式,整理的文件如下图一览无余!
搜集费时费力,能看到此处的都是真爱!
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
写能避免一些问题
- 第四,学习设计模式并不是必须的
程序员A:B,你这段代码使用的是XXX设计模式对吗?
程序员B:不好意思,我没有学习过设计模式,但是我的经验告诉我是这样写的
[外链图片转存中…(img-3WOuuzKn-1712656132699)]
从设计思想解读开源框架,一步一步到Spring、Spring5、SpringMVC、MyBatis等源码解读,我都已收集整理全套,篇幅有限,这块只是详细的解说了23种设计模式,整理的文件如下图一览无余!
[外链图片转存中…(img-XauZSkyv-1712656132699)]
搜集费时费力,能看到此处的都是真爱!
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-GSq2f80Q-1712656132700)]