在1月份调查Apple M1 GPU的几个星期后,我能够使用自己的开源代码绘制三角形。虽然我开始解剖指令集,但指定为机器代码的着色器。真正的图形驱动程序需要从高级着色语言(GLSL或金属)的编译器到原生二进制文件。我们对M1 GPU指令集的理解已在过去几个月中提出。上周,我开始编写一个针对Apple GPU的免费和开源着色器编译器。进度很快:在第一周结束时,它可以编译基本顶点和片段着色器,足以渲染3D场景。上图所示的旋转立方体具有以惯用的GLSL编写的着色器,与新鲜的Free Software编译器编译,并在1月份的第一个三角形等本机代码呈现。这里没有专有的斑点!
在过去的几个月里,Dougall Johnson已经调查了深入的指令,建立了我的初始工作。他对架构的调查结果很出色,专注于计算内核,以补充我对图形的关注。用他的笔记和我的命令流制品武装,我可以在编译器上剪切。
编译器的设计必须适合开发背景。 Asahi Linux旨在在Apple Silicon上运行Linux桌面,因此我们的司机应遵循Linux的最佳实践,如上游开发。这包括在MESA中使用新的中间表示(NIR),为开源图形驱动程序中的家庭。 NIR是一个用于着色器编译器的轻量级库,具有GLSL前端和包括英特尔和AMD的后端目标。 NIR是LLVM的替代方案,Apple使用的编译器框架。仅仅因为苹果喜欢llvm并不意味着我们必须。阀门的一支团队着名被重写了AMD的LLVM后端作为NIR编译器,提高性能。如果它足够好的阀门,这对我来说足够了。
支持NIR作为输入不决定我们的编译器自己的中间代表,这反映了硬件的设计。 AGX2(Apple的GPU)的指令集具有:
矢量值在外围分离为载体组合并提取伪指令,在寄存器分配期间优化。
来源和目的地是大小的。优化器将大小转换指令折叠为使用和定义。
来源具有绝对值和否定位;说明具有饱和位。同样,优化器将这些折叠折叠。
一个大的寄存器文件意味着寄存器略有罕见,因此不要优化寄存器溢出性能。
最小化寄存器压力至关重要。使用静态单分配(SSA)表单以促进压力估计,通知优化。
调度程序只需重新排序“指令”而不泄漏到后端其余部分的详细信息。在注册分配之前和之后,调度是可行的。
将其放在一起,agx编译器的设计出现:将NIR转换为基于SSA的中间表示的代码生成器,通过指令组合传递优化,计划最小化寄存器压力,在从SSA外面的同时分配的寄存器,再次调度以最大化指令 - 灵活性,最后包装到二进制指令。
这些决策反映了软件可见的硬件特征,它们本身是“阴影”由硬件设计施放。调查这些特征可以深入了解硬件本身。考虑注册文件。虽然每个线程可以访问最多256个半字寄存器,但有一个性能损失:使用的寄存器越多,可能的并发线程越多,因为线程共享寄存器文件。在金属作为MaxTotalThreadSperThreadGreadGreadGreadGroupGroupGroup属性中报告给定的着色器中允许的线程数。因此,我们可以通过改变金属着色器的寄存器压力来研究寄存器压力与占用速度,通过MAXTOTALTHREADSPERTHREADGROUP的价值与MAXTOTALTHREADSPERTHREADGROUP的价值相关:
从桌面上,明确才能直到阈值,这并不重要,这些寄存器使用了多少;占用率不受影响。最精良的着色器落在这个括号内,不必担心。在击中阈值后,其他GPU可能会向内存泄露寄存器,但苹果不需要溢出,直到需要超过256个寄存器。在112和256寄存器之间,线程数以几乎线性的方式减少,以64个线程为增量。仔细考虑舍入,恢复配方金属用于将寄存器用法映射到螺纹计数很容易。
什么不太明显的是,我们可以推断机器的注册文件的大小。一方面,如果使用256个寄存器,则机器仍然可以支持384个线程,因此寄存器文件必须至少为256个半字* 2字节/每半词* 384线程= 192kib大。同样地,在104寄存器处支持1024个线程,需要至少104 * 2 * 1024 = 208kib。如果该文件有任何更大,我们会期望在更高的压力下需要更多的线程,因此我们猜测每个线程组在其寄存器文件中有208个Kib。
这个故事没有结束那里。来自Apple的公共规格,M1 GPU支持24576 = 1024 * 24同时线程。由于该表显示了每个ThreadGroup最多1024个线程,因此我们推断24个线程组可以在芯片上并行执行,每个芯片都有其自己的寄存器文件。将其放在一起,GPU有208个Kib * 24 = 4.875 MIB的注册文件!这种大小将其与桌面GPU联盟。
对于所有可见硬件功能,考虑不存在的硬件功能同样重要。有趣的是,GPU缺乏竞争对手普遍存在的一些固定功能图形硬件。例如,我没有遇到用于读取顶点属性或均匀缓冲对象的硬件。 OpenGL和vutkan规范为每个人假设专用硬件,所以捕获是什么?
简单地说 - 苹果不需要关心vulkan或OpenGL性能。他们唯一适当支持的API是他们自己的金属,它们可以塑造以适应硬件而不是扭曲硬件以匹配API。实际上,金属解除了顶点属性和均匀缓冲区,优先达到一般恒定缓冲器,以计算为中心的设计。编译器负责将固定功能属性和统一状态转换为着色器代码。从理论上讲,这有轻微的运行时间成本;传统智慧表示专用硬件比软件仿真更快,功耗较低。在实践中,代码如此简单,它可能没有任何区别,尽管应用程序开发人员应该注意在插入转换代码时使用的顶点格式。一如既往,有一个折衷:省略功能允许Apple将更多算术逻辑单元(或寄存器文件!)挤压到芯片上,加快其他一切。
更重要的问题是CPU花费编译着色器的增加时间。如果更改的固定函数属性状态会影响着色器,则可以在随机OpenGL呼叫期间在Inopportune时调用编译器。在这里,Apple具有另一个技巧:金属需要在创建流水线时要指定的顶点属性的布局,允许编译器以不额外的成本专门化格式。 OpenGL司机支付设计决定的价格;金属免于着色器重新编译税。
银色衬里是“缺少”特征,如属性和均匀缓冲区,没有任何反向工程师。只要我们知道如何在Compute Kernels中访问内存,我们就可以编写下降代码,没有硬件谜团。到目前为止,我已经实现了足以旋转立方体。
目前,正在进行的编译器支持OpenGL ES 2.0中的大多数算术和输入/输出指令,具有简单的优化器和本机指令包。支持控制流,纹理,调度和注册分配将在朝向真正的驱动程序工作时进一步下降。