- 多线程
从单个CPU你能得到的处理能力是有限的。因此,许多现代计算机系统有多个CPU核。利用单个CPU的方式是,在多个线程间分解计算任务。通常,最优线程数等于CPU核个数。工作负荷应该理想地在线程间平均分配。
在代码具有粗粒度的内秉并行性时,多线程化是有用的。多线程化不能用于细粒度并行,因为启动与停止线程以及线程同步有可观的开销代价。线程间通讯代价会相当高,尽管这些代价在较新的处理器上降低了。计算任务最好最大程度地分解为线程。如果最外层循环可以被并行化,那么它应该被分解为每线程一个循环,每个做整个任务中自己的份额。线程局部储存最好使用栈。静态线程局部内存是低效的,应该避免。
-
- 多线程并行
大多数现代微处理器有多个CPU核,因此它们可以同时执行多个线程。另外,许多Intel与AMD处理器在每个核可以运行两个线程。Knight’s Corner与Knight’s Landing处理器甚至可以每核运行4个线程。“超线程”是并行多线程的Intel术语。
运行在同一个核上的2或多个线程将总是竞争相同的资源,比如缓存,指令解码器与执行单元。如果任何共享资源是单线程性能的限制因素,在每个核上运行2个线程没有好处。相反,每个线程运行速度可能不到一半,因为缓存逐出与其他资源冲突。但如果时间的很大部分花在缓存不命中、分支误预测或长依赖链,那么每条线程运行速度将超过单线程速度一半。在这个情形里,使用并行多线程是有优势的,但性能不会加倍。与另一个线程共享核资源的线程将总是比单独运行在一个核上的线程要慢。
一个CPU核容量更高,并发多线程的好处越大。Knight’s Landing处理器有相当低的容量,因此在这个处理器里运行2个线程很少有好处,4个线程更是少得多。并发多线程在更旧的Knight’s Corrner处理器上要更有用,因为它没有乱序处理能力。
做实验以确定在特定的应用中使用并发多线程是否有好处是必须的。
关于多线程与并发多线程的更多细节,参考手册1《优化C++软件》。
- CPU分派
如果正在使用不是所有微处理器都支持的指令,你必须首先检查程序是否运行在支持这些指令的微处理器上。如果你的程序可以从使用特定指令集显著获益,可以制作程序关键部分使用这个指令集的一个版本,以及另一个与旧微处理器兼容的版本。
手册1《优化C++软件》第13章对CPU分派有重要的建议。可以使用分支或代码指针实现CPU分派,如下面例子所示。
Example 15.1. Function with CPU dispatching
MyFunction proc near
; Jump through pointer. The code pointer initially points to
; MyFunctionDispatch. MyFunctionDispatch changes the pointer
; so that it points to the appropriate version of MyFunction.
; The next time MyFunction is called, it jumps directly to
; the right version of the function
jmp [MyFunctionPoint]
; Code for each version. Put the most probable version first:
MyFunctionAVX:
; AVX version of MyFunction
ret
MyFunctionSSE2:
; SSE2 version of MyFunction
ret
MyFunction386:
; Generic/80386 version of MyFunction
ret
MyFunctionDispatch:
; Detect which instruction set is supported.
; Function InstructionSet is in asmlib
call InstructionSet ; eax indicates instruction set
mov edx, offset MyFunction386
cmp eax, 4 ; eax >= 4 if SSE2
jb DispEnd
mov edx, offset MyFunctionSSE2
cmp eax, 11 ; eax >= 11 if AVX
jb DispEnd
mov edx, offset MyFunctionAVX
DispEnd:
; Save pointer to appropriate version of MyFunction
mov [MyFunctionPoint], edx
jmp edx ; Jump to this version
.data
MyFunctionPoint DD MyFunctionDispatch ; Code pointer
.code
MyFunction endp
检测支持哪个指令集的函数InstructionSet,在可以从www.agner.org/optimize/asmlib.zip下载的库中得到。大多数操作系统也有这个目的的函数。当然,建议保存InstructionSet的输出,而不是每次需要这个信息时再次调用它。使用CPU分派的函数的细致例子,还可参考www.agner.org/optimize/asmexamples.zip。
-
- 检查操作系统对XMM与YMM寄存器的支持
不幸,可以从CPUID指令获得的信息不足以确定是否使用XMM寄存器。在任务切换期间,操作系统必须保存这些寄存器,在任务重新开始时恢复它们。微处理器可以禁用XMM寄存器,以避免在不保存这些寄存器的旧操作系统下使用它们。支持XMM寄存器的操作系统必须设置控制寄存器CR4的比特9来启用XMM寄存器,并表示在任务切换期间它能保存及恢复这些寄存器。(在启用XMM寄存器时,保存与恢复寄存器实际上更快)。
不幸的是,仅能在特权模式读CR4寄存器。因此,在确定是否允许使用XMM寄存器方面,应用程序存在问题。根据Intel官方文档,应用程序确定操作系统是否支持XMM寄存器的仅有方法是,尝试执行一条XMM指令,看是否得到一个无效操作码异常。这是荒唐的,因为不是所有的操作系统、编译器与编程语言都向应用程序提供了捕捉无效操作码异常的设施。如果没有方法知道使用XMM寄存器是否会使软件崩溃,使用这些寄存器的好处消失殆尽。
这些严重的问题促使我查找检查操作系统是否支持XMM寄存器使用的替代方法,很幸运地,我找到了一个可以可靠工作的方法。如果启用了XMM寄存器,FXSAVE与FXRSTOR指令可以读及修改XMM寄存器。如果禁用了XMM寄存器,FXSAVE与FXRSTOR指令不能访问这些寄存器。因此,通过FXSAVE与FXRSTOR读、写XMM寄存器,检查是否启用了XMM寄存器是可能的。在www.agner.org/optimize/asmlib.zip中的子例程使用了这个方法。除了高级语言,这些子例程也可以从汇编调用,提供了一个检测XMM寄存器是否可以使用的简单方式。
为了验证这个检测方法对所有微处理器工作正确,我首先检查了各种手册。1999版的Intel软件开发者手册如是说FXSTOR指令:“如果没有设置CR4.OSFXSR比特,在保存映像里的流媒体SIMD扩展域(XMM0-XMM7与MXCSR)将不会载入处理器。”AMD的程序员手册也如是说。不过,2003版1的Intel手册说该行为是依赖于实现的。为了澄清这,我联系了Intel技术支持,并得到回复,“如果没有设置CR4的OSFXSR比特,在执行FXRSTOR时,不会恢复XMM寄存器。”他们进一步确认对所有版本的微处理器与所有微代码更新,这都成立。我视这为我的方法将在所有Intel微处理器上有效的一个Intel的保证。我们可以相信该方法在AMD处理器也会正确工作,因为对这个问题,AMD手册没有歧义。依赖这个方法在未来微处理器也能正确工作看起来是安全的,因为任何偏离上面说明的微处理器除了不能运行现有的程序,还引入一个严重的问题。与现有程序兼容,是微处理器厂商严重关切的。
在Intel手册中推荐的检测方法有依赖于编译器与操作系统捕捉无效操作码异常能力的缺点。例如,一个使用Intel检测方法的Windows应用必须在所有兼容的操作系统中测试,包括运行在若干其他操作系统下的各种Windows模拟器。我的检测方法没有这个问题,因为它与编译器及操作系统无关。我的方法有使模块化程序更容易的进一步好处,因为一个使用XMM指令的模块、子例程库或DLL可以包括该检测过程,使得支持XMM 的问题不再是调用程序忧虑的事情,它甚至可以使用另外的程序语言编写。某些操作系统提供系统函数来告知支持哪个指令集,但上面提到的方法与操作系统无关。
检测对YMM寄存器的操作支持更容易。以下方法描述在《Intel Advanced Vector Extensions Programming Reference》:使用eax = 1执行CPUID。检查ecx的27与28比特是否都是1(OSXSAVE与AVX特性标记)。如果是,那么使ecx = 0执行XGETBV获取XFEATURE_ENABLED_MASK。检查eax的1与2比特是否都是1(支持XMM与YMM状态)。如果是。那么使用YMM寄存器是安全的。
上面讨论依赖于下面的文档:
Intel application note AP-900: "Identifying support for Streaming SIMD Extensions in the Processor and Operating System". 1999.
Intel application note AP-485: "Intel Processor Identification and the CPUID Instruction". 2002.
"Intel Architecture Software Developer's Manual, Volume 2: Instruction Set Reference", 1999.
"IA-32 Intel Architecture Software Developer's Manual, Volume 2: Instruction Set Reference", 2003.
"AMD64 Architecture Programmer’s Manual, Volume 4: 128-Bit Media Instructions", 2003.
"Intel Advanced Vector Extensions Programming Reference", 2008, 2010.