【发布时间】:2018-12-15 11:19:00
【问题描述】:
我将一个多线程 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 次。下面显示了几乎相同的 Linux 和 Windows 代码在 N 线程的 100 次迭代中的结果:
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 所做的任何事情都不应该影响这种代码,但是......
-
也许使用
SetThreadAffinityMask/GetThreadIdealProcessorEx会有所帮助? -
您在任务管理器性能选项卡中看到多少 CPU 图表?
-
您上面显示的时间与程序的输出不同。它们有什么关系?
-
那里有很多代码,很可能你正在 Windows 环境中做一些“Linux-y”......举个简单的例子,你不必要地为每个处理器调用QueryPerformanceFrequency:“ ...只需要在应用初始化时查询频率,并且可以缓存结果" ...移植通常意味着为您的目标平台翻译代码,而不是代码的音译..就像你把日语翻译成英语一样,这没有意义......
标签: c linux windows multithreading cygwin