3

我有一个具有如下所示一般结构的程序。基本上,我有一个对象向量。每个对象都有成员向量,其中一个是包含更多向量的结构向量。通过多线程,对象被并行操作,进行涉及大量访问和修改成员向量元素的计算。一个对象一次只能被一个线程访问,并被复制到该线程的堆栈中进行处理。

问题是该程序无法扩展到 16 个内核。我怀疑并被告知问题可能是错误共享和/或缓存失效。如果这是真的,那么原因似乎一定是向量分配的内存彼此太近,因为据我了解,这两个问题(简单来说)都是由不同处理器同时访问的近端内存地址引起的。这种推理是否有意义,这可能会发生吗?如果是这样,我似乎可以通过使用 .reserve() 填充成员向量来增加额外的容量来解决这个问题,在向量数组之间留下大量的空内存空间。那么,这一切有意义吗?我完全出去吃午饭了吗?

struct str{
    vector <float> a;   vector <int> b;      vector <bool> c;  };

class objects{
    vector <str> a;     vector <int> b;      vector <float> c;  
    //more vectors, etc ...
    void DoWork();            //heavy use of vectors
};    

main(){
    vector <object> objs;
    vector <object> p_objs = &objs;

    //...make `thread_list` and `attr`
    for(int q=0; q<NUM_THREADS; q++)
        pthread_create(&thread_list[q], &attr, Consumer, p_objs );
    //...
}

void* Consumer(void* argument){
     vector <object>* p_objs = (vector <object>*) argument ;
     while(1){
         index = queued++;  //imagine queued is thread-safe global
         object obj = (*p_objs)[index]        
         obj.DoWork();
         (*p_objs)[index] = obj;
}
4

1 回答 1

2

好吧,在线程 0 中复制的最后一个向量是objs[0].c. 在线程 1 中复制的第一个向量是objs[1].a[0].a。因此,如果他们分配的两个数据块碰巧都占用相同的高速缓存行(64 字节,或者该 CPU 的实际值),那么您将获得错误共享。

当然,所涉及的任何两个向量也是如此,但只是为了一个具体的例子,我假设线程 0 首先运行并在线程 1 开始分配之前进行分配,并且分配器倾向于使连续分配相邻.

reserve()可能会阻止您实际操作的块的部分占用相同的缓存行。另一种选择是每个线程的内存分配——如果这些向量的块是从不同的池分配的,那么它们不可能占据同一行,除非池这样做。

如果您没有每线程分配器,那么问题可能是内存分配器的争用,如果DoWork重新分配向量很多。或者它可能是对 . 使用的任何其他共享资源的争用DoWork。基本上,假设每个线程花费 1/K 的时间做需要全局独占访问的事情。然后它可能看起来相当好地并行化到某个数量 J <= K,此时获取独占访问会显着影响加速,因为核心花费了大量时间空闲。超过 K 个核心,额外的核心几乎没有任何改进,因为共享资源无法更快地工作。

在这荒谬的结尾,想象一些工作花费 1/K 的时间持有全局锁,而 (K-1)/K 的时间等待 I/O。然后问题似乎令人尴尬地并行到几乎多达 K 个线程(与内核数量无关),此时它停止了死机。

所以,在您排除真正的分享之前,不要专注于虚假分享;-)

于 2011-12-16T11:00:05.997 回答