目录
1. 简介
2. 代码解析
2.1 原始代码
2.2 优化后
2.3 分析优化措施
3. 总结
1. 简介
在Vitis HLS中,实现II(迭代间隔)= 1是提高循环执行效率的关键。II=1意味着每个时钟周期都可以开始一个新的迭代,这是最理想的情况,可以大大提高硬件执行的并行度和速度。
本文对两个示例进行分析,针对存在的存储器访问瓶颈进行优化,以实现迭代间隔为1的目标。随后将详细解析优化后的代码,探讨优化措施对性能的影响。
2. 代码解析
2.1 原始代码
存储器访问存在瓶颈
#include "ap_int.h"ap_int<10> example(ap_int<7> mem[128]) {ap_int<10> sum = 0;int i;SUM_LOOP:for (i = 2; i < 128; ++i)sum += mem[i] + mem[i - 1] + mem[i - 2];return sum;
}
综合报告:
+ Performance & Resource Estimates: PS: '+' for module; 'o' for loop; '*' for dataflow+-------------+------+------+---------+-----------+----------+---------+------+----------+------+----+----------+-----------+-----+| Modules | Issue| | Latency | Latency | Iteration| | Trip | | | | | | || & Loops | Type | Slack| (cycles)| (ns) | Latency | Interval| Count| Pipelined| BRAM | DSP| FF | LUT | URAM|+-------------+------+------+---------+-----------+----------+---------+------+----------+------+----+----------+-----------+-----+|+ example | -| 4.64| 254| 2.540e+03| -| 255| -| no| -| -| 39 (~0%)| 211 (~0%)| -|| o SUM_LOOP | II| 7.30| 252| 2.520e+03| 3| 2| 126| yes| -| -| -| -| -|+-------------+------+------+---------+-----------+----------+---------+------+----------+------+----+----------+-----------+-----+
- 函数总延迟:254 cycles
- 循环总延迟:252 cycles
- 没有使用 BRAM 或者 URAM,通过 fabric 实现存储器
此段原始代码中,每次循环迭代都会从 mem 数组中读取三次数据(mem[i]、mem[i-1]和mem[i-2]),这些重复的内存访问会增加访问延迟。
2.2 优化后
实现II(迭代间隔)= 1。
#include "ap_int.h"ap_int<10> example(ap_int<7> mem[128]) {ap_int<7> tmp0, tmp1, tmp2;ap_int<10> sum = 0;int i;tmp0 = mem[0];tmp1 = mem[1];
SUM_LOOP:for (i = 2; i < 128; i++) {tmp2 = mem[i];sum += tmp2 + tmp1 + tmp0;tmp0 = tmp1;tmp1 = tmp2;}return sum;
}
综合报告:
+ Performance & Resource Estimates: PS: '+' for module; 'o' for loop; '*' for dataflow+------------------------------+------+------+---------+-----------+----------+---------+------+----------+------+----+----------+-----------+-----+| Modules | Issue| | Latency | Latency | Iteration| | Trip | | | | | | || & Loops | Type | Slack| (cycles)| (ns) | Latency | Interval| Count| Pipelined| BRAM | DSP| FF | LUT | URAM|+------------------------------+------+------+---------+-----------+----------+---------+------+----------+------+----+----------+-----------+-----+|+ example | -| 3.87| 133| 1.330e+03| -| 134| -| no| -| -| 57 (~0%)| 198 (~0%)| -|| + example_Pipeline_SUM_LOOP | -| 3.87| 129| 1.290e+03| -| 129| -| no| -| -| 37 (~0%)| 139 (~0%)| -|| o SUM_LOOP | -| 7.30| 127| 1.270e+03| 2| 1| 126| yes| -| -| -| -| -|+------------------------------+------+------+---------+-----------+----------+---------+------+----------+------+----+----------+-----------+-----+
- 函数总延迟:133 cycles
- 循环总延迟:129 cycles
- 没有使用 BRAM 或者 URAM,通过 fabric 实现存储器
通过引入三个临时变量tmp0、tmp1、tmp2来减少冗余的内存访问。这三个变量用于存储当前及之前两次迭代访问的mem数组元素。在每次循环迭代中,只需要读取一次新的数组元素(tmp2 = mem[i]),然后将这个新读取的值与之前存储的两个值(tmp1和tmp0)相加。然后更新tmp0和tmp1的值,使它们分别变为前一次和当前迭代的值。
在循环内每次只进行一次读取操作。
2.3 分析优化措施
在对原始代码进行优化时,核心目标是改善内存访问模式以提高性能。原始代码中,每次循环迭代需要进行三次内存访问,这导致了存储器访问成为性能的瓶颈。为了解决这一问题,优化后的代码引入了临时变量tmp0、tmp1和tmp2,将前两次迭代的数据存储在这些变量中,从而避免了不必要的重复内存访问。
对于此类问题,可以总结出以下优化原则:
减少内存访问: 通过引入临时变量,优化后的代码显著减少了对存储器的访问次数。现在,每次迭代只需进行一次内存访问,大大降低了访问延迟,提高了数据访问效率。
优化数据重用: 引入临时变量tmp0、tmp1和tmp2使得前两次迭代的数据得以重复利用。这种数据重用策略增强了数据访问的局部性,减少了存储器的频繁访问,有助于进一步提高性能。
改进迭代间隔: 优化后的代码不仅减少了存储器访问次数,还改善了内存访问模式,使得循环执行效率得到了显著提高。这种改进有助于实现迭代间隔II=1,即每个时钟周期开始一个新的迭代,从而进一步提高了整体代码的性能表现。
3. 总结
通过对原始代码进行优化,成功地改善了内存访问模式,提高了循环执行效率。优化后的代码减少了存储器访问次数,优化了数据重用,以及改进了迭代间隔,使得每个时钟周期都可以开始一个新的迭代。这些优化措施有效地减少了存储器访问延迟,提高了硬件执行效率。优化后的代码在性能和效率上都有了显著的提升,更适用于高性能处理应用场景。