DPDK的编程规范,用来指导如何写高性能程序,觉得挺有意思的,所以翻译过来。
内存
Memory Copy
不要在数据面上使用 libc 函数,例如 memcpy 和 strcpy 等,推荐用 rte_memcpy
这样的优化实现,实际上就是 SIMD 的版本。
为了使用 SIMD,需要保证地址不是虚拟地址,例如 malloc 等产生的是虚拟地址,那么在物理地址上可能是分散的,就做不了 SIMD 优化。
下面介绍 rte_memcpy
,它要求地址不连续。
rte_mov15_or_less 处理小内存
下面这段代码处理小内存的复制。这里压根不考虑内存对齐了,直接复制。
- 如果
n & 8
,就一次性复制头 64 bit。 - 如果剩下来的还满足
n & 4
,就一次性复制下面 32 bit。 - 由此类推。
1 | static __rte_always_inline void * |
AVX512 实现
首先这里使用的都是带 u,也就是非对齐的版本。其中:
- 16 byte 数据对应 128 bit,对应一个 xmm 寄存器。
- 32 byte 数据对应 256 bit,对应一个 ymm 寄存器。
- 64 byte 数据对应 512 bit,对应一个 zmm 寄存器。
注意,这里是 64 byte 而不是 64 bit,64 bit 等于普通寄存器了。
1 | /** |
下面是逐 128 byte 搬数据。
1 | /** |
下面是逐 512 byte 搬数据。这个是用在对齐逻辑上的,不知道为什么还带 u。
1 | /** |
下面来看 rte_memcpy_generic
。
首先是处理非对齐的部分:
- 如果长度小于 16 bytes,走 rte_mov15_or_less
- 如果长度小于等于 32 bytes,走两次 rte_mov16
令 n=17,则
第一次从 src[0..16] 复制到 dst[0..16]。
第二次从 src[1..17] 复制到 dst[1..17]。 - 如果长度小于等于 64 bytes,类似上面。
- 如果长度小于等于 512 bytes,则是一个类似于 rte_mov15_or_less 的实现
但是在剩余长度小于 128 bytes 后,如果大于 64 bytes,就走两次 rte_mov64 解决战斗。
如果小于 64 bytes,就走一次 rte_mov64 解决战斗。
如果需要复制的长度大于 512 bytes,就需要处理对齐的部分。这里检查 dst 是否按照 64 bytes 对齐,即 0x3F
对齐。如果不对齐,则先把前面的部分给复制完毕。
【Q】为什么是考虑 dst 对齐而不是是 src 对齐?
1 |
|
Memory Allocation
避免使用 malloc 等在堆上分配内存,毕竟维护堆还是比较麻烦的,CSAPP 的 Data Lab 令我记忆犹新,并且也不容易做 parallel allocation。
更为推荐的做法是对固定大小的对象构建内存池,例如librte_mempool
/rte_malloc
的实现。在这样的实现中需要考虑内存对齐,无锁访问,NUMA感知,批量读写,每个核心的Cache。