Intel® FPGA SDK for OpenCL™ Pro Edition: 最佳实践实践指南

ID 683521
日期 9/26/2022
Public
文档目录

7.2. 内核矢量化

要达到较高吞吐量,可以矢量化您的内核。 内核矢量化使得多个work-items以单指令多数据(SIMD)方式执行。 您可以指示 Intel® FPGA SDK for OpenCL™ Offline Compiler将内核中的每个标量操作(例如,加法或乘法)转换为SIMD运算。

在您的内核代码中包含num_simd_work_items属性以指示离线编译器在不修改内核主题的情况下对每个work-item执行更多加法。以下代码片段将向量化因子4应用于原始内核代码:

__attribute__((num_simd_work_items(4)))
__attribute__((reqd_work_group_size(64,1,1)))
__kernel void sum (__global const float * restrict a,
                   __global const float * restrict b,
                   __global float * restrict answer)
{
   size_t gid = get_global_id(0);

   answer[gid] = a[gid] + b[gid];
}

要使用num_simd_work_items属性,您还必须使用reqd_work_group_size属性指定需要的工作组大小。您对reqd_work_group_size指定的工作组大小必须能被您分配给num_simd_work_items的值整除。在上述代码实例中,内核具有64个work-item的固定工作组大小。在每个工作组内,work-item平均分布在4个SIMD矢量lane中。在离线编译器实现四个SIMD矢量lane后,每个work-item现在执行四倍的工作。

离线编译器矢量化代码,并且可能合并存储器访问。您不需要更改任何内核代码或者主机代码,因为离线编译器会自动应用这些优化。

可以手动矢量化您的内核代码,但是必须调整您主机应用程序中的NDRange以反映您实现的矢量化程度。如下实例显示了手动复制内核中的操作时代码的变更情况:

__kernel void sum (__global const float * restrict a,
                   __global const float * restrict b,
                   __global float * restrict answer)
{
   size_t gid = get_global_id(0);

   answer[gid * 4 + 0] = a[gid * 4 + 0] + b[gid * 4 + 0];
   answer[gid * 4 + 1] = a[gid * 4 + 1] + b[gid * 4 + 1];
   answer[gid * 4 + 2] = a[gid * 4 + 2] + b[gid * 4 + 2];
   answer[gid * 4 + 3] = a[gid * 4 + 3] + b[gid * 4 + 3];
}

该形式下,内核加载从数组ab来的四个单元,计算求和,并将计算结果存储到数组answer中。因为FPGA流水线加载并将数据加载到存储器中的相邻位置,您可以手动指示离线编译器来合并每组四个加载和存储应用操作。

注意: 在您实现手动优化后,每个work-item处理的工作量是原来的四倍。因此,主机应用程序必须使用比原始实例小四倍的NDRange。反之,当您采用离线编译器的自动矢量化功能时,您不需要调整NDRange的大小。您可以使用num_simd_work_items属性以最少的代码更改量来调整矢量宽度。