c - Windows 10 上的多线程性能比 Linux 差很多
问题描述
我将一个多线程 Linux 应用程序移植到 Windows,并在运行 Windows 10 Pro 的服务器上对其进行测试。与在相同双引导硬件上运行的 Linux 版本的性能相比,Windows 版本的性能非常糟糕。我将代码简化为一个显示相同症状的小型多线程示例。我希望 SO 社区可以提供一些见解,了解为什么 Windows 和 Linux 之间存在这种应用程序的性能差异,以及如何解决问题的建议。
我正在测试的机器具有双 Intel Xeon Gold 6136 CPU(24/48 个物理/逻辑内核)@3.0 GHz(涡轮增压至 3.6 GHz)和 128 GB 内存。机器设置为双启动 CentOS 或 Windows 10。没有运行 Windows Hypervisor(Hyper-V 已禁用)。NUMA 被禁用。在我正在执行的测试中,每个线程都应该能够在单独的核心上运行;没有其他消耗处理器的应用程序正在运行。
该应用程序执行复杂的转换以将约 15 MB 的输入数据集转换为约 50 MB 的输出数据。我编写了简化的多线程测试(仅计算、仅数据移动等)以缩小问题范围。仅计算测试显示没有性能差异,但数据复制方案确实如此。可重复的场景是让每个线程将数据从其 15 MB 输入缓冲区复制到其 50 MB 输出缓冲区。输入缓冲区中的每个“int”连续写入输出缓冲区 3 次。使用 N 个线程进行 100 次迭代的几乎相同的 Linux 和 Windows 代码的结果如下所示:
Windows (or cygwin) Linux (native)
Threads Time (msec) Time (msec)
1 4200 3000
2 4020 2300
3 4815 2300
4 6700 2300
5 8900 2300
6 14000 2300
7 16500 2300
8 21000 2300
12 39000 2500
16 75000 3000
24 155000 4000
以上时间是工作线程中的处理时间。结果不包括分配内存或启动线程的任何时间。似乎线程在 Linux 下是独立运行的,但在 Windows 10 下却不是。
我用于 Windows 测试的完整 C 代码在这里:
//
// Thread test program
//
// To compile for Windows:
// vcvars64.bat
// cl /Ox -o windowsThreadTest windowsThreadTest.c
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <windows.h>
#include <process.h>
#define __func__ __FUNCTION__
//
// Global data
//
HANDLE *threadHandleArray = NULL;
DWORD *threadIdArray = NULL;
//
// Time keeping
//
double *PCFreq = NULL;
__int64 *CounterStart = NULL;
void StartCounter(int whichProcessor)
{
LARGE_INTEGER li;
DWORD_PTR old_mask;
if ( !PCFreq )
{
printf("No freq array\n");
return;
}
if(!QueryPerformanceFrequency(&li))
{
printf("QueryPerformanceFrequency failed!\n");
return;
}
PCFreq[whichProcessor] = ((double)(li.QuadPart))/1000.0;
QueryPerformanceCounter(&li);
CounterStart[whichProcessor] = li.QuadPart;
}
double GetCounter()
{
LARGE_INTEGER li;
DWORD_PTR old_mask;
DWORD whichProcessor;
whichProcessor = GetCurrentProcessorNumber();
if ( CounterStart && CounterStart[whichProcessor] != 0 )
{
QueryPerformanceCounter(&li);
return ((double)(li.QuadPart-CounterStart[whichProcessor]))/PCFreq[whichProcessor];
}
else
return 0.0;
}
typedef struct
{
int retVal;
int instance;
long myTid;
int verbose;
double startTime;
double elapsedTime;
double totalElapsedTime;
struct {
unsigned intsToCopy;
int *inData;
int *outData;
} rwInfo;
} info_t;
int rwtest( unsigned intsToCopy, int *inData, int *outData)
{
unsigned i, j;
//
// Test is simple. For every entry in input array, write 3 entries to output
//
for ( j = i = 0; i < intsToCopy; i++ )
{
outData[j] = inData[i];
outData[j+1] = inData[i];
outData[j+2] = inData[i];
j += 3;
}
return 0;
}
DWORD WINAPI workerProc(LPVOID *workerInfoPtr)
{
info_t *infoPtr = (info_t *)workerInfoPtr;
infoPtr->myTid = GetCurrentThreadId();
double endTime;
BOOL result;
SetThreadPriority(threadHandleArray[infoPtr->instance], THREAD_PRIORITY_HIGHEST);
// record start time
infoPtr->startTime = GetCounter();
// Run the test
infoPtr->retVal = rwtest( infoPtr->rwInfo.intsToCopy, infoPtr->rwInfo.inData, infoPtr->rwInfo.outData );
// end time
endTime = GetCounter();
infoPtr->elapsedTime = endTime - infoPtr->startTime;
if ( infoPtr->verbose )
printf("(%04x): done\n", infoPtr->myTid);
return 0;
}
//
// Main Test Program
//
int main(int argc, char **argv)
{
int i, j, verbose=0, loopLimit;
unsigned size;
unsigned int numThreads;
info_t *w_info = NULL;
int numVirtualCores;
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
if ( argc != 4 )
{
printf("windowsThreadTest <numLoops> <numThreads> <Input size in MB>\n");
return -1;
}
numVirtualCores = sysinfo.dwNumberOfProcessors;
printf("%s: There are %d processors\n", __func__, numVirtualCores);
// Setup Timing
PCFreq = (double *)malloc(numVirtualCores * sizeof(double));
CounterStart = (__int64 *)malloc(numVirtualCores * sizeof(__int64));
if (!PCFreq || !CounterStart)
goto free_and_exit;
for ( i = 0; i < numVirtualCores; i++)
StartCounter(i);
//
// Process input args
//
loopLimit = atoi( argv[1] );
numThreads = atoi( argv[2] );
size = atoi( argv[3] ) * 1024 * 1024;
//
// Setup data array for each thread
//
w_info = (info_t *)malloc( numThreads * sizeof(info_t) );
if ( !w_info )
{
printf("Couldn't allocate w_info of size %zd, numThreads=%d\n", sizeof(info_t), numThreads);
goto free_and_exit;
}
memset( w_info, 0, numThreads * sizeof(info_t) );
//
// Thread Handle Array
//
threadHandleArray = (HANDLE *)malloc( numThreads * sizeof(HANDLE) );
if ( !threadHandleArray )
{
printf("Couldn't allocate handleArray\n");
goto free_and_exit;
}
//
// Thread ID Array
//
threadIdArray = (DWORD *)malloc( numThreads * sizeof(DWORD) );
if ( !threadIdArray )
{
printf("Couldn't allocate IdArray\n");
goto free_and_exit;
}
//
// Run the test
//
printf("Read/write testing... threads %d loops %lu input size %u \n", numThreads, loopLimit, size);
for ( j = 0; j < loopLimit; j++ )
{
//
// Set up the data for the threads
//
for ( i = 0; i < numThreads; i++ )
{
int idx;
int *inData;
int *outData;
unsigned inSize;
unsigned outSize;
inSize = size; // in MB
outSize = size * 3; // in MB
//
// Allocate input buffer
//
inData = (int *) malloc( inSize );
if ( !inData )
{
printf("Error allocating inData of size %zd\n", inSize * sizeof(char));
goto free_and_exit;
}
else
{
if ( verbose )
printf("Allocated inData of size %zd\n", inSize * sizeof(char));
}
//
// Allocate output buffer 3x the size of the input buf
//
outData = (int *) malloc( outSize * 3 );
if ( !outData )
{
printf("Error allocating outData of size %zd\n", outSize * sizeof(char));
goto free_and_exit;
}
else
{
if ( verbose )
printf("Allocated outData of size %zd\n", outSize * sizeof(char));
}
//
// Put some data into input buffer
//
w_info[i].rwInfo.intsToCopy = inSize/sizeof(int);
for ( idx = 0; idx < w_info[i].rwInfo.intsToCopy; idx++)
inData[idx] = idx;
w_info[i].rwInfo.inData = inData;
w_info[i].rwInfo.outData = outData;
w_info[i].verbose = verbose;
w_info[i].instance = i;
w_info[i].retVal = -1;
}
//
// Start the threads
//
for ( i = 0; i < numThreads; i++ )
{
threadHandleArray[i] = CreateThread( NULL, 0, workerProc, &w_info[i], 0, &threadIdArray[i] );
if ( threadHandleArray[i] == NULL )
{
fprintf(stderr, "Error creating thread %d\n", i);
return 1;
}
}
//
// Wait until all threads have terminated.
//
WaitForMultipleObjects( numThreads, threadHandleArray, TRUE, INFINITE );
//
// Check the return values
//
for ( i = 0; i < numThreads; i++ )
{
if ( w_info[i].retVal < 0 )
{
printf("Error return from thread %d\n", i);
goto free_and_exit;
}
if ( verbose )
printf("Thread %d, tid %x %f msec\n", i, (unsigned)w_info[i].myTid, w_info[i].elapsedTime);
w_info[i].totalElapsedTime += w_info[i].elapsedTime;
}
//
// Free up the data from this iteration
//
for ( i = 0; i < numThreads; i++ )
{
free( w_info[i].rwInfo.inData );
free( w_info[i].rwInfo.outData );
CloseHandle( threadHandleArray[i] );
}
}
//
// All done, print out cumulative time spent in worker routine
//
for ( i = 0; i < numThreads; i++ )
{
printf("Thread %d, loops %d %f msec\n", i, j, w_info[i].totalElapsedTime);
}
free_and_exit:
if ( threadHandleArray )
free( threadHandleArray );
if ( threadIdArray )
free( threadIdArray );
if ( PCFreq )
free( PCFreq );
if ( CounterStart )
free( CounterStart );
if ( w_info )
free( w_info );
return 0;
}
上面的代码很容易更改为使用 pthreads,使用命令行 'gcc -O3 -o pthreadTestLinux pthreadTest.c' 进行编译以获得上述 Linux 结果(如有必要,我可以发布)。如果在 cygwin 环境中使用 gcc 在 Windows 上编译,结果会反映使用 Windows 示例代码的结果。
我尝试了各种 BIOS 设置、提高线程优先级、预分配线程池等,但性能没有任何变化。我不认为这是错误共享的情况,因为 Linux 版本使用几乎相同的代码显示完全不同的性能。我想知道我的编译方式是否有问题。我正在使用 64 位工具链。
有任何想法吗?
解决方案
我在多核/多处理器机器上看到了 Cygwin 应用程序的类似问题。据我所知,这在 Cygwin 中仍然是一个未解决的问题。
我注意到并且您可以尝试的一件事是,将进程固定到单个 CPU 可能会显着提高其性能(但显然也会限制利用多核和多线程并行性的能力)。您可以使用 Windows 任务管理器将进程关联设置为仅一个 CPU/内核,从而将进程固定到单个 CPU。
如果这样做显着提高了单个线程的性能,那么您会看到我注意到的相同问题。而且,我认为这不是您的代码的问题,而是 Cygwin 的问题。
推荐阅读
- angular - Angular 2:找不到 ngOnInit/ngOnDestroy
- sas - SAS: Apparent symbolic reference not resolved for Macro reference
- google-apps-script - 秘密可以存储在 Code.gs 文件中吗?
- php - 从 json_encode 返回失败
- c# - Bouncycastle 问题使用给定的公钥验证给定的 ECC 签名
- linux - 如何从不同的文件调用 wait_event_interruptible。
- python - 为什么 eval('"\x27"') == eval('"\\x27"')?
- php - Laravel未定义变量中的ErrorException
- yarnpkg - Yarn 如何指定包的安装路径
- java - MongoDB 通过 $in 命令获取匹配数