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

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

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

Решение

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

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

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

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

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

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

Другие вопросы по тегам