使用Intel® FPGA SDK for OpenCL™ 离线编译器,不需要调整kernel代码便可以将其最佳的适应于固定的硬件设备,而是离线编译器会根据kernel的要求自适应调整硬件的结构。

  通常来说,应该先优化针对单个计算单元的kernel,之后累哦通过增加计算单元数量来拓展硬件以填充FPGA其余的部分,从而提升性能。Kernel的使用面积与硬件编译所需要的时间有关,因此为了避免硬件编译时间过长,首先要专注于优化kernel在单个计算单元上的性能。

  要优化kernel的性能,主要包括数据处理以及内存访问优化。

  a. 通过SDK的channel 或pipe来传输数据。为了提高kernel之间的数据传输效率,在kernel程序中使用channel通道拓展。 如果想利用通道功能,又想使用其他SDK运行kernel,则使用OpenCL pipes。

  b. 展开循环。

  c. 优化浮点运算。对于浮点操作,可以手动引导SDK的离线编译器进行优化,从而在硬件中创建更有效的pipeline结构并减少总体硬件使用率。

  d. 分配对齐的内存。再分配与FPGA进行数据传输的主机端存储器时,存储器至少是64字节对齐的。

  e. 使用或不用Padding来对齐结构。

  f. 保持向量元素的相似结构。如果更新了向量的一个元素,那么更新这个向量的所有元素。

  g. 避免指针混淆。尽量在指针参数中插入strict关键字。

  h. 避免开销大的函数/功能。有些函数在FPGA中实现开销很大,可能会减低kernel的性能,或是需要大量硬件来实现。

  i. 避免依赖于work-item id的后向分支。避免在kernel中包括任何与工作项ID相关的向后分支(即,循环中发生的分支),因为这会降低性能。

 1、通过SDK的channel 或pipe来传输数据

  为了提高kernel之间的数据传输效率,在kernel程序中使用channel通道拓展。 如果想利用通道功能,又想使用其他SDK运行kernel,则使用OpenCL pipes。

  有时,FPGA到global memory全局存储器带宽会限制内核之间的数据传输效率。 理论上FPGA到global memory全局存储器的最大带宽根据目标定制平台和板上可用的全局存储器bank的数量而变化。 要确定主板的理论最大带宽,要参考主板的文档。

  实际上,kernel无法实现最大可用全局内存带宽的100%利用率。 利用率级别取决于算法的访问模式。

  如果全局内存带宽是我们使用OpenCL内核的性能限制,首先尝试将算法分解为多个较小的kernel。 其次,通过在内核之间实现SDK的channel或OpenCL的pipe进行数据传输来消除一些全局内存访问。

(1) Channel与pipe的特性

a. Default Behavior

  Channel默认行为是阻塞的,而pipe的默认行为是非阻塞的(nonblocking)。

b. 多个OpenCL内核的并发执行

  可以同时执行多个OpenCL内核。 要启用并发执行,要修改主机代码以实例化多个命令队列。 每个同时执行的kernel内核都与一个单独的命令队列关联。

pipe的特别注意事项:Intel SDK 中OpenCL的pipe是允许在其他的OpenCL SDK上兼容的,但不能最大化kernel内核吞吐量。OpenCL 2.0中要求在进行pipe读取前先进性pipe写入,以免kernel在空pipe中读取数据,因此kernel无法同时运行。由于Intel SDK支持并发执行,可以修改主机应用程序以及kernel程序来实现并发执行,从而提高吞吐量。但不能将kernel移植到其他SDK上。

  要启用并发执行包含pipe的内核,需要将内核代码中的depth属性替换为blocking属性(即__attribute __((blocking)))。 blocking属性在read_pipe和write_pipe函数调用时引入blocking行为。 调用点将阻止内核执行,直到管道的另一端准备好为止。

  如果同时将blocking属性和depth属性添加到内核,则当管道为空时,read_pipe仅调用一个块,而当管道为满时,write_pipe仅调用一个块。 blocking行为会导致内核之间的隐式同步,从而使得内核之间互锁。

c. 隐式内核Kernel同步

  通过blocking channel调用以及blocking pipe调用来隐式同步kernel。

channel int c0; 

__kernel
void producer (__global int * in_buf) 
{ 
  for (int i = 0; i < 10; i++) 
  {   
    write_channel_intel (c0, in_buf[i]);
  } 
}

__kernel
void consumer (__global int * ret_buf) 
{                 
  for (int i = 0; i < 10; i++) 
  { 
    ret_buf[i] = read_channel_intel(c0);
  }
} 
Kernels with Blocking Channel Call

相关文章:

  • 2021-06-03
  • 2021-05-21
  • 2021-10-08
  • 2022-01-22
  • 2022-02-11
  • 2022-01-05
  • 2022-01-11
猜你喜欢
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-10-01
相关资源
相似解决方案