Intel64及IA-32架构优化指南第8章多核与超线程技术——8.7 前端优化 8.8 亲和性与共享的平台资源

8.7 前端优化


对于L2统一Cache被两个处理器核心所共享的双核处理器(Intel Core Duo处理器以及基于Intel Core微架构的处理器),多线程软件应该考虑增加代码工作集,由于从统一的Cache取代码的两个线程作为前端和Cache优化的一部分。对于基于Intel Core微架构的四核处理器,应用于Intel Core 2 Duo处理器的考虑也应用于四核处理器。


8.7.1 避免过度的循环展开


循环展开可以减少分支个数并从而提升应用程序代码的分支可预测性。循环展开在第3章中详细讨论。循环展开必须被明智地使用。确定考虑所提升的分支可预测性的利益与循环流探测器(LSD)的低利用的成本。

用户源代码编写规则34:避免过度循环展开来确保LSD被高效操作。


8.8 亲和性与管理共享的平台资源


现代操作系统提供了API或/以及数据构造(比如亲和性掩模)来允许应用程序管理某些共享资源,比如逻辑处理器、非统一的存储器访问(NUMA)存储器子系统。

在多线程化软件之前,考虑使用亲和性API,应该考虑表8-2中的建议。

表8-2:设计时资源管理选择

运行时环境:                            线程调度/处理器亲和性的考虑              ||           存储器亲和性考虑

●  一个单线程应用:通过让OS调度器管理调度来支持关注系统响应的调度器目标以及吞吐。OS为终端用户提供工具来优化运行时特定的环境。  ||  不相关,让OS做这个工作。

●  一个多线程应用要求 i)少于系统中所有的处理器资源,ii)与其它并发的应用程序共享系统资源,iii)其它并发应用可能具有更高优先级:依赖于OS默认的调度器策略。硬编码亲和性绑定将可能危害系统响应和吞吐;并且/或在某些情况下损害应用程序性能。  ||  依赖于OS默认的调度器策略。使用可以提供透明的NUMA利益的API,而不用显式地管理NUMA。

●  一个多线程的应用程序要求i)前台及更高优先级,ii)使用少于系统中所有处理器资源,iii)与其它并发应用程序共享系统资源,iv)但其它并发应用具有更低优先级:如果考虑了应用程序定制的线程绑定,那么与OS调度器可互操作的方法应该被采取,而不是使用硬编码的线程亲和性策略。比如,使用SetThreadIdealProcessor()可以为位置性优化的应用绑定策略提供一个浮动的基数来定位一个下一个空闲核心的绑定策略,并与默认的OS策略交互。  ||  使用可以提供透明NUMA利益的API而不需要显式地管理NUMA。使用性能事件来诊断非本地存储器访问发布,如果默认的OS策略引起性能问题。

●  一个运行在前台的多线程应用,要求系统中所有处理器资源并且不与并发应用共享系统资源;基于MPI的多线程:应用程序定制的线程绑定策略会比默认的OS策略更高效。使用性能事件来帮助优化位置性以及Cache传输机会。引入其自己的显式的线程亲和绑定策略的一个多线程应用应该用受终端用户或管理员所准许的选择权的某些形式来部署。比如,部署显式线程亲和性绑定策略的允许可以在安装之后获得准许。  ||  应用定制的存储器亲和性绑定策略可以比默认的OS策略更高效。使用性能事件来诊断与OS或定制策略相关的非本地存储访问问题。


8.8.1 共享资源的拓扑枚举


多线程软件是基于OS调度策略还是需要使用亲和性API来做定制的资源管理,对共享平台资源的拓扑的理解是有必要的。逻辑处理器的处理器拓扑(SMT)、处理器核心、以及平台中的物理处理器可以使用由CPUID所提供的信息来枚举到。这个在第8章中讨论。


8.8.2 非统一存储器访问


