Windows、MSVC和OpenMP中的线程亲和性

6
我想将代码中的线程绑定到每个物理核心。在GCC中,我成功地使用了sched_setaffinity来实现这一点,因此不再需要设置export OMP_PROC_BIND=true。我想在Windows下使用MSVC做同样的事情。Windows和Linux使用不同的线程拓扑。Linux会分散线程,而Windows则使用紧凑形式。换句话说,在具有四个内核和八个超线程的Linux系统中,我只需要将线程绑定到前四个处理单元。在Windows中,我将它们设置为每隔一个处理单元设置一次。
我已经成功地使用SetProcessAffinityMask实现了这一点。当我在进程上右键单击并点击“设置关联性”时,可以从Windows任务管理器中看到每隔一个CPU被设置(我的八个超线程系统上的0、2、4、6)。问题是当我运行代码时,效率是不稳定的。有时它几乎是恒定的,但大部分时间都有很大变化。我将优先级改为高,但没有任何区别。在Linux下,效率是稳定的。也许Windows仍在迁移线程?还有其他需要在Windows上绑定线程的事情吗?
以下是我使用的代码:
#ifdef _WIN32   
HANDLE process;
DWORD_PTR processAffinityMask = 0;
//Windows uses a compact thread topology.  Set mask to every other thread
for(int i=0; i<ncores; i++) processAffinityMask |= 1<<(2*i);        
//processAffinityMask = 0x55;
process = GetCurrentProcess();
SetProcessAffinityMask(process, processAffinityMask);
#else
cpu_set_t  mask;
CPU_ZERO(&mask);
for(int i=0; i<ncores; i++) CPU_SET(i, &mask);      
sched_setaffinity(0, sizeof(mask), &mask);       
#endif

编辑:以下是我现在使用的代码,似乎在Linux和Windows上都很稳定

    #ifdef _WIN32   
    HANDLE process;
    DWORD_PTR processAffinityMask;
    //Windows uses a compact thread topology.  Set mask to every other thread
    for(int i=0; i<ncores; i++) processAffinityMask |= 1<<(2*i);
    process = GetCurrentProcess();
    SetProcessAffinityMask(process, processAffinityMask);
    #pragma omp parallel 
    {
        HANDLE thread = GetCurrentThread();
        DWORD_PTR threadAffinityMask = 1<<(2*omp_get_thread_num());
        SetThreadAffinityMask(thread, threadAffinityMask);
    }
    #else
    cpu_set_t  mask;
    CPU_ZERO(&mask);
    for(int i=0; i<ncores; i++) CPU_SET(i, &mask);
    sched_setaffinity(0, sizeof(mask), &mask);
    #pragma omp parallel 
    {
       cpu_set_t  mask;
       CPU_ZERO(&mask);
       CPU_SET(omp_get_thread_num(),&mask);
       pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask); 
    }
    #endif

1
在并行区域内使用GetCurrentThread()获取当前线程的句柄,并根据omp_get_thread_num()的结果将其分配到一个设置了单个位的亲和掩码中。 - Hristo Iliev
@HristoIliev,您的意思是在并行部分内吗?我尝试过了,它为每个线程返回相同的句柄。我会继续尝试... - Z boson
我意味着从并行区域内部调用它,以便所有OpenMP线程都进行调用。在这种情况下,除非您分配到共享变量,否则不应返回相同的线程句柄。 - Hristo Iliev
@HristoIliev,好的,我现在明白了。目前它是稳定的,但我需要再测试几次。对于Linux,我想我可以使用pthread_setaffinity_np?到目前为止似乎可以(并且pthread_self()确实为并行区域中的每个线程返回不同的值)。 - Z boson
我正在尝试做类似的事情,但在Linux中编译时遇到了困难。头文件应该包括什么?我已经有了#define _GNU_SOURCE#include<sched.h>。 - Shervin
显示剩余4条评论
1个回答

1

您应该使用SetThreadAffinityMask函数(请参见MSDN参考)。您正在设置进程的掩码。

您可以使用此代码在OpenMP中获取线程ID

int tid = omp_get_thread_num();

但是上面的代码提供了OpenMP内部的线程ID,而不是系统线程ID。本文将对此进行更详细的解释:

http://msdn.microsoft.com/en-us/magazine/cc163717.aspx

如果您需要明确地使用这些线程,请使用此Intel文档中解释的显式亲和类型

https://software.intel.com/sites/products/documentation/studio/composer/en-us/2011Update/compiler_c/optaps/common/optaps_openmp_thread_affinity.htm


你有一些示例代码吗?我的意思是为每个线程设置掩码的代码。 - Z boson
1
可能有一种方法可以获取与进程相关联的线程,并循环遍历它们。 - Z boson
1
http://msdn.microsoft.com/en-us/library/windows/desktop/ms686852%28v=vs.85%29.aspx - Z boson
1
微软的OpenMP实现使用系统管理的线程池。枚举和绑定所有进程线程会影响一些不属于线程池的线程(例如管理器线程,可能是隐藏的窗口消息循环线程等)。我会在并行区域内执行绑定操作。 - Hristo Iliev
2
顺便说一句,有很多种创建错误亲和掩码的方法会导致问题。如果你要手动设置线程亲和性,你应该查看核心检测示例。 - Chuck Walbourn
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接