第4A部分:多线程BFP
到目前为止,之前的所有阶段都在单个硬件线程中执行其滤波计算。xcore XS3设备上的每个 Tile 都有8个可用的硬件线程,而迄今为止大部分这些线程都未被使用。在第4A部分中,将并行化应用程序,以便滤波计算跨越多个线程以减少延迟。
第4A部分将采用与第3B部分最相似的块浮点方法。之所以将此阶段建模为第3B部分而不是第3C部分,是因为lib_xcore_math的高级BFP API与这种并行性不兼容。对于这种高级用法,需要使用较低级别的API。
来自lib_xcore_math
本页引用了lib_xcore_math中的以下操作:
实现
第4A部分的实现分为两个文件,part4A.c和stage9.xc。在stage9.xc中,只实现了filter_frame()函数,这是因为在XC中编写它可以利用XC语言的便捷语法,使用par块进行同步并行操作。
实际上,在第4A部分中,唯一与第3B部分不同的函数是filter_frame()。
// 计算整个输出帧
// 在stage9.xc中定义
void filter_frame(
int32_t frame_out[FRAME_SIZE],
exponent_t* frame_out_exp,
headroom_t* frame_out_hr,
const int32_t history_in[HISTORY_SIZE],
const exponent_t history_in_exp,
const headroom_t history_in_hr);
第4A部分的filter_frame()的内部与第3B部分非常相似。在第4A部分,所有的并行性都发生在par块内部。从语义和概念上讲,这与for循环的工作方式非常相似。不同之处在于,当然,每个“迭代”不是按顺序进行,而是在par块中每个“迭代”同时进行。
通常情况下,有多种方法可以引入并行性。例如,一个选项是将并行性下移到filter_sample()中。在这种情况下,每个线程可以计算内积的一部分,然后由主(调用)线程将部分结果相加。
相反,因为我们处理的是数据帧,第4A部分选择让每个工作线程计算不同的输出样本。具体而言,在每次for循环的迭代中,下一个THREADS个输出样本将被计算(注意每次迭代s增加THREADS)。在每次迭代中,par块启动THREADS(4)个线程,每个线程计算一个不同的输出样本。每个线程计算的输出样本的索引基于它获取的tid的值。
请注意,实现并行性的另一种方式是为每个工作线程分配一组输出样本的_范围_,每个线程在其分配的输出样本范围内进行内部迭代。实际上,这种方式很可能更好,因为启动和同步线程组的开销较小(每帧执行一次而不是64次)。然而,timer_start()和timer_stop()不是线程安全的,所以这会破坏我们的性能信息。
结果
下面的示例计时信息是每四个样本的计时
计时
| 计时类型 | 测量时间 |
|---|---|
| 每个滤波器系数 | 15.05 ns |
| 每个输出样本 | 15413.09 ns |
| 每帧 | 1033652.88 ns |
输出波形
