注意处理器差异
除了架构差异之外,在锁的移植过程中我们还有注意处理器的差异。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; };
父主题: 锁的移植