Ошибка cilk_for для распараллеливания над std :: set, пропущенная operator- ()

0

Я пытался использовать cilk_for для итерации по множеству. оказывается, что у него нет operator- (..), определенного для множества. в руководстве Cilk_for объясняется причина, но не приводится ни одного примера для обработки такого случая. они говорят, что необходимо предоставить следующее: но я не уверен, где и как ставить ценности:

ссылка здесь

difference_type operator-(termination_type, variable_type); // where to use this?

//my example
auto func = std::bind (my_functor, std::placeholders::_1);  

std::set<vertex_id> vertex_set;
// fill the vertex_set with some vertex ids

cilk_for (auto it = vertex_set.begin(); it!=vertex_set.end(); ++it) {
   func(*it) 
}

Как и где я могу предоставить operator- (..) для компилятора cilk для его обработки?

variable_type - это set::iterator. тип разница является difference_type (ptrdiff_t), но то, что является termination_type согласно их примеру?

Теги:
c++11
parallel-processing
cilk-plus

1 ответ

1
Лучший ответ

Проблема не в терминальном выражении, это! = Vertex_set.end(), что прекрасно. В этом случае term_type просто std :: set :: iterator.

Проблема в том, что std :: set не имеет итераторов с произвольным доступом и, следовательно, не имеет operator- или operator+ =. Другими словами, вы не можете продвигать итератор в постоянное время, и вы не можете вычислить расстояние между двумя итераторами в постоянное время. Нет разумного способа добавить этих пропавших операторов. Цикл cilk_for просто не предназначен для перемещения линейных или древовидных контейнеров, таких как список или набор.

Чтобы понять, почему это так, рассмотрим, что необходимо для выполнения цикла параллельно. Во-первых, вы должны вырезать данные, настроенные на грубые равные части, тогда вы должны отправить эти куски параллельным рабочим. Каждая итерация цикла должна быть в состоянии немедленно перейти к блоку ввода, на котором он должен работать. Если ему необходимо линейно пройти весь контейнер, чтобы найти начало его куска, то вы в значительной степени победили цель использования параллельного цикла.

Существует несколько способов улучшить ситуацию. Во-первых, стоит ли считать, что ваш набор достаточно велик, и набор операций, которые вы делаете на нем достаточно дорого, чтобы вообще беспокоиться о параллелизме? Если нет, используйте серийный цикл и забудьте об этом. Если да, рассмотрите возможность использования другой структуры данных. Можете ли вы заменить набор на векторную структуру данных. Поиск в Интернете, и вы найдете такие вещи, как AssocVector в Loki, который имеет тот же интерфейс, что и std :: set, но использует вектор в своей реализации и имеет итераторы с произвольным доступом; его нужно легко модифицировать или обернуть, чтобы дать в интерфейсе std :: set вместо std :: map. Он часто будет выполнять std :: set или std :: map.

Если my_functor выполняет значительную работу, вы можете амортизировать стоимость линейного обхода с помощью простого последовательного цикла, содержащего икру:

for (auto it = vertex_set.begin(); it!=vertex_set.end(); ++it) {
    cilk_spawn func(*it);
}
cilk_sync;

Остерегайтесь, однако, что если функция func() относительно невелика, то накладные расходы на повторяющиеся икры будут иметь заметное негативное влияние на производительность.

Кроме того, вы можете использовать cilk_for, перебирать индексы вместо использования итераторов, как в следующем коде:

cilk_for (std::size_t i = 0; i < vertex_set.size(); ++i) {
    auto it = vertex_set.begin();
    std::advance(it, i);  // Slow step!
    func(*it);
}

Если ваш набор достаточно велик, вы можете уменьшить количество вызовов до std :: advance, предварительно перебирая набор в вектор итераторов. Затем вы можете параллельно перебирать куски:

// Each element of this vector is the start of a chunk of the set.
std::vector<std::set<vertex_id>::iterator> chunks;
// Create 1000 chunks
const auto chunk_size = vertex_set.size() / 1000;
auto chunk = vector_set.begin();
for (int i = 0; i < 1000; ++i) {
    chunks.push(chunk);
    advance(chunk, chunk_size);
}
chunks.push(vector_set.end());

// Now, process the chunks in parallel
cilk_for (int i = 0; i < 1000; ++i) {
    // Iterate over elements in the chunk.
    for (auto it = chunks[i]; it != chunks[i + 1]; ++it)
        func(*it);
}

В зависимости от вашей ситуации существует много вариаций по двум вышеуказанным темам. Обязательно выполните некоторые профилирования и сроки, чтобы помочь вам выбрать наилучший подход.

Я остановлюсь, отметив, что рассматривается вариант cilk_for, который будет работать с любой структурой данных, которая может быть подразделена рекурсивно (как дерево может). Такая вариация решит вашу проблему напрямую, но я не могу обещать, когда такая вещь будет доступна.

  • 0
    спасибо за ответы Пабло. Я использую Boost Graph для своих параллельных вычислений, используя tbb и cilk. Я попробую эти подходы.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню