程序性能优化手段-实际代码案例1

  在前两篇中,分别介绍了通过数据分片和避免伪共享两种方法来增加程序性能。现在来看一个实际的代码案例。
  在Windows内核中,有一些带CacheAware的函数,包括RundownProtection和PushLock系列的函数。顾名思义,这些函数是进行了Cache友好的优化,其中包括了对多核进行数据分片以及CacheLine大小对齐的内存块。
  我们来看一下这些函数是如何工作的吧,这里以RundownProtection为例,这个锁可以被翻译为销毁保护吧,具体用途可以参考MSDN,这里不细介绍。
  首先看Cache友好的数据结构:

typedef struct _EX_RUNDOWN_REF_CACHE_AWARE {

    //这个结构就包含了缓存友好的结构

    PEX_RUNDOWN_REF RunRefs;

    //如果申请的内存不和Cacheline对齐,那么这个就保存不对齐的内存地址

    PVOID PoolToFree;

    //每一个RundownRef结构的大小,MP下就是Cacheline大小

    ULONG RunRefSize;

    //总共有多少个RundownRef结构,MP下就是CPU Group0中Core的个数

    ULONG Number;
} EX_RUNDOWN_REF_CACHE_AWARE, *PEX_RUNDOWN_REF_CACHE_AWARE;

  有一个函数专门用于初始化这个结构,看看是如何初始化这个结构的:

NTKERNELAPI
PEX_RUNDOWN_REF_CACHE_AWARE
ExAllocateCacheAwareRundownProtection(
    __in POOL_TYPE PoolType,
    __in ULONG PoolTag
    )
{

    PEX_RUNDOWN_REF_CACHE_AWARE RunRefCacheAware;
    PEX_RUNDOWN_REF RunRefPool;
    PEX_RUNDOWN_REF CurrentRunRef;
    ULONG PaddedSize;
    ULONG Index;

    PAGED_CODE();

    //首先申请EX_RUNDOWN_REF_CACHE_AWARE的内存,这个是没有什么需要处理的
    RunRefCacheAware = ExAllocatePoolWithTag (PoolType,
                                              sizeof( EX_RUNDOWN_REF_CACHE_AWARE ),
                                              PoolTag
                                              );

    if (NULL != RunRefCacheAware) {

        //获得CPU的个数,XP是不支持CPU组的
        RunRefCacheAware->Number = KeNumberProcessors;

        //计算每个RundownRef结构的大小,对于MP,这个是Cacheline大小,否则就是原始大小
        if (RunRefCacheAware->Number > 1) {
            PaddedSize = KeGetRecommendedSharedDataAlignment ();
            ASSERT ((PaddedSize & (PaddedSize - 1)) == 0);
        } else {
            PaddedSize = sizeof (EX_RUNDOWN_REF);
        }

        ASSERT (sizeof (EX_RUNDOWN_REF) <= PaddedSize);

        RunRefCacheAware->RunRefSize = PaddedSize;

        //尝试申请一块刚刚好的内存
        RunRefPool = ExAllocatePoolWithTag (PoolType,
                                            PaddedSize * RunRefCacheAware->Number,
                                            PoolTag);

        if (RunRefPool == NULL) {
            ExFreePool (RunRefCacheAware);
            return NULL;
        }

        //如果是MP,但内存起始地址不和Cacheline大小对齐
        if ((RunRefCacheAware->Number > 1) && 
            !EXP_IS_ALIGNED_ON_BOUNDARY (RunRefPool, PaddedSize)) {

            //先把这块内存释放掉
            ExFreePool (RunRefPool);

            //在增加一块Cacheline的大小
            RunRefPool = ExAllocatePoolWithTag (PoolType,
                                                PaddedSize * RunRefCacheAware->Number + PaddedSize,
                                                PoolTag);

            if (RunRefPool == NULL) {
                ExFreePool (RunRefCacheAware);
                return NULL;
            }
            //获得对齐后的RundownRef数组的起始地址
            CurrentRunRef = EXP_ALIGN_UP_PTR_ON_BOUNDARY (RunRefPool, PaddedSize);

        } else {

            //否则申请内存的起始地址就是RundownRef数组的起始地址
            CurrentRunRef = RunRefPool;
        }

        //保存申请的内存地址和对齐后的地址
        RunRefCacheAware->PoolToFree = RunRefPool;
        RunRefCacheAware->RunRefs = CurrentRunRef;

        //对每块CPU初始化其对应的RundownRef结构
        for (Index = 0; Index < RunRefCacheAware->Number; Index++) {
            CurrentRunRef = EXP_GET_PROCESSOR_RUNDOWN_REF (RunRefCacheAware, Index);
            ExInitializeRundownProtection (CurrentRunRef);
        }
    }

    return RunRefCacheAware;
}

  在这个函数中,会至少申请CachelineSize*ProcessorNumber大小的内存,即对每个CPU单独申请一块内存,而内存的大小至少是缓存行大小,这样就形成了一个不会产生缓存同步的RundownRef数组。如果这个内存无法和缓存行大小对齐,那么就重新申请内存,这次在加上一个缓存行大小的空间,这样就可以让RundownRef数组的起始地址在缓存行大小上对齐。最后分别对数组中每个RundownRef结构进行初始化。
  然后每次在获取这个锁的时候,就可以以当前运行的CPU的序号作为下标来操作对应的数组元素。

NTKERNELAPI
BOOLEAN
FASTCALL
ExAcquireRundownProtectionCacheAware (
     __inout PEX_RUNDOWN_REF_CACHE_AWARE RunRefCacheAware
     )
{
    //请求当前CPU对应数组元素的Rundwon锁
   return ExAcquireRundownProtection (EXP_GET_CURRENT_RUNDOWN_REF (RunRefCacheAware));
}

PEX_RUNDOWN_REF
FORCEINLINE
EXP_GET_CURRENT_RUNDOWN_REF(
    IN PEX_RUNDOWN_REF_CACHE_AWARE RunRefCacheAware
    )
{
    //计算数组元素的字节偏移
    return ((PEX_RUNDOWN_REF) (((PUCHAR) RunRefCacheAware->RunRefs) +
                               (KeGetCurrentProcessorNumber() % RunRefCacheAware->Number) * RunRefCacheAware->RunRefSize));
}

  每次申请一个RundownRef锁时,首先获取当前的CPUNumber,然后计算出需要操作的数组元素,最后只需要操作对应的元素即可。
  如果没有分片,获取锁时将不得不进行加锁操作,而大小如果不满足缓存行大小,那么两个core同时获取锁时,也将会产生伪共享问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值