1. 程式人生 > >VPP高效能從何而來之一:cache優化

VPP高效能從何而來之一:cache優化

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)