• 鲲鹏CompareAndSwap


    x86平台 CompareAndSwap实现如下所示

    #define barrier() __asm__ __volatile__("": : :"memory")
    int CompareAndSwap(volatile int* ptr,
                                int old_value,
                                int new_value) {
      int prev;
      __asm__ __volatile__("lock; cmpxchgl %1,%2"
                           : "=a" (prev)
                           : "q" (new_value), "m" (*ptr), "0" (old_value)
                           : "memory");
      return prev;
    }
    static void lock(int *l){
        while(CompareAndSwap(l, 0, 1) != 0);
    }
    static void unlock(int volatile *l){
        barrier();
        *l = 0;
    }

    指令集差异

    这是一个简化的自旋锁实现,在我们对CompareAndSwap这个函数进行移植时,我们首先关注到的是两个架构中指令集的差异。在这个实现中,通过内联汇编语法使用了x86架构下的cmpxchgl指令,但是在arm架构下并没有与之完全一一对应的指令

    在arm架构下,原子操作是通过exclusive指令对实现的,如下图所示(图片来自Armv8体系结构参考手册):

    所以我们使用exclusive指令对来实现这个CompareAndSwap函数,如下所示:

    int CompareAndSwap(volatile int* ptr,
                                 int old_value, 
                                 int new_value) {
      int prev;
      int temp;
    __asm____volatile__ (
        "0:                                    
    	"
        "ldxr %w[prev], %[ptr]                 
    	" 
        "cmp %w[prev], %w[old_value]           
    	"
        "bne 1f                                
    	"
        "stxr %w[temp], %w[new_value], %[ptr]  
    	" 
        "cbnz %w[temp], 0b                     
    	" 
        "1:                                    
    	"
        : [prev]"=&r" (prev),
          [temp]"=&r" (temp),
          [ptr]"+Q" (*ptr)
        : [old_value]"IJr" (old_value),
          [new_value]"r" (new_value)
        : "cc", "memory"
      );
      return prev;
    }

    内存序差异

    在如上述所示替换了CompareAndSwap函数之后,发现自旋锁并没有按预期工作,其主要是x86架构和arm中的内存序差异导致的。

    修改前:

    static void lock(int *l){ 
         while(CompareAndSwap(l, 0, 1) != 0); 
     } 
     static void unlock(int volatile *l){ 
         barrier(); 
         *l = 0; 
     }

    表1所述,在arm架构下允许原子操作和内存读写之间的乱序,导致了上述代码中lock函数之后的内存访问可能被乱序到lock中的原子操作获取到锁之前执行,进而导致了非预期程序行为

    另一方面,在释放锁的时候,原代码中使用了一个编译型内存屏障,但是在arm更宽松的内存序模型下不足以保证正确,需要改为CPU级的内存屏障,

    修改后代码如下所示:

    #define smp_mb()  asm volatile("dmb ish" ::: "memory")
    static void lock(int *l){
        while(CompareAndSwap(l, 0, 1) != 0);
        smp_mb();
    }
    static void unlock(int volatile *l){
        smp_mb();
        *l = 0;
    }

    至此该自旋锁在功能上的移植就完成了,经过验证这个实现可以按预期工作。实际上,根据尽可能使用acquire和release语义进行同步中所述,我们可以通过半屏障来进一步优化这个锁的实现,提升锁的性能。在获取锁的时候只需要acquire语义,而释放锁的时候只需要release语义,从而去掉上面所使用的CPU级的全屏障。最终移植到arm架构下的实现如下所示:

    int CompareAndSwap(volatile int* ptr, 
                                int old_value, 
                                int new_value) {
      int prev;
      int temp;
    __asm____volatile__ (
        "0:                                    
    	"
        "ldaxr %w[prev], %[ptr]                 
    	" 
        "cmp %w[prev], %w[old_value]           
    	"
        "bne 1f                                
    	"
        "stxr %w[temp], %w[new_value], %[ptr]  
    	" 
        "cbnz %w[temp], 0b                     
    	"  
        "1:                                    
    	"
        : [prev]"=&r" (prev),
          [temp]"=&r" (temp),
          [ptr]"+Q" (*ptr)
        : [old_value]"IJr" (old_value),
          [new_value]"r" (new_value)
        : "cc", "memory"
      );
      return prev;
    }
    static void lock(int *l) {
        while(CompareAndSwap(l, 0, 1) != 0);
    }
    static void unlock(int volatile *l)
    {
        int zero = 0;
        __atomic_store(l, &zero, __ATOMIC_RELEASE);
    }

    除了架构差异之外,在锁的移植过程中我们还有注意处理器的差异。x86架构下大部分处理器L3 cache的Cacheline大小一般为64字节,而鲲鹏920芯片的L3 cache Cacheline大小为128字节,所以在设计锁的数据结构时要格外注意,尽量避免多线程相互独立修改的变量共享同一个Cacheline的情况出现,即通常说的“伪共享”现象——这对性能有较大的影响。

    下面举一个linux内核中的优化案例进行说明。这是linux内核iommu驱动中针对arm平台进行优化的一个补丁(参考链接:https://gitlab.freedesktop.org/drm/msm/commit/14bd9a607f9082e7b5690c27e69072f2aeae0de4),优化前的代码如下所示:

    struct iova_domain {
        /* ………… */
        struct iova anchor; 
        struct iova_rcache rcaches[IOVA_RANGE_CACHE_MAX_SIZE];  
        iova_flush_cb   flush_cb;   
        iova_entry_dtor entry_dtor; 
        /* Number of TLB flushes that have been started */
        atomic64_t  fq_flush_start_cnt;
        /* Number of TLB flushes that have been finished */
        atomic64_t  fq_flush_finish_cnt;   
        struct timer_list fq_timer;
        /* 1 when timer is active, 0 when not */
        atomic_t fq_timer_on; 
    };

    在这个数据结构中,多线程访问的变量主要是原子类型的fq_flush_start_cnt、fq_flush_finish_cnt和atomic_t fq_timer_on。为了避免伪共享现象,我们需要尽量避免这些变量分布在同一个cacheline上。在cacheline为64B的情况下,通过在fq_flush_finish_cnt和fq_timer_on之间插入一个fq_timer变量,就可以达到这个目的。

    但是当处理器的cacheline变成128B的情况下,这个处理不足以满足要求,所以我们需要进一步调整数据结构——在原子变量之间插入更多的变量使得fq_flush_finish_cnt和fq_timer_on分布在不同的cacheline上,优化后的代码如下所示:

    struct iova_domain {
        ……
        /* Number of TLB flushes that have been started */
        atomic64_t  fq_flush_start_cnt;
        /* Number of TLB flushes that have been finished */
        atomic64_t  fq_flush_finish_cnt;   
        struct iova anchor; 
        struct iova_rcache rcaches[IOVA_RANGE_CACHE_MAX_SIZE];  
        iova_flush_cb   flush_cb;   
        iova_entry_dtor entry_dtor; 
        struct timer_list fq_timer;
        /* 1 when timer is active, 0 when not */
        atomic_t fq_timer_on; 
    };

    https://support.huaweicloud.com/codeprtr-kunpenggrf/kunpengtaishanporting_12_0052.html

  • 相关阅读:
    安卓基础之读取联系人的姓名和电话
    Android基础之6.0系统以上的权限分配
    Android基础之内容提供者的实现
    android中Post方式发送HTTP请求
    安卓基础之Sqlite数据库最最基础操作
    安卓基础之Get方式发送http请求
    安卓基础之国际化
    安卓基础之主题/样式
    安卓基础之Activity的生命周期
    Kotlin入门(14)继承的那些事儿
  • 原文地址:https://www.cnblogs.com/dream397/p/14550864.html
Copyright © 2020-2023  润新知