内核源码的时候经常可以看到likely()
和unlikely()
函数,这两个函数的作用是什么?-- 先得学一学GCC提供的内建函数!!
likely和unlikely内核中的定义
# define likely(x) __builtin_expect(!!(x), 1)
# define unlikely(x) __builtin_expect(!!(x), 0)
# define likely_notrace(x) likely(x)
# define unlikely_notrace(x) unlikely(x)
内建函数
GUN C语言提供了一系列内建函数以进行优化,这些内建函数以“_builtin”(build in function)作为前缀。
-
__builtin_constant_p(x)
判断x是否在编译时就可以被确定为常量,如果x为常量,那么返回1,否则返回0。#define udelay(n) \ (__builtin_constant_p(n) ? \ ((n) > (MAX_UDELAY_MS * 1000) ? __bad_udelay() : \ __const_udelay((n) * UDELAY_MULT)) : \ __udelay(n))
-
__builtin_expect(exp, c)
__builtin_expect
的函数原型为long __builtin_expect (long exp, long c)
, __builtin_expect (lexp, c)的返回值仍是exp值本身,并不会改变exp的值。这里的意思是exp==c的概率很大,用来引导GCC用来条件分支预测,开发人员最清楚最可能执行哪个分支,并将最有可能执行的分支告诉编译器,让编译器优化指令序列排序,使指令尽可能的顺序执行,从而提高CPU预取指令的正确性,提升CPU执行性能。
# define likely(x) __builtin_expect(!!(x), 1) //x为真的可能性较大 # define unlikely(x) __builtin_expect(!!(x), 0) //x为假的可能性较大
为什么要使用!!符号呢?
计算机中bool逻辑只有0和1,非0即是1,当likely(x)中参数不是逻辑值时,就可以使用!!符号转化为逻辑值1或0 。比如:!!(3)=!(!(3))=!0=1,这样就把参数3转化为逻辑1了。
- 在执行条件分支指令时,CPU也会预取下一条指令执行,但是如果条件分支的结果为跳转到了其他指令,那CPU预取的下一条指令就没用了,这样就降低了流水线的效率。
- 跳转指令相对于顺序执行的指令会多消耗CPU时间,如果可以尽可能不执行跳转,也可以提高CPU性能。
-
likely使用实例
static inline void native_set_ldt(const void *addr, unsigned int entries) { if (likely(entries == 0)) asm volatile("lldt %w0"::"q" (0)); else { unsigned cpu = smp_processor_id(); ldt_desc ldt; set_tssldt_descriptor(&ldt, (unsigned long)addr, DESC_LDT, entries * LDT_ENTRY_SIZE - 1); write_gdt_entry(get_cpu_gdt_rw(cpu), GDT_ENTRY_LDT, &ldt, DESC_LDT); asm volatile("lldt %w0"::"q" (GDT_ENTRY_LDT*8)); } }
加
likely
的意思是变量entries
的值为0的可能性较大,那么执行if的机会大,如果以上代码likely
改为unlikely
,则表示entries
的值不为0的可能性大一些,执行else机会大一些,加上这种修饰,编译成二进制代码时likely使得if后面的执行语句紧跟着前面的程序,unlikely使得else后面的语句紧跟着前面的程序,这样就会被cache预读取,增加程序的执行速度。 -
__builtin_prefetch(const void *addr, int rw, int locality)
主动进行数据预取,在使用addr的值之前就把该值读到cache中,降低读取时延,从而提高性能。- addr
要预取数据的地址 - rw
读写属性,1表示可写,0表示只读 - locality
数据在缓存中的时间局部属性,0表示读取完addr的值之后不用保留在缓存中,1~3表示时间局部属性逐渐增强
// inlcude/linux/prefetch.h #ifndef ARCH_HAS_PREFETCH #define prefetch(x) __builtin_prefetch(x) #endif #ifndef ARCH_HAS_PREFETCHW #define prefetchw(x) __builtin_prefetch(x,1) #endif #ifndef ARCH_HAS_SPINLOCK_PREFETCH #define spin_lock_prefetch(x) prefetchw(x) #endif
- addr
-
prefetch()使用实例
void __free_pages_core(struct page *page, unsigned int order) { unsigned int nr_pages = 1 << order; struct page *p = page; unsigned int loop; /* * When initializing the memmap, __init_single_page() sets the refcount * of all pages to 1 ("allocated"/"not free"). We have to set the * refcount of all involved pages to 0. */ prefetchw(p); for (loop = 0; loop < (nr_pages - 1); loop++, p++) { prefetchw(p + 1); __ClearPageReserved(p); set_page_count(p, 0); } __ClearPageReserved(p); set_page_count(p, 0); atomic_long_add(nr_pages, &page_zone(page)->managed_pages); /* * Bypass PCP and place fresh pages right to the tail, primarily * relevant for memory onlining. */ __free_pages_ok(page, order, FPI_TO_TAIL | FPI_SKIP_KASAN_POISON); }
在处理page数据结构之前,可通过prefetchw()预取到缓存中,从而提升性能。