10

当使用内核对象来同步运行在不同 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 );
  }

使用可以双启动到 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;
}

更多细节

我更新了测试以强制线程运行一次迭代并在每个循环中强制切换到下一个线程。每个线程都会在每个循环结束时发出下一个要运行的线程的信号(循环方式)。而且我还更新了它以使用自旋锁作为信号量(它是一个内核对象)的替代品。

我测试的所有机器都是 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 之外的所有机器的典型比率为 2 到 4 倍(在多个 CPU 上运行需要 2 到 4 倍的时间)。但是在 Win2008R2 上,这个比率是 9 倍。
  • 但是......我无法重现所有 Win2008R2 机器上的减速。我在 4 上进行了测试,它出现在其中的 3 上。所以我不禁想知道是否有某种配置设置或性能调整选项可能会影响这一点。我已经阅读了性能调优指南,查看了各种设置,并更改了各种设置(例如,后台服务与前台应用程序),但行为没有任何区别。
  • 它似乎不一定与物理内核之间的切换有关。我最初怀疑它与重复访问不同内核上的全局数据的成本有关。但是当运行一个使用简单自旋锁进行同步的测试版本(不是内核对象)时,在不同的 CPU 上运行各个线程在所有操作系统类型上都相当快。多 CPU 信号量同步测试与多 CPU 自旋锁测试的比率通常为 10 倍到 15 倍。但是对于 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)   |
+----------------+-----------+---------------+----------------+

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

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

虽然这会使这篇非常冗长且冗长的帖子变得更长,但如果有人想要,我可以发布上述数字来自的测试代码的增强版本。这将显示循环逻辑和测试的自旋锁版本的执行。

扩展背景

试图回答一些关于为什么以这种方式完成的不可避免的问题。我也一样……当我读到一个帖子时,我常常想知道我为什么还要问。所以这里有一些尝试澄清:

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

4 回答 4

3

从评论中提取答案:

也许服务器没有设置为高性能电源计划?Win2k8 可能有不同的默认值。许多服务器不是默认的,这对性能造成了很大的影响。

OP确认这是根本原因。

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

于 2013-01-21T23:46:06.717 回答
0

很可能是操作系统安装配置不同。也许慢速系统被配置为不允许同时调度进程中的多个线程。如果其他一些高优先级进程总是(或大部分)准备好运行,唯一的选择是让您的线程按顺序运行,而不是并行运行。

于 2013-01-18T17:51:10.323 回答
0

我在这里添加了这个额外的“答案”信息,而不是把它埋在我过长的 OP 中。@usr 通过电源管理选项建议为我指明了正确的方向。OP 中的人为测试以及原始问题涉及不同线程之间的大量握手。真实世界应用程序中的握手跨越不同的进程,但测试表明,如果是线程或进程进行握手,结果没有差异。在 Windows Server 2008 R2 中,当 CPU使用率较低(例如,5% 到 10%)运行时,电源设置似乎极大地影响了 CPU 间信号量(内核同步对象)的共享。我对这一点的理解完全基于测量和计时应用。

Serverfault 上的一个相关问题也谈到了这一点。

测试设置

操作系统电源选项设置Windows Server 2008 R2 的默认电源计划为“平衡”。将其更改为“高性能”选项有助于此测试的性能。特别是,“更改高级电源设置”下的一项指定设置似乎是关键设置。高级设置在处理器电源管理下有一个选项,称为最低处理器状态。平衡计划下的默认值似乎是 5%。在我的测试中将其更改为 100% 是关键。

BIOS 设置此外,BIOS 设置对本次测试的影响很大。我确信这在硬件上会有很大差异,但我测试的主机有一个名为“CPU 电源管理”的设置。BIOS 设置的描述是“启用或禁用在没有系统活动时自动停止(原文如此)微处理器时钟的省电功能。” 我将此选项更改为“已禁用”。

实验结果

显示的两个测试用例是:

  • (一个简单的。OP 中包含的修改版本。这个简单的测试在两个 CPU 上的两个线程之间的每次迭代中强制执行循环切换。每个线程运行 100 万次迭代(因此,有 200 万次跨 CPU 的上下文切换)。
  • (b) 现实世界。真实世界的客户端/服务器测试,其中客户端通过共享内存向服务器发出许多“小”请求,并与全局命名信号量同步。

三个测试场景是:

  • (i) 平衡。Windows Server 2008 R2 的默认安装,它使用平衡电源计划。
  • (ii) 高性能。我将电源选项从“平衡”更改为“高性能”。等效地,通过将​​如上所述的“最低处理器状态 CPU”选项设置为 100%(从 5%),会产生相同的结果。
  • (iii) BIOS。如上所述,我禁用了 CPU 电源管理 BIOS 选项,并选择了高性能电源选项。

给出的时间以秒为单位:

╔════════════════╦═════════════╦═══════════════╦════════════╗
║                ║ (i)Balanced ║ (ii) HighPerf ║ (iii) BIOS ║
╠════════════════╬═════════════╬═══════════════╬════════════╣
║ (a) Simple     ║ 21.4 s      ║ 9.2 s         ║ 4.0 s      ║
║ (b) Real World ║ 9.3 s       ║ 2.2 s         ║ 1.7 s      ║
╚════════════════╩═════════════╩═══════════════╩════════════╝

因此,在进行了两项更改(操作系统和 BIOS)之后,实际测试和人为测试的运行速度都比默认安装和默认 BIOS 设置下快了大约5 倍。


在测试这些案例时,我有时会遇到无法解释的结果。当 CPU 很忙时(一些后台进程会启动),测试会运行得更快。我会把它归档在我的脑海里,然后困惑一会儿。但现在它是有道理的。当另一个进程运行时,它会将 CPU 使用率提高到使其保持在高功率状态所需的任何阈值,并且上下文切换会很快。我仍然不知道哪个方面慢(主要成本隐藏在 WaitForSingleObject 调用中),但最终结果现在都有意义。

于 2013-01-22T00:57:14.470 回答
-3

这不是一个合理的基准,您的信号量总是在同一个进程中被 frobbed(因此大概在同一个 CPU/内核上)。在实际情况下,锁定成本的一个重要部分是当不同的 CPU/内核争夺对内存区域的独占访问(在缓存之间来回反弹)时所涉及的内存访问。寻找更多真实世界的基准(对不起,不是我的领域),o(甚至更好)使用(人为但现实的)测试数据测量(一些缩减版本)您的应用程序。

[基准测试的测试数据永远不应该是测试或回归测试的数据:后来在(可能很少使用的)极端情况下,您需要“典型”运行进行基准测试。]

于 2013-01-20T01:38:03.793 回答