当使用内核对象来同步运行在不同 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 上,它不是。它也非常依赖于客户的应用程序。如果他们用大量对服务器的小请求编写它,那么两个进程之间就会有很多通信。
- 可以使用轻量级锁吗?可能。我已经在看那个了。但它独立于原始问题。