DPDK20.05 – rte_memcpy函数
DPDK中,rte_memcpy 函数是一个非常典型的 DPDK 利用其SIMD指令,对常规函数进行优化的一个示例。
x86版本的实现在 dpdk-20.05/lib/librte_eal/x86/include/rte_memcpy.h 文件中实现。
1、rte_memcpy()
1 2 3 4 5 6 7 8 | static __rte_always_inline void *rte_memcpy(void *dst, const void *src, size_t n) { /* 如果源地址与目的地址都是地址对齐的,则使用 rte_memcpy_aligned() 函数 */ if (!(((uintptr_t)dst | (uintptr_t)src) & ALIGNMENT_MASK)) return rte_memcpy_aligned(dst, src, n); else return rte_memcpy_generic(dst, src, n); } |
ALIGNMENT_MASK 宏定义的值,根据CPU的不同而不同。
对于支持到 AVX512 指令的CPU,ALIGNMENT_MASK 的值定义为 0x3F,即64字节对齐。
对于支持到 AVX2 指令的CPU,ALIGNMENT_MASK 的值定义为 0x1F,即32字节对齐。
其余的所有CPU,ALIGNMENT_MASK 的值定义为 0x0F,即16字节对齐。
2、rte_mov16()、rte_mov32、rte_mov64、rte_mov128与rte_mov128blocks
由于 Intel CPU 对 SSE、AVX等 SIMD指令的支持,使每次最多处理的数据超过64字节成为可能。
以目前最常见的支持 AVX2 指令较多的CPU为例,该指令集最高支持256位的宽指令。
2.1 rte_mov16()
该函数用来拷贝16字节(128位)数据,且数据的源空间与目的空间不能有重叠。
1 2 3 4 5 6 7 | static __rte_always_inline void rte_mov16(uint8_t *dst, const uint8_t *src) { __m128i xmm0; xmm0 = _mm_loadu_si128((const __m128i *)src); /* 从源空间加载128位到寄存器 */ _mm_storeu_si128((__m128i *)dst, xmm0); /* 将128位数据保存到目的内存空间 */ } |
2.2 rte_mov32()
该函数用来拷贝32字节(256位)数据,且数据的源空间与目的空间不能有重叠。
1 2 3 4 5 6 7 | static __rte_always_inline void rte_mov32(uint8_t *dst, const uint8_t *src) { __m256i ymm0; ymm0 = _mm256_loadu_si256((const __m256i *)src); /* 从源空间加载256位到寄存器 */ _mm256_storeu_si256((__m256i *)dst, ymm0); /* 将256位数据保存到目的内存空间 */ } |
2.3 rte_mov64()
该函数用来拷贝64字节(512位)数据,且数据的源空间与目的空间不能有重叠。
由于支持到 avx2 指令集的CPU 不支持 512 位宽的指令,因此该函数是将被拷贝数据拆分成两个256位的数据进行拷贝。
1 2 3 4 5 | static __rte_always_inline void rte_mov64(uint8_t *dst, const uint8_t *src) { rte_mov32((uint8_t *)dst + 0 * 32, (const uint8_t *)src + 0 * 32); rte_mov32((uint8_t *)dst + 1 * 32, (const uint8_t *)src + 1 * 32); } |
2.4 rte_mov128()
该函数的实现与 rte_mov64() 类似,只是将数据分成4部分 256位的数据进行拷贝。
数据的源空间与目的空间不能有重叠。
1 2 3 4 5 6 7 | static __rte_always_inline void rte_mov128(uint8_t *dst, const uint8_t *src) { rte_mov32((uint8_t *)dst + 0 * 32, (const uint8_t *)src + 0 * 32); rte_mov32((uint8_t *)dst + 1 * 32, (const uint8_t *)src + 1 * 32); rte_mov32((uint8_t *)dst + 2 * 32, (const uint8_t *)src + 2 * 32); rte_mov32((uint8_t *)dst + 3 * 32, (const uint8_t *)src + 3 * 32); } |
2.5 rte_mov128blocks()
该函数用来拷贝128字节块(1024位)数据,且数据的源空间与目的空间不能有重叠。
不管数据有多少个128字节块,都将数据以 128 字节为一部分进行划分进行数据拷贝。
这128字节中又会被分成4部分,每部分32字节(256位)进行数据拷贝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | static __rte_always_inline void rte_mov128blocks(uint8_t *dst, const uint8_t *src, size_t n) { __m256i ymm0, ymm1, ymm2, ymm3; while (n >= 128) { /* 每次循环拷贝128字节数据 */ ymm0 = _mm256_loadu_si256((const __m256i *)((const uint8_t *)src + 0 * 32)); n -= 128; ymm1 = _mm256_loadu_si256((const __m256i *)((const uint8_t *)src + 1 * 32)); ymm2 = _mm256_loadu_si256((const __m256i *)((const uint8_t *)src + 2 * 32)); ymm3 = _mm256_loadu_si256((const __m256i *)((const uint8_t *)src + 3 * 32)); src = (const uint8_t *)src + 128; _mm256_storeu_si256((__m256i *)((uint8_t *)dst + 0 * 32), ymm0); _mm256_storeu_si256((__m256i *)((uint8_t *)dst + 1 * 32), ymm1); _mm256_storeu_si256((__m256i *)((uint8_t *)dst + 2 * 32), ymm2); _mm256_storeu_si256((__m256i *)((uint8_t *)dst + 3 * 32), ymm3); dst = (uint8_t *)dst + 128; } } |
3、rte_memcpy_aligned()
如果要拷贝的源地址和目的地址都是对 ALIGNMENT_MASK 对齐的,则会调用该函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | static __rte_always_inline void *rte_memcpy_aligned(void *dst, const void *src, size_t n) { void *ret = dst; /* 如果要拷贝的数据小于16字节,会对数据进行分段,再对每一段进行拷贝 */ /* 最多分成了 4 段,即最多 4 次即可拷贝完成 */ if (n < 16) { if (n & 0x01) { *(uint8_t *)dst = *(const uint8_t *)src; src = (const uint8_t *)src + 1; dst = (uint8_t *)dst + 1; } if (n & 0x02) { *(uint16_t *)dst = *(const uint16_t *)src; src = (const uint16_t *)src + 1; dst = (uint16_t *)dst + 1; } if (n & 0x04) { *(uint32_t *)dst = *(const uint32_t *)src; src = (const uint32_t *)src + 1; dst = (uint32_t *)dst + 1; } if (n & 0x08) *(uint64_t *)dst = *(const uint64_t *)src; return ret; } /* 如果要拷贝的数据 大于等于 16字节,且不大于32字节,则将数据分成两段, * 前16字节(即128位)是一段,用一个 128 位宽的 SIMD指定完成; * 剩余的数据长度是一段,也用一个 128 位宽的 SIMD指定完成; */ if (n <= 32) { rte_mov16((uint8_t *)dst, (const uint8_t *)src); rte_mov16((uint8_t *)dst - 16 + n, (const uint8_t *)src - 16 + n); return ret; } /* 如果要拷贝的数据大于 32 字节,不超过 64 字节,将数据分成 2 部分, * 前 32 字节(即256位)是一段,用一个 256 位宽的 SIMD 指令完成; * 剩余的数据长度是一段,也用一个 256 位宽的 SIMD 指令完成 */ if (n <= 64) { rte_mov32((uint8_t *)dst, (const uint8_t *)src); rte_mov32((uint8_t *)dst - 32 + n, (const uint8_t *)src - 32 + n); return ret; } /* 如果要拷贝的数据不小于 64 字节,则将数据拆分成 64 字节(256位)为一段进行拷贝 */ for (; n >= 64; n -= 64) { rte_mov64((uint8_t *)dst, (const uint8_t *)src); dst = (uint8_t *)dst + 64; src = (const uint8_t *)src + 64; } /* 最后不管剩余多少(少于64字节),都用256位宽的 SIMD 指令完成 */ rte_mov64((uint8_t *)dst - 64 + n, (const uint8_t *)src - 64 + n); return ret; } |
4、rte_memcpy_generic()
如果要拷贝的源地址或目的地址没有对 ALIGNMENT_MASK 对齐,则会调用该函数进行数据拷贝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | static __rte_always_inline void *rte_memcpy_generic(void *dst, const void *src, size_t n) { uintptr_t dstu = (uintptr_t)dst; uintptr_t srcu = (uintptr_t)src; void *ret = dst; size_t dstofss; size_t bits; /* 如果要拷贝的数据小于16字节,会对数据进行分段,再对每一段进行拷贝 */ /* 最多分成了 4 段,即最多 4 次即可拷贝完成 */ if (n < 16) { if (n & 0x01) { *(uint8_t *)dstu = *(const uint8_t *)srcu; srcu = (uintptr_t)((const uint8_t *)srcu + 1); dstu = (uintptr_t)((uint8_t *)dstu + 1); } if (n & 0x02) { *(uint16_t *)dstu = *(const uint16_t *)srcu; srcu = (uintptr_t)((const uint16_t *)srcu + 1); dstu = (uintptr_t)((uint16_t *)dstu + 1); } if (n & 0x04) { *(uint32_t *)dstu = *(const uint32_t *)srcu; srcu = (uintptr_t)((const uint32_t *)srcu + 1); dstu = (uintptr_t)((uint32_t *)dstu + 1); } if (n & 0x08) { *(uint64_t *)dstu = *(const uint64_t *)srcu; } return ret; } /** 如果要拷贝的数据不少于16字节,不超过32字节,则将数据分成两段拷贝, * 第一次拷贝前16字节,第二次拷贝剩余的部分 */ if (n <= 32) { rte_mov16((uint8_t *)dst, (const uint8_t *)src); /* 第一次拷贝前16字节 */ rte_mov16((uint8_t *)dst - 16 + n, (const uint8_t *)src - 16 + n); /* 第二次拷贝剩余的部分 */ return ret; } if (n <= 48) { /* 如果数据长度大于32 字节,不超过 48 字节,则将数据分成三段拷贝 */ rte_mov16((uint8_t *)dst, (const uint8_t *)src); rte_mov16((uint8_t *)dst + 16, (const uint8_t *)src + 16); rte_mov16((uint8_t *)dst - 16 + n, (const uint8_t *)src - 16 + n); return ret; } if (n <= 64) { /* 如果数据长度大于48字节,不超过64字节,则将数据分成两段拷贝 */ rte_mov32((uint8_t *)dst, (const uint8_t *)src); /* 第一次拷贝32字节,256 位 */ rte_mov32((uint8_t *)dst - 32 + n, (const uint8_t *)src - 32 + n); /* 第二次拷贝剩余的部分 */ return ret; } if (n <= 256) { /* 如果要拷贝的数据长度大于64字节不超过 256字节,将将数据分成多部分拷贝 */ if (n >= 128) { /* 如果数据不小于128字节,则对于128字节内的部分,直接调用 rte_mov128() */ n -= 128; rte_mov128((uint8_t *)dst, (const uint8_t *)src); src = (const uint8_t *)src + 128; dst = (uint8_t *)dst + 128; } COPY_BLOCK_128_BACK31: if (n >= 64) { /* 如果剩余的部分不小于64字节,则对于剩余的64字节内的部分调用rte_mov64() */ n -= 64; rte_mov64((uint8_t *)dst, (const uint8_t *)src); src = (const uint8_t *)src + 64; dst = (uint8_t *)dst + 64; } if (n > 32) { /* 如果剩余的部分大于32字节,则将数据再次分段 */ rte_mov32((uint8_t *)dst, (const uint8_t *)src); rte_mov32((uint8_t *)dst - 32 + n, (const uint8_t *)src - 32 + n); return ret; } if (n > 0) { /* 如果还有剩余,再调用rte_mov32() 执行一次拷贝 */ rte_mov32((uint8_t *)dst - 32 + n, (const uint8_t *)src - 32 + n); } return ret; } /** 如果要拷贝的数据超过256字节 */ dstofss = (uintptr_t)dst & 0x1F; if (dstofss > 0) { /* 如果目的地址没有32字节对齐,则先将其对齐,以使 store 操作对齐 */ /* 前面的掩码部分得到的是32字节里,非数据的部分,其余的才是要拷贝的部分 */ dstofss = 32 - dstofss; n -= dstofss; rte_mov32((uint8_t *)dst, (const uint8_t *)src); src = (const uint8_t *)src + dstofss; dst = (uint8_t *)dst + dstofss; } /** 以128字节块为单位进行拷贝,最后多出来的部分由下面的代码处理 */ rte_mov128blocks((uint8_t *)dst, (const uint8_t *)src, n); /* 移动 src 与 dst,使其移动到还没被拷贝的位置 */ bits = n; /* 暂存 */ n = n & 127; /* 127 即 0x3F,剩余的部分肯定不足128字节 */ bits -= n; /* 得到剩余的字节所在的偏移 */ src = (const uint8_t *)src + bits; dst = (uint8_t *)dst + bits; /** 拷贝剩余的部分(不足128字节) */ goto COPY_BLOCK_128_BACK31; } |
————————————————————
原创文章,转载请注明: 转载自孙希栋的博客
本文链接地址: 《DPDK20.05 – rte_memcpy函数》
/* 最后不管剩余多少(少于64字节),都用256位宽的 SIMD 指令完成 */
rte_mov64((uint8_t *)dst – 64 + n, (const uint8_t *)src – 64 + n);
请教一下,关于这部分,为什么不会导致内存被踩踏,在这种情况下mov62,一定会出现读取超过src有效地址的部分数据,覆写到dst尾部 没有找到相关的资料说明