> 文章列表 > Linux 内核likely与unlikey

Linux 内核likely与unlikey

Linux 内核likely与unlikey

内核源码的时候经常可以看到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了。

  1. 在执行条件分支指令时,CPU也会预取下一条指令执行,但是如果条件分支的结果为跳转到了其他指令,那CPU预取的下一条指令就没用了,这样就降低了流水线的效率。
  2. 跳转指令相对于顺序执行的指令会多消耗CPU时间,如果可以尽可能不执行跳转,也可以提高CPU性能。

实际例子来理解下

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

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()预取到缓存中,从而提升性能。