10.3.2 运行期按架构派发
下面我想为你展示在Intel平台上对原子整型操作进行性能优化的一点小技巧。正如我们前面已经了解到的,Intel处理器能够不被中断地执行单个指令形式的RMW(读-改-写)操作(例如ADD、XADD),因而在Intel单处理器上我们不必对总线进行加锁就能实现该操作的原子性。相反,在多处理器机器上要想达到这个目的的话,你就必须对总线进行加锁。由于人们构建(build)并分发的通常是单一版本的代码,1所以最好让代码只在必要时才付出因总线锁定而导致的性能开销。既然对总线加锁和不加锁两种情况下的指令数目都很少,那么我们就必须以一种高效的方式来完成这种优化,否则用于测试是否多处理器的代码的开销反而大过它带来的节省。解决方案的简化版本2展示于程序清单10.4中,它跟大多数现代的Win32编译器都是兼容的:3
程序清单10.4
- namespace
- {
- static bool s_uniprocessor = is_host_up();
- }
-
- inline __declspec(naked) void __stdcall
- atomic_increment(sint32_t volatile * /* pl */)
- {
- if(s_uniprocessor)
- {
- _asm
- {
- mov ecx, dword ptr [esp + 4]
- add dword ptr [ecx], 1
- ret 4
- }
- }
- else
- {
- _asm
- {
- mov ecx, dword ptr [esp + 4]
- lock add dword ptr [ecx], 1
- ret 4
- }
- }
- }
即使你对Intel汇编语言不熟悉,你也应该能够看出这种机制是何等的简单。s_uniprocessor变量对于单处理器机器来说是true,对于多处理器来说则是false。如果是true的话,就可以不加锁地实现原子的递增操作,反之则需要对总线进行加锁。这里在s_uniprocessor的实例化中可能存在的任何竞争条件都是无关紧要的,因为缺省情况下是进行加锁。
在下面的性能测试表中,这正是Synesis库里的Atomic_API和WinSTL原子函数所采用的机制。