生产者使用者范例 - 2021.2 Chinese

Vitis 高层次综合用户指南 (UG1399)

Document ID
UG1399
Release Date
2021-12-15
Version
2021.2 Chinese

请考虑软件设计师编写多线程程序的方式,通常有一个主线程用于执行某些初始化步骤,随后分叉为多个子线程用于执行某些并行计算,当所有并行计算都完成后,主线程会整理结果并写入输出。程序员必须理清哪些部分可以分叉以供并行计算,哪些部分需要按顺序执行。这种分叉/连接类型的并行化操作不仅适用于 CPU,也适用于 FPGA,但 FPGA 上的吞吐量的关键模式之一是生产者使用者范例。您需要将生产者使用者范例应用于顺序程序,并将其转换为可并行执行的抽取功能以便提升性能。

您可借助一条简单的问题语句的帮助来更好地理解这个分解进程。假定您有一份数据手册,可供我们将其中的项导入列表。随后,您将对列表中的每个项进行处理。处理完每个项需耗时约 2 秒。处理完后,您将把结果写入另一份数据手册,此操作将耗时约每项各 1 秒。因此,如果输入 Excel 工作表中有总计 100 个项,那么将耗时总计 300 秒来生成输出。这样做的目的是对此问题进行分解,以便您识别能够并行执行的任务,从而提升系统吞吐量。

图 1. 程序工作流程

第一步是了解程序工作流程,识别独立的任务或函数。整个工作流程分 4 个步骤,类似上图所示的程序工作流程(无重叠)。在此示例中,“写入输出”(步骤 3)任务独立于“处理数据”(步骤 2)处理任务。虽然步骤 3 取决于步骤 2 的输出,但当步骤 2 中的任意项完成处理后,您即可立即将其写入输出文件。您无需等待所有数据都完成处理后再开始将数据写入输出文件。此类型的交织/重叠任务执行方式是非常常见的原则。如上图所示(例如:含重叠的程序工作流程)。如图所示,含重叠的工作流程比不含重叠时更快完成。现在,您可将步骤 2 视作为生产者,将步骤 3 视作为使用者。生产者使用者模式对于 CPU 性能的影响有限。您可交织执行每个线程的步骤,但这需要谨慎分析,以便充分利用底层多线程和 L1 高速缓存架构,因此较为耗时。但在 FPGA 上,由于可采用定制架构,生产者和使用者线程可以同时执行,开销极低甚至没有,因此能够显著提升吞吐量。

首先考量的最简单案例是单一生产者和单一使用者通过大小有限的缓冲器来进行通信。如果缓冲器已满,那么生产者可以选择阻塞/停滞或者丢弃数据。当使用者从缓冲器中移除某个项后,它会通知生产者,生产者随后开始再次填充缓冲器。如果使用者发现缓冲器已空,则可以同样方式停滞。当生产者将数据置入缓冲器后,它会唤醒休眠中的使用者。该解决方案可通过进程间通信(通常使用监控器或信号量)来实现。不充分的解决方案可能导致死锁,即两个进程都停滞并等待唤醒。但在单一生产者和使用者的情况下,通信模式与先入先出 (FIFO) 或乒乓缓冲器 (PIPO) 实现之间存在强映射关系。这种类型的通道无需依赖信号量、互斥体或监控器来进行数据传输,即可提供高效的数据通信。使用此类锁定原语对于性能可能开销较大,并且难以使用和调试。PIPO 和 FIFO 是常用的选择,因为可以避免端到端的原子同步需求。

在此类宏观级别架构最优化中,可通过缓冲器封装通信,使程序员可免于担心存储器模型和其它非确定性行为(如争用条件等)。在此类设计中可达成的网络类型为纯粹的“数据流网络”,可在输入侧接受串流数据,对此数据串流执行一些基本处理,然后将其作为数据串流发出。并行程序的复杂性完全被抽离。请注意,“导入数据”(步骤 1)和“导出数据”(步骤 4)同样在最大程度提升可用的并行性方面扮演着相应的角色。为了使计算能够与 I/O 成功重叠,重要的是第一步对输入的读取结果进行封装,最后一步则是写入输出。这样即可实现 I/O 与计算的最大程度重叠。在计算步骤中间读取或写入输入/输出端口将会限制设计的可用并发性。这同样是在对设计工作流程进行设计时需要牢记的。

最后,此类“数据流网络”的性能依赖于设计师能够持续向网络馈送数据,使数据能够在系统中保持串流。数据流中出现中断可能导致性能下降。视频串流应用就是一个很好的例子,比如在线游戏中,实时高清 (HD) 视频持续流经系统,帧处理率受到持续监控,以确保满足期望的结果质量。游戏玩家可以在屏幕上立即观察到帧处理率下降。假想一下,这能为一大群游戏玩家提供持续性帧率支持,同时功耗相比传统 CPU 或 GPU 架构显著降低 - 这就是硬件加速的魅力。使数据在生产者与使用者之间持续保持流动至关重要。下一步您将深入了解本节中介绍的这种串流范例。