初学者也能看懂的DPDK解析( 三 )


SIMD在游戏后台比较常见 , 但是其他业务如果有类似批量处理的场景 , 要提高性能 , 也可看看能否满足 。
4. 不使用慢速API
这里需要重新定义一下慢速API , 比如说gettimeofday , 虽然在64位下通过vDSO已经不需要陷入内核态 , 只是一个纯内存访问 , 每秒也能达到几千万的级别 。但是 , 不要忘记了我们在10GE下 , 每秒的处理能力就要达到几千万 。所以即使是gettimeofday也属于慢速API 。DPDK提供Cycles接口 , 例如rte_get_tsc_cycles接口 , 基于HPET或TSC实现 。
在x86-64下使用RDTSC指令 , 直接从寄存器读取 , 需要输入2个参数 , 比较常见的实现:
static inline uint64_trte_rdtsc(void){ uint32_t lo, hi; __asm__ __volatile__ ( "rdtsc" : "=a"(lo), "=d"(hi) ); return ((unsigned long long)lo) | (((unsigned long long)hi) << 32);}这么写逻辑没错 , 但是还不够极致 , 还涉及到2次位运算才能得到结果 , 我们看看DPDK是怎么实现:
static inline uint64_trte_rdtsc(void){ union { uint64_t tsc_64; struct { uint32_t lo_32; uint32_t hi_32; }; } tsc; asm volatile("rdtsc" : "=a" (tsc.lo_32), "=d" (tsc.hi_32)); return tsc.tsc_64;}巧妙的利用C的union共享内存 , 直接赋值 , 减少了不必要的运算 。但是使用tsc有些问题需要面对和解决
1) CPU亲和性 , 解决多核跳动不精确的问题
2) 内存屏障 , 解决乱序执行不精确的问题
3) 禁止降频和禁止Intel Turbo Boost , 固定CPU频率 , 解决频率变化带来的失准问题
5. 编译执行优化
1) 分支预测
现代CPU通过pipeline、superscalar提高并行处理能力 , 为了进一步发挥并行能力会做分支预测 , 提升CPU的并行能力 。遇到分支时判断可能进入哪个分支 , 提前处理该分支的代码 , 预先做指令读取编码读取寄存器等 , 预测失败则预处理全部丢弃 。我们开发业务有时候会非常清楚这个分支是true还是false , 那就可以通过人工干预生成更紧凑的代码提示CPU分支预测成功率 。
#pragma once#if !__GLIBC_PREREQ(2, 3)# if !define __builtin_expect# define __builtin_expect(x, expected_value) (x)# endif#endif#if !defined(likely)#define likely(x) (__builtin_expect(!!(x), 1))#endif#if !defined(unlikely)#define unlikely(x) (__builtin_expect(!!(x), 0))#endif2) CPU Cache预取
Cache Miss的代价非常高 , 回内存读需要65纳秒 , 可以将即将访问的数据主动推送的CPU Cache进行优化 。比较典型的场景是链表的遍历 , 链表的下一节点都是随机内存地址 , 所以CPU肯定是无法自动预加载的 。但是我们在处理本节点时 , 可以通过CPU指令将下一个节点推送到Cache里 。
API文档:https://doc.dpdk.org/api/rte__prefetch_8h.html
static inline void rte_prefetch0(const volatile void *p){ asm volatile ("prefetcht0 %[p]" : : [p] "m" (*(const volatile char *)p));}#if !defined(prefetch)#define prefetch(x) __builtin_prefetch(x)#endif…等等
3) 内存对齐
内存对齐有2个好处:
l 避免结构体成员跨Cache Line , 需2次读取才能合并到寄存器中 , 降低性能 。结构体成员需从大到小排序和以及强制对齐 。参考《Data alignment: Straighten up and fly right》
#define __rte_packed __attribute__((__packed__))l 多线程场景下写产生False sharing , 造成Cache Miss , 结构体按Cache Line对齐
#ifndef CACHE_LINE_SIZE#define CACHE_LINE_SIZE 64#endif#ifndef aligined#define aligined(a) __attribute__((__aligned__(a)))#endif4) 常量优化
常量相关的运算的编译阶段完成 。比如C++11引入了constexp , 比如可以使用GCC的__builtin_constant_p来判断值是否常量 , 然后对常量进行编译时得出结果 。举例网络序主机序转换
#define rte_bswap32(x) ((uint32_t)(__builtin_constant_p(x) ?rte_constant_bswap32(x) :rte_arch_bswap32(x)))其中rte_constant_bswap32的实现
#define RTE_STATIC_BSWAP32(v)((((uint32_t)(v) & UINT32_C(0x000000ff)) << 24) |(((uint32_t)(v) & UINT32_C(0x0000ff00)) << 8) |(((uint32_t)(v) & UINT32_C(0x00ff0000)) >> 8) |(((uint32_t)(v) & UINT32_C(0xff000000)) >> 24))5)使用CPU指令
现代CPU提供很多指令可直接完成常见功能 , 比如大小端转换 , x86有bswap指令直接支持了 。
static inline uint64_t rte_arch_bswap64(uint64_t _x){ register uint64_t x = _x; asm volatile ("bswap %[x]" : [x] "+r" (x) ); return x;}


推荐阅读