寻找线程同步性能问题的解释

Mar*_*ins 9 c performance multithreading synchronization windows-server-2008

当使用内核对象来同步在不同CPU上运行的线程时,使用Windows Server 2008 R2相对于其他操作系统可能会有一些额外的运行时成本吗?

编辑:通过答案发现,问题还应该包括"在较低的CPU利用率水平下运行"这一短语.我在自己对这个问题的回答中包含了更多信息.

背景

我研究的产品使用共享内存和信号量进行进程间的通信(当两个进程在同一台机器上运行时).有关Windows Server 2008 R2(我在此之后缩短为Win2008R2)的性能问题的报告使我发现在Win2008R2上的两个线程之间共享信号量与其他操作系统相比相对较慢.

再现它

我能够通过在两个线程上同时运行以下代码来重现它:

for ( i = 0; i < N; i++ )
  {
  WaitForSingleObject( globalSem, INFINITE );
  ReleaseSemaphore( globalSem, 1, NULL );
  }
Run Code Online (Sandbox Code Playgroud)

使用可以双启动到Windows Server 2003 R2 SP2和Windows Server 2008 R2的计算机进行测试,上面的代码片段在Win2003R2机器上运行速度比Win2008R2快7倍(Win2003R2为3秒,Win2008R2为21秒).

简单版本的测试

以下是上述测试的完整版本:

#include <windows.h>
#include <stdio.h>
#include <time.h>


HANDLE gSema4;
int    gIterations = 1000000;

DWORD WINAPI testthread( LPVOID tn )
{
   int count = gIterations;

   while ( count-- )
      {
      WaitForSingleObject( gSema4, INFINITE );
      ReleaseSemaphore( gSema4, 1, NULL );
      }

   return 0;
}


int main( int argc, char* argv[] )
{
   DWORD    threadId;
   clock_t  ct;
   HANDLE   threads[2];

   gSema4 = CreateSemaphore( NULL, 1, 1, NULL );

   ct = clock();
   threads[0] = CreateThread( NULL, 0, testthread, NULL, 0, &threadId );
   threads[1] = CreateThread( NULL, 0, testthread, NULL, 0, &threadId );

   WaitForMultipleObjects( 2, threads, TRUE, INFINITE );

   printf( "Total time = %d\n", clock() - ct );

   CloseHandle( gSema4 );
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

更多细节

我更新了测试以强制线程运行单个迭代并强制切换到每个循环的下一个线程.每个线程都表示下一个线程在每个循环结束时运行(循环式).我还更新了它,使用自旋锁作为信号量的替代品(这是一个内核对象).

我测试的所有机器都是64位机器.我把测试编译成32位.如果构建为64位,它整体运行速度更快一些,并将比率改变一些,但最终结果是相同的.除了Win2008R2之外,我还遇到了Windows 7 Enterprise SP 1,Windows Server 2003 R2 Standard SP 2,Windows Server 2008(不是R2)和Windows Server 2012 Standard.

  • 在单个CPU上运行测试的速度要快得多(通过使用SetThreadAffinityMask设置线程亲和性并使用GetCurrentProcessorNumber检查"强制" ).毫不奇怪,当使用单个CPU时,所有操作系统都更快,但是在Win2008R2上,多CPU和单个CPU与内核对象同步的比例要高得多.除Win2008R2之外的所有机器的典型比率是2x到4x(在多个CPU上运行需要2到4倍).但在Win2008R2上,这个比例是9倍.
  • 但是......我无法重现所有Win2008R2机器的减速.我在4上测试过,它出现了3个.所以我不禁想知道是否存在某种可能影响这种情况的配置设置或性能调整选项.我已阅读性能调整指南,查看各种设置,并更改了各种设置(例如,后台服务与前台应用程序),但行为没有差异.
  • 它似乎不一定与物理核心之间的切换有关.我原先怀疑它与以不同方式重复访问不同核心上的全局数据的成本有关.但是当运行使用简单的自旋锁进行同步(而不是内核对象)的测试版本时,在所有操作系统类型上运行不同CPU上的各个线程的速度相当快.多CPU信号量同步测试与多CPU自旋锁测试的比率通常为10x至15x.但对于Win2008R2标准版机器,这个比例是30倍.

以下是更新测试中的一些实际数字(时间以毫秒为单位):

+----------------+-----------+---------------+----------------+
|       OS       | 2 cpu sem |   1 cpu sem   | 2 cpu spinlock |
+----------------+-----------+---------------+----------------+
| Windows 7      | 7115 ms   | 1960 ms (3.6) | 504 ms (14.1)  |
| Server 2008 R2 | 20640 ms  | 2263 ms (9.1) | 866 ms (23.8)  |
| Server 2003    | 3570 ms   | 1766 ms (2.0) | 452 ms (7.9)   |
+----------------+-----------+---------------+----------------+
Run Code Online (Sandbox Code Playgroud)

测试中的两个线程中的每一个都运行了100万次迭代.那些睾丸都在相同的机器上运行.Win Server 2008和Server 2003号码来自双引导计算机.Win 7机器具有完全相同的规格,但是是不同的物理机器.这款机器是配备Core i5-2520M 2.5GHz的联想T420笔记本电脑.显然不是服务器类机器,但我在真正的服务器类硬件上得到了类似的结果.括号中的数字是第一列与给定列的比率.

为什么这个操作系统似乎会为CPU内核级同步引入额外费用的任何解释?或者你知道一些可能影响这个的配置/调整参数吗?

虽然它会使这个非常冗长和长篇文章更长,我可以发布上述数字来自的测试代码的增强版本,如果有人想要的话.这将显示循环逻辑和测试的自旋锁版本的强制执行.

扩展背景

试图回答一些关于为什么以这种方式完成事情的不可避免的问题.我也是一样......当我读一篇文章时,我常常想知道为什么我甚至都在问.所以这里有一些尝试澄清:

  • 申请是什么?它是一个数据库服务器.在某些情况下,客户在与服务器相同的计算机上运行客户端应用程序.在这种情况下,使用共享内存进行通信(与套接字相比)更快.这个问题与共享内存通信有关.
  • 工作量是否真的取决于事件?嗯...共享内存comm是使用命名信号量实现的.客户端发信号通知信号量,服务器读取数据,服务器在响应准备好时发信号通知客户端的信号量.在其他平台上,它快速致盲.在Win2008R2上,它不是.它也非常依赖于客户应用程序.如果他们向服务器写了很多小请求,那么这两个进程之间就会有很多通信.
  • 可以使用轻量级锁吗?有可能.我已经在看那个了.但它独立于原始问题.

usr*_*usr 3

从评论中提取答案:

也许服务器没有设置高性能电源计划?Win2k8 可能有不同的默认值。许多服务器默认情况下不是这样,这会严重影响性能。

OP 确认这是根本原因。

这是造成这种行为的一个有趣的原因。当我正在做一些完全不同的事情时,这个想法闪现在我的脑海中。