X86平台AMD CCP外设寄存器访问过慢?DPDK cryptodev优化咨询
X86平台外设寄存器访问性能优化方案
问题背景
在DPDK环境下调用AMD cryptodev(对应CCP硬件加速模块)排查性能瓶颈时,发现驱动链路中的寄存器读写操作占了很高的耗时比例。
最初采用单步clock_gettime的测时逻辑,估算单次寄存器读操作耗时约4us,对应代码如下:
struct timespec time_start = { 0, 0 }, time_end = { 0, 0 }; clock_gettime(CLOCK_REALTIME, &time_start); printf("start time %lus,%lu ns\n", time_start.tv_sec, time_start.tv_nsec); cur_head_offset = CCP_READ_REG(b_info->cmd_q->reg_base, CMD_Q_HEAD_LO_BASE); clock_gettime(CLOCK_REALTIME, &time_end); printf("end time %lus,%lu ns\n", time_end.tv_sec, time_end.tv_nsec); printf("duration:%lds %ldns\n", time_end.tv_sec-time_start.tv_sec, time_end.tv_nsec-time_start.tv_nsec);
该段代码的实际运行输出为:
start time 1616032196s,470511856 ns end time 1616032196s,470516756 ns duration:0s 4900ns start time 1616032196s,470523966 ns end time 1616032196s,470527946 ns duration:0s 3980ns
经提醒修正测试方法后,改为循环执行100万次寄存器访问、搭配DPDK原生HPET高精度时钟统计平均耗时,最终得到的单次访问平均耗时约565ns,排除了测时流程本身的开销干扰,结果符合硬件访问的预期水平。
可落地的优化方向
- 先把测量做准,排除测时本身的开销干扰
单步测时不要在两次计时间隔里只插入一次寄存器访问,clock_gettime属于用户态陷入内核的系统调用,固定开销就有数百纳秒,计算出来的耗时根本不能代表寄存器访问本身的速度。正确的测量方式是循环执行上百万次访问、统计总时长计算平均值,搭配DPDK提供的用户态TSC、HPET时钟源,全程不触发系统调用,得到的结果才是硬件的真实性能。 - 调整寄存器映射的内存属性
外设寄存器通过内存映射映射到CPU地址空间时,默认采用最严格的不可缓存(UC)属性,这种属性下每次寄存器访问都会强制CPU做全链路的序保序、缓存一致性同步,本身开销就高。可以根据寄存器的读写特性调整映射属性:比如只读的队列状态寄存器,可以换成写合并(WC)属性,去掉不必要的序保序开销;对有写时序要求的控制类寄存器,保留UC属性保证操作可靠性。 - 减少无效的寄存器访问频次
轮询队列状态是最常见的寄存器高频访问场景,完全没必要每处理一个报文就读一次硬件寄存器:可以把硬件返回的队列状态缓存在CPU本地变量里,每处理固定数量的报文(比如16个)再读一次硬件值做同步,大部分场景下这种方式不会影响队列的实时性,还能把寄存器访问的频次压到原来的几十分之一。业务空闲时可以切到中断驱动模式,彻底避免轮询带来的CPU空耗。 - 降低编译与屏障操作的额外开销
寄存器读写的实现必须用volatile修饰指针,防止编译器把连续的寄存器访问合并、或者直接判定为无价值操作删掉;同时不要在每次寄存器访问前后都插入全内存屏障,根据X86架构的强内存模型,大部分场景下只需要在跨队列同步的节点加必要的轻量屏障就够了,全屏障本身会带来数百纳秒的额外开销。
修正后的批量测时代码参考
uint64_t start, end; start = rte_get_hpet_cycles(); for (i = 0;i < 1000000; ++i){ cur_head_offset = CCP_READ_REG(b_info->cmd_q->reg_base, CMD_Q_HEAD_LO_BASE); } end = rte_get_hpet_cycles(); printf("start : %lu %lu\n", start, rte_get_hpet_hz()); printf("end : %lu %lu\n", end, rte_get_hpet_hz()); double time = (double)(end - start)/(double)rte_get_hpet_hz(); printf("Average per operation %lf ns\n", time / 1000000 * 1000 * 1000 * 1000);对应实测输出
start : 2301456683 14318180 end : 2309549122 14318180 Average per operation 565.186288 ns
内容的提问来源于stack exchange,提问作者xisme
相关产品推荐
相关产品推荐

