gpt4 book ai didi

c - Windows 10 上的多线程性能比 Linux 差得多

转载 作者:可可西里 更新时间:2023-11-01 11:43:51 28 4
gpt4 key购买 nike

我将一个多线程 Linux 应用程序移植到 Windows,并在运行 Windows 10 专业版的服务器上对其进行测试。与在相同双启动硬件上运行的 Linux 版本的性能相比,Windows 版本的性能非常糟糕。我将代码简化为一个表现出相同症状的小型多线程示例。我希望 SO 社区能够就此应用程序在 Windows 和 Linux 之间存在这种性能差异的原因提供一些见解,并提供有关如何解决该问题的建议。

我正在测试的机器具有双 Intel Xeon Gold 6136 CPU(24/48 物理/逻辑内核)@3.0 GHz(Turbo 加速至 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 上编译,结果将反射(reflect)使用 Windows 示例代码的结果。

我已经尝试过各种 BIOS 设置、提高线程优先级、预分配线程池等,但性能没有任何变化。我不认为这是 虚假共享 的情况,因为 Linux 版本使用几乎相同的代码显示出截然不同的性能。我想知道我的编译方式是否有问题。我正在使用 64 位工具链。

有什么想法吗?

最佳答案

我在多核/多处理器机器上看到过类似的 Cygwin 应用程序问题。据我所知,这在Cygwin中仍然是一个 Unresolved 问题。

我注意到并且您可以尝试的一件事是,将进程固定到单个 CPU 可能会显着提高其性能(但显然也会限制利用多核和多线程并行性的能力)。您可以使用 Windows 任务管理器将进程关联设置为仅一个 CPU/核心,从而将进程固定到单个 CPU。

如果这样做可以显着提高单个线程的性能,那么您就会遇到我注意到的相同问题。而且,我不认为这是您的代码的问题,而是 Cygwin 的问题。

关于c - Windows 10 上的多线程性能比 Linux 差得多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51217320/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com