Локальный набор потоков TBB с использованием combinable или enumerable_thread_specific?

Я хочу запустить tbb::parallel_for для большого набора данных и создать уникальный набор. В теле parallel_for содержится некоторая дополнительная логика, которая определяет, следует ли включать в этот набор каждый подэлемент исходного набора данных. Результирующий набор обычно намного меньше, чем исходный набор данных, и я бы предпочел не вычислять вектор с дубликатами и удалять дубликаты после этого, так как это увеличит использование памяти.

В моей первой реализации используется tbb::concurrent_unordered_set, и при профилировании я заметил значительное узкое место в производительности в методе set.insert().

Моя попытка улучшить это состоит в том, чтобы попытаться использовать локальное хранилище потока для создания набора для каждого потока, а затем объединить наборы в конце, чтобы удалить атомарность.

Несмотря на то, что я прочитал много документации, я по-прежнему не уверен, что лучше подходит tbb::combinable или tbb::enumerable_thread_specific.

Это должно быть довольно распространенным вариантом использования. Может ли кто-нибудь предоставить пример реализации или указать мне пример в Интернете, на который я могу посмотреть?


person Dan    schedule 16.05.2015    source источник


Ответы (1)


Я думаю, вы идете в правильном направлении. Параллельные хеш-таблицы эффективны для большого количества элементов (тысячи). Хотя вы все равно можете попытаться зарезервировать достаточную мощность перед запуском вашего алгоритма и поиграть с коэффициентом загрузки concurrent_unordered_set (установленным на 1), а также попробовать concurrent_hash_map (это быстрее при использовании insert(value) без доступа, также необходимо зарезервировать некоторую мощность) .

И tbb::combinable, и tbb::enumerable_thread_specific используют одну и ту же реализацию бэкенда. Разница только в интерфейсе. В документации есть пример для последнего, я изменил его стиль кусочек:

typedef tbb::enumerable_thread_specific< std::pair<int,int> > CounterType;
CounterType MyCounters (std::make_pair(0,0));

int main() {
    tbb::parallel_for( tbb::blocked_range<int>(0, 100000000),
      [](const tbb::blocked_range<int> &r) {
        CounterType::reference my_counter = MyCounters.local();
        ++my_counter.first; my_counter.second += r.size();
    });

    std::pair<int,int> sum = MyCounters.combine(
        [](std::pair<int,int> x, std::pair<int,int> y) {
            return std::make_pair(x.first+y.first, x.second+y.second);
        });
    printf("Total calls to operator() = %d, "
         "total iterations = %d\n", sum.first, sum.second);
}

И, наконец, попробуйте также альтернативный подход, используйте tbb::parallel_reduce, где вам не нужны дополнительные средства, такие как комбинирование, более того, сокращение выполняется в основном параллельно (логирование только P последовательных шагов, в то время как объединение значений, специфичных для потока, требует последовательного посещения всех P элементов).

person Anton    schedule 16.05.2015