使用两个或更多的基于代号名为Nehalem的Intel微架构的Intel Xeon处理器支持非统一存储器访问(NUMA)拓扑,因为每个物理处理器提供了其自己的本地存储器控制器。NUMA提供了可以对物理处理器个数进行裁减的系统存储器带宽。系统存储器延迟将展示非对称行为,依赖于发生在本地的同一个插座中的或远程地来自另一个插座中的存储器事务。此外,OS特定的构造以及/或实现行为在API层会呈现额外的复杂度,多线程软件在一个NUMA环境中可能需要注意存储器分配/初始化。

一般,延迟敏感的工作负荷将更偏好让存储器交通驻留在本地而不是远程。如果多个线程共享一个缓存,那么程序员将需要注意在一个NUMA系统上OS特定的存储器分配/初始化的行为。

提供编成接口来管理局部/远程NUMA交通的OS构造被称为存储器亲和性。因为OS管理物理地址(系统RAM)之间的映射到线形地址(被应用程序软件访问);并且分页允许对一个物理页的动态重分派以动态地映射到不同的线性地址,对存储器亲和性的适当使用将需要很多OS特定的知识。

为了简化应用程序编成,OS可以实现某些API以及物理/线性地址映射。一个公共的技术是对OS延迟物理存储器页分派的提交,直到那个物理存储器页上的第一个存储器引用在线性地址空间中被一个应用程序线程所访问。这意味着在线性地址空间中,一个应用程序对一个存储器缓存的分配不必要判定哪个插座将服务本地存储器交通,当存储器分配API返回到程序时。然而,支持这个程度的NUMA透明性的存储器API根据不同的OS而有所不同。比如,可移植的C语言API“malloc”在Linux上提供了一些透明性程度,而Windows上的“VirtualAlloc”表现类似。不同的OS也可以提供需要显式的NUMA信息的存储器分配API,这样在线性地址之间的映射到本地/远程存储器交通在分配上是固定的。

例8-9展示了多线程应用能承担处理OS特定的API最少努力的量,并利用NUMA硬件特性。这个对存储器缓存初始化的并行方法对让每个工作者线程组在NUMA系统上保持存储器交通本地性是有传导性的。

例8-9:使用OpenMP与NUMA的并行存储器初始化技术

#ifdef _LINUX    // Linux实现了malloc在第一次访问时提交物理页
    buf1 = (char*)malloc(DIM*sizeof(double)+1024);
    buf2 = (char*)malloc(DIM*sizeof(double)+1024);
    buf3 = (char*)malloc(DIM*sizeof(double)+1024);
#endif

#ifdef windows    // Windows实现了malloc在分配时提交物理页,所以使用VirtualAlloc
    buf1 = (char*)VirtualAlloc(NULL, DIM*sizeof(double)+1024, fAllocType, fProtect);
    buf2 = (char*)VirtualAlloc(NULL, DIM*sizeof(double)+1024, fAllocType, fProtect);
    buf3 = (char*)VirtualAlloc(NULL, DIM*sizeof(double)+1024, fAllocType, fProtect);
#endif

    a = (double*)buf1;
    b = (double*)buf2;
    c = (double*)buf3;

#pragma omp parallel
{
    // 使用OpenMP线程来执行循环的每次迭代
    // OpenMP线程的数量可以由默认值或环境变量来指定
#pragma omp for private(num)
    // 每个线程迭代使用私有迭代器被分派到不同的OpenMP线程中执行
    for(num = 0; num < len; num++)
    {
        // 每个线程执行对它自己的映射到各自线程的本地存储器控制器的存储器地址、物理页的第一次触碰
        a[num] = 10.;
        b[num] = 10.;
        d[num] = 10.;
    }
}

注意,在例8-9中所展示的例子暗示了存储器缓存将在由OpenMP所创建的工作者线程结束之后被释放。这种情况避免了跨不同应用程序线程的对malloc/free重复使用的一个潜在问题。因为如果被一个线程所初始化的本地存储器在后面被另一个线程释放,那么OS在与NUMA拓扑有关的线性地址空间中跟踪/重分配存储器池可能会有困难。在Linux中,另一个API“numa_local_alloc”可以被使用。

posted @ 2013-06-05 12:41  zenny_chen  Views(567)  Comments(0Edit  收藏  举报