Передача адреса итератора в функцию в STL :: for_each - PullRequest
1 голос
/ 21 октября 2011

У меня есть функция, которую я в конечном итоге хочу распараллелить.

В настоящее время я называю вещи в цикле for.

double temp = 0;
int y = 123;  // is a value set by other code
for(vector<double>::iterator i=data.begin(); i != data.end(); i++){
    temp += doStuff(i, y);
}

doStuff должен знать как далеко вниз по списку. Поэтому я использую i - data.begin () для расчета.

Далее, я бы хотел использовать функцию stl :: for_each. Моя задача состоит в том, чтобы мне нужно было передать адрес моего итератора и значение y. Я видел примеры использования bind2nd для передачи параметра в функцию, но как я могу передать адрес итератора в качестве первого параметра?

Повышение функций FOREACH также выглядит возможным, однако я не знаю, будет ли оно автоматически распараллеливаться, как в версии STL.

Мысли, идеи, предложения?

Ответы [ 3 ]

3 голосов
/ 22 октября 2011

Если вы хотите здесь получить реальное распараллеливание, используйте

  • GCC с оптимизацией векторизации дерева (-O3) и SIMD (например, -march = native для получения поддержки SSE).Если операция (dostuff) нетривиальна, вы можете сделать это раньше времени (std::transform или std::for_each) и накапливать следующее (std::accumulate), так как накопление будет оптимизировано, как ничто другое в инструкциях SSE!

    void apply_function(double& value)
    {
         value *= 3; // just a sample...
    }
    
    // ...
    
    std::vector<double> data(1000);
    std::for_each(data.begin(), data.end(), &apply_function);
    double sum = std::accumulate(data.begin(), data.end(), 0);
    

Обратите внимание , что, хотя это на самом деле не будет выполняться в нескольких потоках, увеличение производительности будет огромным, поскольку инструкции SSE4 могут обрабатывать многие плавающие операции * в параллели _на одном ядре_.

Если вы хотели истинного параллелизма, используйте один из следующих

Параллельный режим GNU

Компиляцияс g++ -fopenmp -D_GLIBCXX_PARALLEL:

__gnu_parallel::accumulate(data.begin(), data.end(), 0.0);

OpenMP напрямую

Компиляция с g++ -fopenmp

double sum = 0.0;
#pragma omp parallel for reduction (+:sum)
for (size_t i=0; i<data.end(); i++)
{
    sum += do_stuff(i, data[i]);
}

Это приведет к параллелизации циклана столько потоков (команда OMP), сколько есть (логических) ядер ЦП на реальной машине, и результат «волшебным образом» объединяется и синхронизируется.

Заключительные замечания:

Вы можете смоделировать двоичную функцию для for_each, используя stateful функциональный объект.Это не совсем рекомендуемая практика.Это также будет казаться очень неэффективным (при компиляции без оптимизации это так).Это связано с тем, что функциональные объекты передаются по значению через STL.Однако разумно ожидать, что компилятор полностью оптимизирует потенциальные накладные расходы, особенно для простых случаев, таких как:

struct myfunctor
{
    size_t index; 
    myfunctor() : index(0) {}

    double operator()(const double& v) const
    {
        return v * 3; // again, just a sample
    }
};

// ...
std::for_each(data.begin(), data.end(), myfunctor());
1 голос
/ 22 октября 2011

Это довольно просто, если вы можете изменить doStuff так, чтобы он брал значение текущего элемента отдельно от индекса, в котором находится текущий элемент. Рассмотрим:

struct context {
    std::size_type _index;
    int            _y;
    double         _result;
};

context do_stuff_wrapper(context current, double value)
{
    current._result += doStuff(current._index, value, current._y);
    current._index++;
}

context c = { 0, 123, 0.0 };
context result = std::accumulate(data.begin(), data.end(), c, do_stuff_wrapper);

Обратите внимание, однако, что алгоритмы стандартной библиотеки не могут «автоматически распараллеливать», поскольку вызываемые ими функции могут иметь побочные эффекты (компилятор знает, возникают ли побочные эффекты, а функции библиотеки - нет). Если вам нужен параллельный цикл, вам придется воспользоваться специальной библиотекой алгоритмов распараллеливания, такой как PPL или TBB.

1 голос
/ 22 октября 2011

temp += doStuff( i, y ); не может быть автоматически распараллелен. Оператор += плохо работает с параллелизмом.

Далее алгоритмы stl ничего не распараллеливают. Как Visual Studio, так и GCC имеют параллельные алгоритмы, подобные std::for_each. Если это то, что вам нужно, вам придется их использовать.

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

Возможно, вы перепутали распараллеливание с развертыванием цикла, что является обычной оптимизацией в реализациях std::for_each.

...