请看《深入浅出DPDK》
这里举例一些书中提到的优化技术:
内存对齐:CPU cache 是以 cache line 为单位的。一次读取一个cache line的数据进入缓存。当多个CPU内核同时访问一个cache line中的数据时会产生伪共享(False Sharing),产生性能损失。C++中声明对齐变量可以使用 alignas(n) ,即以n字节对齐。并行编程中尽量避免同时访问一个cache line中的资源。
SIMD:Single-Instruction Multiple-Data(单指令多数据)的缩写,一个指令执行多个操作。在C++中 可以使用intrinsics 也可以使用编译器的自动向量化 ,OpenMP 的pragma omp simd提示 gcc和clang编译器都支持。
这是一段测试代码:
#include <iostream> using namespace std; alignas(32) int aa[64]; alignas(32) int bb[64]; alignas(32) int cc[64]; #pragma omp declare simd aligned(a,b:32) void add(const int*__restrict__ a,const int*__restrict__ b,int*__restrict__ c,int n) { #pragma omp simd aligned(a,b:32) for(int i=0;i<n;i++) { c[i]=a[i]+b[i]; } } int main(int argc,char* argv[]) { int count; cin>>count; for(int i=0;i<64;i++) { aa[i]=i+1; bb[i]=i+count; } add(aa,bb,cc,64); for(int i=0;i<64;i++) { cout<<cc[i]; } return 0; }
使用clang编译输出汇编文件:
clang -O3 -S -o testomp.S -march=core-avx2 -fopenmp testomp.cpp
main函数的汇编如下:
main: # @main .seh_proc main # %bb.0: pushq %rsi .seh_pushreg 6 pushq %rdi .seh_pushreg 7 pushq %rbx .seh_pushreg 3 subq $48, %rsp .seh_stackalloc 48 .seh_endprologue leaq "?cin@std@@3V?$basic_istream@DU?$char_traits@D@std@@@1@A"(%rip), %rcx leaq 44(%rsp), %rdx callq "??5?$basic_istream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@AEAH@Z" vpbroadcastd 44(%rsp), %ymm0 vmovaps __ymm@0000000800000007000000060000000500000004000000030000000200000001(%rip), %ymm1 # ymm1 = [1,2,3,4,5,6,7,8] vmovaps %ymm1, "?aa@@3PAHA"(%rip) vpaddd __ymm@0000000700000006000000050000000400000003000000020000000100000000(%rip), %ymm0, %ymm1 vmovdqa %ymm1, "?bb@@3PAHA"(%rip) vmovaps __ymm@000000100000000f0000000e0000000d0000000c0000000b0000000a00000009(%rip), %ymm1 # ymm1 = [9,10,11,12,13,14,15,16] vmovaps %ymm1, "?aa@@3PAHA"+32(%rip) vpaddd __ymm@0000000f0000000e0000000d0000000c0000000b0000000a0000000900000008(%rip), %ymm0, %ymm1 vmovdqa %ymm1, "?bb@@3PAHA"+32(%rip) vmovaps __ymm@0000001800000017000000160000001500000014000000130000001200000011(%rip), %ymm1 # ymm1 = [17,18,19,20,21,22,23,24] vmovaps %ymm1, "?aa@@3PAHA"+64(%rip) vpaddd __ymm@0000001700000016000000150000001400000013000000120000001100000010(%rip), %ymm0, %ymm1 vmovdqa %ymm1, "?bb@@3PAHA"+64(%rip) vmovaps __ymm@000000200000001f0000001e0000001d0000001c0000001b0000001a00000019(%rip), %ymm2 # ymm2 = [25,26,27,28,29,30,31,32] vmovaps %ymm2, "?aa@@3PAHA"+96(%rip) vpaddd __ymm@0000001f0000001e0000001d0000001c0000001b0000001a0000001900000018(%rip), %ymm0, %ymm2 vmovdqa %ymm2, "?bb@@3PAHA"+96(%rip) vmovaps __ymm@0000002800000027000000260000002500000024000000230000002200000021(%rip), %ymm2 # ymm2 = [33,34,35,36,37,38,39,40] vmovaps %ymm2, "?aa@@3PAHA"+128(%rip) vpaddd __ymm@0000002700000026000000250000002400000023000000220000002100000020(%rip), %ymm0, %ymm2 vmovdqa %ymm2, "?bb@@3PAHA"+128(%rip) vmovaps __ymm@000000300000002f0000002e0000002d0000002c0000002b0000002a00000029(%rip), %ymm2 # ymm2 = [41,42,43,44,45,46,47,48] vmovaps %ymm2, "?aa@@3PAHA"+160(%rip) vpaddd __ymm@0000002f0000002e0000002d0000002c0000002b0000002a0000002900000028(%rip), %ymm0, %ymm2 vmovdqa %ymm2, "?bb@@3PAHA"+160(%rip) vmovaps __ymm@0000003800000037000000360000003500000034000000330000003200000031(%rip), %ymm2 # ymm2 = [49,50,51,52,53,54,55,56] vmovaps %ymm2, "?aa@@3PAHA"+192(%rip) vpaddd __ymm@0000003700000036000000350000003400000033000000320000003100000030(%rip), %ymm0, %ymm2 vmovdqa %ymm2, "?bb@@3PAHA"+192(%rip) vmovaps __ymm@000000400000003f0000003e0000003d0000003c0000003b0000003a00000039(%rip), %ymm2 # ymm2 = [57,58,59,60,61,62,63,64] vmovaps %ymm2, "?aa@@3PAHA"+224(%rip) vpaddd __ymm@0000003f0000003e0000003d0000003c0000003b0000003a0000003900000038(%rip), %ymm0, %ymm2 vmovdqa %ymm2, "?bb@@3PAHA"+224(%rip) vpaddd __ymm@0000000f0000000d0000000b0000000900000007000000050000000300000001(%rip), %ymm0, %ymm2 vmovdqa %ymm2, "?cc@@3PAHA"(%rip) vpaddd __ymm@0000001f0000001d0000001b0000001900000017000000150000001300000011(%rip), %ymm0, %ymm0 vmovdqa %ymm0, "?cc@@3PAHA"+32(%rip) vpaddd "?aa@@3PAHA"+64(%rip), %ymm1, %ymm0 vmovdqa %ymm0, "?cc@@3PAHA"+64(%rip) vmovdqa "?bb@@3PAHA"+96(%rip), %ymm0 vpaddd "?aa@@3PAHA"+96(%rip), %ymm0, %ymm0 vmovdqa %ymm0, "?cc@@3PAHA"+96(%rip) vmovdqa "?bb@@3PAHA"+128(%rip), %ymm0 vpaddd "?aa@@3PAHA"+128(%rip), %ymm0, %ymm0 vmovdqa %ymm0, "?cc@@3PAHA"+128(%rip) vmovdqa "?bb@@3PAHA"+160(%rip), %ymm0 vpaddd "?aa@@3PAHA"+160(%rip), %ymm0, %ymm0 vmovdqa %ymm0, "?cc@@3PAHA"+160(%rip) vmovdqa "?bb@@3PAHA"+192(%rip), %ymm0 vpaddd "?aa@@3PAHA"+192(%rip), %ymm0, %ymm0 vmovdqa %ymm0, "?cc@@3PAHA"+192(%rip) vmovdqa "?bb@@3PAHA"+224(%rip), %ymm0 vpaddd "?aa@@3PAHA"+224(%rip), %ymm0, %ymm0 vmovdqa %ymm0, "?cc@@3PAHA"+224(%rip) xorl %edi, %edi leaq "?cc@@3PAHA"(%rip), %rbx leaq "?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A"(%rip), %rsi .p2align 4, 0x90
看到了吧,循环成功的进行了向量化,优化成了AVX2指令。
NUMA:(Non-UniformMemory Architecture,非一致性内存架构), 分配内存尽可能指定在本地内存上分配。使用POSIX函数pthread_setaffinity_np设定CPU的亲和性,将线程绑定到CPU内核上,避免来回切换不同内核(可能会切换到另一个CPU上)以造成性能损失。
大页内存:一般情况下x86架构CPU使用4k内存页,不过使用一些方法可以分配2M,1G的内存页。Linux系统可以使用mount hugetlbfs 或者 MMAP加上MAP_HUGETLB参数。windows系统virtualalloc加上MEMLARGEPAGES参数。使用大页内存可以减少需要的内存页表,降低tlb miss概率,从而提升性能。例如 1G内存,x86_64架构下, 4k页表会用掉2097152字节,2M页表会用掉4096字节,而1G页表只需8字节。
c快在基本可以知道相应的汇编是怎么写的。