C ++ Использование `.reserve ()` для дополнения `std :: vector`s как способ защиты от аннулирования многопоточного кэша и ложного совместного использования - PullRequest
3 голосов
/ 16 декабря 2011

У меня есть программа с общей структурой, показанной ниже. В принципе, у меня есть вектор объектов. Каждый объект имеет векторы-члены, и один из них является вектором структур, которые содержат больше векторов. Благодаря многопоточности объекты работают параллельно, выполняя вычисления, которые включают в себя значительный доступ и модификацию элементов вектора-члена. К одному объекту одновременно обращается только один поток, и он копируется в стек этого потока для обработки.

Проблема в том, что программе не удается масштабировать до 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;
}

1 Ответ

2 голосов
/ 16 декабря 2011

Ну, последний вектор, скопированный в нить 0, равен objs[0].c.Первый вектор, скопированный в потоке 1: objs[1].a[0].a.Таким образом, если их два блока распределенных данных занимают одну и ту же строку кэша (64 байта или что-то еще для этого ЦП), у вас будет ложное совместное использование.

И, конечно, то же самое верноиз любых двух задействованных векторов, но ради конкретного примера я сделал вид, что поток 0 запускается первым и выполняет свое распределение до того, как поток 1 начинает выделять, и что распределитель имеет тенденцию делать последовательные выделения смежными.

reserve() может помешать частям этого блока, на котором вы на самом деле воздействуете, занимать одну и ту же строку кэша.Другим вариантом может быть выделение памяти для каждого потока - если блоки этих векторов выделяются из разных пулов, они не могут занимать одну и ту же строку, если не занимают пулы.

Если у вас нетв распределителях потоков проблема может быть в распределении памяти, если DoWork много перераспределяет векторы.Или это может быть конфликт на любом другом общем ресурсе, используемом DoWork.По сути, представьте, что каждый поток тратит 1 / K своего времени на выполнение чего-то, что требует глобального монопольного доступа.Тогда может показаться, что он достаточно хорошо распараллеливается до определенного числа J <= K, и в этот момент получение эксклюзивного доступа значительно сказывается на ускорении, поскольку ядра тратят значительную часть времени простоя.За исключением K ядер, с дополнительными ядрами улучшения почти не происходит, потому что общий ресурс не может работать быстрее. </p>

В конце этого, представьте себе какую-то работу, которая тратит 1 / K своего времени, удерживая глобальную блокировку.и (K-1) / K своего времени ожидания на I / O.Тогда проблема кажется смущающей параллелью почти до K потоков (независимо от количества ядер), после чего она останавливается.

Так что, не сосредотачивайтесь на ложном совместном использовании, пока не исключите true Поделиться; -)

...