当前位置: 首页 > 工具软件 > Likely > 使用案例 >

Linux 内核likely与unlikey

周伟泽
2023-12-01

内核源码的时候经常可以看到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性能。
  • 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
    
  • 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()预取到缓存中,从而提升性能。

 类似资料: