Почему std :: for_each быстрее, чем __gnu_parallel :: for_each - PullRequest
0 голосов
/ 15 января 2019

Я пытаюсь понять, почему std::for_each, работающий в одном потоке, * в 1002 * раза быстрее, чем __gnu_parallel::for_each в приведенном ниже примере:

Time =0.478101 milliseconds

против

Time =0.166421 milliseconds

Вот код, который я использую для тестирования:

#include <iostream>
#include <chrono>
#include <parallel/algorithm>

//The struct I'm using for timming
struct   TimerAvrg
{
    std::vector<double> times;
    size_t curr=0,n;
    std::chrono::high_resolution_clock::time_point begin,end;
    TimerAvrg(int _n=30)
    {
        n=_n;
        times.reserve(n);
    }

    inline void start()
    {
        begin= std::chrono::high_resolution_clock::now();
    }

    inline void stop()
    {
        end= std::chrono::high_resolution_clock::now();
        double duration=double(std::chrono::duration_cast<std::chrono::microseconds>(end-begin).count())*1e-6;
        if ( times.size()<n)
            times.push_back(duration);
        else{
            times[curr]=duration;
            curr++;
            if (curr>=times.size()) curr=0;}
    }

    double getAvrg()
    {
        double sum=0;
        for(auto t:times)
            sum+=t;
        return sum/double(times.size());
    }
};



int main( int argc, char** argv )
{
    float sum=0;
    for(int alpha = 0; alpha <5000; alpha++)
    {
        TimerAvrg Fps;
        Fps.start();
        std::vector<float> v(1000000);
        std::for_each(v.begin(), v.end(),[](auto v){ v=0;});
        Fps.stop();
        sum = sum + Fps.getAvrg()*1000;
    }

    std::cout << "\rTime =" << sum/5000<< " milliseconds" << std::endl;
    return 0;
}

Это моя конфигурация:

gcc version 7.3.0 (Ubuntu 7.3.0-21ubuntu1~16.04) 

Intel® Core™ i7-7600U CPU @ 2.80GHz × 4

htop, чтобы проверить, работает ли программа в одном или нескольких потоках

g++ -std=c++17 -fomit-frame-pointer -Ofast -march=native -ffast-math -mmmx -msse -msse2 -msse3 -DNDEBUG -Wall -fopenmp benchmark.cpp -o benchmark 

Этот же код не компилируется с gcc 8.1.0. Я получил это сообщение об ошибке:

/usr/include/c++/8/tr1/cmath:1163:20: error: ‘__gnu_cxx::conf_hypergf’ has not been declared
   using __gnu_cxx::conf_hypergf;

Я уже проверил пару сообщений, но они либо очень старые, либо не одной и той же проблемы.

Мои вопросы:

Почему параллельно медленнее?

Я использую неправильные функции?

В cppreference говорится, что gcc с Standardization of Parallelism TS не поддерживается (упомянуто красным цветом в таблице), и мой код работает параллельно!?

Ответы [ 2 ]

0 голосов
/ 15 января 2019

Ваш тест неисправен, я даже удивлен, что для его запуска требуется время.

Вы писали: std :: for_each (v.begin (), v.end (), [] (auto v) {v = 0;});

Поскольку v является локальным аргументом operator() без чтения, я ожидаю, что он будет удален вашим компилятором. Поскольку теперь у вас есть цикл с телом, этот цикл можно удалить, так как эффекта не наблюдается. И так же, вектор может быть удален, так как у вас нет читателей.

Итак, без каких-либо побочных эффектов все это можно было бы удалить. Если бы вы использовали параллельный алгоритм, есть вероятность, что у вас есть какая-то синхронизация, которая значительно усложняет оптимизацию, поскольку в другом потоке могут быть побочные эффекты? Доказать это не сложнее, не говоря уже о побочных эффектах управления потоками, которые могут существовать?

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

0 голосов
/ 15 января 2019

Ваша функция [](auto v){ v=0;} чрезвычайно просто.

Функцию можно заменить одним вызовом memset или использовать инструкции SIMD для однопоточного параллелизма. Зная, что он перезаписывает то же состояние, которое изначально имел вектор, весь цикл можно было оптимизировать. Оптимизатору может быть проще заменить std::for_each, чем параллельную реализацию.

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

Многопоточный параллелизм часто стоит только для вычислительно дорогих задач. v=0 - это одна из наименее дорогих вычислительных операций.

...