VPP高性能从何而来之一:cache优化


cache的哲学:时间局限性和空间局限性
时间局限性:程序即将用到的指令/数据可能就是目前正在用到的指令/数据,因此当前用到的指令或数据将会继续放在cache中以备将来继续使用;如循环语句的终止条件满足之前,处理器会反复用到循环语句中的指令;
这就是双层循环时候为什么尽量把循环次数更大的放在内层的原因
空间局限性:程序即将用到的指令/数据可能与目前正在用到的指令/数据在空间上相邻或接近。因此处理器在处理当前指令或数据时候,可以从内层中把相邻区域的指令或数据预取到cache中。这就是VPP向量报文的秘密。

cache优化包括如下几个方面

1.cache line 对齐

cache line bytes是指一次性从内存读到cache中的字节数。cache line对齐的目的,可减少CPU访问cache、cache访问内存的次数。因为数据跨越两个cache line,就意味着两次load或者两次store。如果数据结构是cache line对齐的, 就有可能减少一次读写。


 /*
 * Allow CFLAGS to override the configured / deduced cache line size
 */
#ifndef CLIB_LOG2_CACHE_LINE_BYTES
 
/* Default cache line size of 64 bytes. */
#ifndef CLIB_LOG2_CACHE_LINE_BYTES
#define CLIB_LOG2_CACHE_LINE_BYTES 6
#endif
 
#endif /* CLIB_LOG2_CACHE_LINE_BYTES defined */
 
#if (CLIB_LOG2_CACHE_LINE_BYTES >= 9)
#error Cache line size 512 bytes or greater
#endif
 
#define CLIB_CACHE_LINE_BYTES (1 << CLIB_LOG2_CACHE_LINE_BYTES)
#define CLIB_CACHE_LINE_ALIGN_MARK(mark) u8 mark[0] __attribute__((aligned(CLIB_CACHE_LINE_BYTES)))


2.尽量避免cache一致性问题

cache一致性问题:多核对同一块内存读写,会引起冲突的问题。
类似于DPDK,VPP为了避免cache一致性问题,对某些数据结构,给每个核都定义一份。
例如snat session定义成per-thread的

typedef struct
{
  /* Main lookup tables */
  clib_bihash_8_8_t out2in;
  clib_bihash_8_8_t in2out;

  /* Endpoint dependent sessions lookup tables */
  clib_bihash_16_8_t out2in_ed;
  clib_bihash_16_8_t in2out_ed;

  /* Find-a-user => src address lookup */
  clib_bihash_8_8_t user_hash;

  /* User pool */
  snat_user_t *users;

  /* Session pool */
  snat_session_t *sessions;

  /* Pool of doubly-linked list elements */
  dlist_elt_t *list_pool;

  /* NAT thread index */
  u32 snat_thread_index;
} snat_main_per_thread_data_t;

3.I-cache与D-cache

I-cache优化:
(1)Vector all over ,所有结构都是数组、链表操作也修改成为动态数组;
(2)分支预测

/* Hints to compiler about hot/cold code. */
#define PREDICT_FALSE(x) __builtin_expect((x),0)
#define PREDICT_TRUE(x) __builtin_expect((x),1)

D-cache优化:利用gcc内置函数 __builtin_prefetch实现的数据手工预取

#define _CLIB_PREFETCH(n,size,type)				\
  if ((size) > (n)*CLIB_CACHE_LINE_BYTES)			\
    __builtin_prefetch (_addr + (n)*CLIB_CACHE_LINE_BYTES,	\
			CLIB_PREFETCH_##type,			\
			/* locality */ 3);

#define CLIB_PREFETCH(addr,size,type)		\
do {						\
  void * _addr = (addr);			\
						\
  ASSERT ((size) <= 4*CLIB_CACHE_LINE_BYTES);	\
  _CLIB_PREFETCH (0, size, type);		\
  _CLIB_PREFETCH (1, size, type);		\
  _CLIB_PREFETCH (2, size, type);		\
  _CLIB_PREFETCH (3, size, type);		\
} while (0)

猜你喜欢

转载自blog.csdn.net/weixin_42265069/article/details/85780560