Vulkan:扩展到多个线程

我们发布了系列有关Vulkan的文章——希望您已阅读其他文章或已关注我们的在线研讨会。

本文将讨论扩展到多个线程的重要性,以及Vulkan如何实现这一目标。

CPU瓶颈——redux

现代CPU有多个内核,在此我不过多讨论。为了获得特定CPU的最佳性能,通过多线程来充分利用这些额外的内核至关重要。如果不有效利用这些内核,你会局限于单个内核的性能——很多性能(效率)不能被合理利用。之前,我编写了相关代码,即GPU在CPU上保持无期限等待状态,以此作为具有高CPU消耗的低效API函数。但即使高消耗,这一问题也可能通过传播到多个线程而有所缓和。

如果您细看我们Gnome Horde 演示上的CPU图表,您会发现即便是相同的内容,OpenGL ES的峰值仍会高很多。内核0和内核1在不断消耗,性能也大为降低。但有四个Intel CPU内核在Nexus player上可用,且其中两个似乎处于备用闲置状态。实际上,确有三个内核在给定时间内处于闲置状态。您看到两个内核状态活跃的唯一原因是,操作系统在内核之间进行线程跳跃,以试图保持芯片冷却。

所以不仅是OpenGL ES工作量大,也无法将工作分配给处理器来分担。较老的API版本如OpenGL ES从未扩展至多个软件线程——最多可以做的是在另一个线程上进行资源流处理,如果想进行其他工作,收益将迅速递减。许多高性能应用程序或引擎则确保在渲染线程时工作量最低,且所有其他线程上的逻辑处理则尽可能多——通常会生成其各自形式的命令缓冲区。

使用所有的内核

如果将渲染线程的工作负载分至多个内核,CPU性能则会更佳。从视频大约15秒起,Gnome Horde演示上的每帧画面便开始生成和消失——每秒调用150 k的新画面,加上250k再次使用的画面。在OpenGL ES上,没有什么工作我们可以做。其使用的单个内核已在最大限度地使用。但是Vulkan却可以通过重新分配工作负载至多个线程中,无缝利用所有可用的内核。

同样重要的是,扩展至多线程的工作并未增加工作量。为保证真正的可扩展性,API需要将工作负载分配至在多个线程中,且尽可能不产生额外的消耗。Vulkan便可做到这一点——在Gnome Horde演示中可以看到。

VulkanCPU vs OpenGL ES CPU: 注意OpenGL ES为何不能进行多线程处理

VulkanCPU vs OpenGL ES CPU: 注意OpenGL ES为何不能进行多线程处理

Vulkan扩展机制

Vulkan不仅仅只是扩展到多个内核——这是显式的API。它可以提供所需的工具和机制,以使应用程序按需扩展。这通过简单的设计选择便可实现,且通过提供显式扩展至多线程的对象还可以实现更多。

无全局状态

OpenGL ES绑定到CPU线程时有具体的情境,这并非真正意义上的“全局状态”,但全局足以引起线程问题。在大多数情况下,当你调用函数时,驱动器必须查找绑定到当前线程的情境。这通常需要使用某种形式的线程本地存储,但这并不利于性能,尤其是调用所有函数时!有了OpenGL ES的bind-to-edit模型,大多数函数可能需要进一步查找具体情境以获得当前绑定对象,但由此便可能引起更多的问题。

Vulkan则没有全局状态,或者至少没有可变的全局状态——根本上说,在系统上只有一定数量的GPU,且应用程序只能进行控制操作,这将使应用程序的生命周期保持不变。Vulkan提供给每个函数的对象都是显式对象(而不是通过绑定调用的对象)。且在任何时候,可涉及到可分派对象(如设备),只要其是一个函数的参数。这意味着没有全局查找,也没有锁定全局状态。

外部同步

OpenGL ES的是属性是,只要涉及到CPU,在任何时间修改任何东西线程都是安全的。虽听起来不错,但这意味着无论应用程序是多线程还是非多线程,驱动器都将进入避免竞态条件的圈子。在实践中,这通常意味着任何函数的互斥锁均可引起修改——这是导致OpenGL ES高消耗的另一个原因。驱动器不知道应用程序如何访问状态,至少没有调用启发式算法——所以很难去做任何事,却可以保持保守和锁定任何事。

另一方面,Vulkan可保证同时读取并访问对象和状态。如果线程修改对象,应用程序必须确保彼时此对象没有被访问。通过API设计选择,通常没有必要在任何给定的时间内从多个线程中修改相同的对象。如果应用程序确实需要从多个线程中修改相同的对象,则它必须使用同步机制来避免竞态条件和数据冲突。

在开发时便关注同步问题,意味着应用程序有机会比驱动器运行的更好。在大多数情况下,线程之间的同步可以在设置通信点完成,且可能不需要互斥锁:防止出现大量的闲置时间。

多线程命令的生成

OpenGL ES的命令生成和命令提交毫无区别。当调用“glDraw”,所有当前的状态都将转换供硬件消耗的状态,并提交执行。生成的命令实际上是一个相当耗成本的操作(OpenGL ES的低效率更是让其雪上加霜)。且由于需要将所有提交的命令序列化,因此每次命令的生成需要在线程上完成。

对于Vulkan,生成和提交是两个完全不同的概念。命令首先记录在命令缓冲区对象中,然后再提交给硬件队列。这使应用程序可在其他线程中记录命令缓冲区,且事实上也可在多个线程中记录命令缓冲区,只是提交的CPU操作成本比较低,可以在任何一个线程中完成且影响甚小。

Vulkan

这使得分工更加有效,扩展至多线程的记录不会带来额外的处理成本。

记录成本可能使命令难以扩展——命令缓冲区需要记录内存,但不可能事先了解需要多少内存。在基层分配内存是一个全局性的操作——需要某种形式的锁定且阻塞其他线程。为了缓解这个问题,命令缓冲区使用了几个不同的策略来避免使用系统内存:

•可以重置命令缓冲区,使他们重新记录且不释放任何已分配的内存。如果在单个命令缓冲区分配的大小仍然不变,没有必要每帧均从系统中分配内存,因此只有第一帧需要成本。

•命令池允许一组命令缓冲区有更大的分配,因此如果各命令缓冲的工作负载随着每帧画面发生变化,但每池工作负载并没有真正改变,这便可维持某种程度上的稳定,且每个命令缓冲区分配量不会过大。

更多有关命令缓冲区和命令池的信息,请阅读“Jesse Barker的SIGGRAPH BoF 分配”一文。该文讨论了Vulkan包括对象的变化。有关这些构造是如何运作以及如何真正实现合适的多线程扩展,以后将进行详细讨论。

总结

本文只是大致浅谈——我着重强调了允许线程存在的特性,但这些策略可渗透至整个API。相比OpenGL ES,Vulkan允许更多扩展,即使结束时不使用多线程,至少还有多线程的选项可供选择。

对于开发人员而言,这意味着设备有更多的内核,开发人员可以更好地管理线程,并可进行先前不可能进行的渲染策略。正如我上次提到的,这将使应用程序具有更好的效率和性能,不然会使单一的内核透支!

请关注我们Twitter(@ImaginationPR @PowerVRInsider)上来自PowerVR团队的最新新闻和公告。

想了解更多关于Vulkan的资讯,可注册两个即将开展的研讨会:
• 明确操作和一致的帧像周期
• 显性架构:Vulkan API如何在PowerVR GPU中运行

您还可以阅读我最新的博文“移动设备的高效率”获取更多资讯。

原文链接: http://blog.imgtec.com/powervr/vulkan-scaling-to-multiple-threads

--电子创新网--
粤ICP备12070055号