Parallelle lussen in C++

Ik vraag me af of er een lichte, ongecompliceerde manier is om lussen zoals foren op bereik gebaseerde-forlussen parallel te laten berekenen in C++. Hoe zou je zoiets implementeren? Van Scala ken ik de functies map, filteren foreachen misschien is het ook mogelijk om deze parallel uit te voeren? Is er een gemakkelijke manier om dit te bereiken in C++?

Mijn primaire platform is Linux, maar het zou fijn zijn als het platformonafhankelijk zou werken.


Antwoord 1, autoriteit 100%

Met de parallelle algoritmen in C++17 kunnen we nu het volgende gebruiken:

std::vector<std::string> foo;
std::for_each(
    std::execution::par_unseq,
    foo.begin(),
    foo.end(),
    [](auto&& item)
    {
        //do stuff with item
    });

om lussen parallel te berekenen. De eerste parameter specificeert het uitvoeringsbeleid


Antwoord 2, autoriteit 35%

Wat is uw platform? Je kunt naar OpenMPkijken, hoewel het geen onderdeel is van C++. Maar het wordt breed ondersteund door compilers.

Voor op bereik gebaseerde for-loops, zie bijvoorbeeld OpenMP met C++11-reeksgebaseerde for-loops?.

Ik heb ook weinig documenten gezien op http://www.open-std.orgdie wijzen op enige inspanningen om parallelle constructies/algoritmen op te nemen in toekomstige C++, maar weten niet wat hun huidige status is.

UPDATE

Gewoon een voorbeeldcode toevoegen:

template <typename RAIter>
void loop_in_parallel(RAIter first, RAIter last) {
   const size_t n = std::distance(first, last);
   #pragma omp parallel for
   for (size_t i = 0; i < n; i++) {
       auto& elem = *(first + i);
       // do whatever you want with elem
    }
}

Het aantal threads kan tijdens runtime worden ingesteld via de omgevingsvariabele OMP_NUM_THREADS.


Antwoord 3, autoriteit 26%

std::asynckan een past hier goed, als u de C++runtime het parallellisme wilt laten regelen.

Voorbeeld van cppreference.com:

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <future>
template <typename RAIter>
int parallel_sum(RAIter beg, RAIter end)
{
    auto len = end - beg;
    if(len < 1000)
        return std::accumulate(beg, end, 0);
    RAIter mid = beg + len/2;
    auto handle = std::async(std::launch::async,
                              parallel_sum<RAIter>, mid, end);
    int sum = parallel_sum(beg, mid);
    return sum + handle.get();
}
int main()
{
    std::vector<int> v(10000, 1);
    std::cout << "The sum is " << parallel_sum(v.begin(), v.end()) << '\n';
}

Antwoord 4, autoriteit 22%

Met C++11 kun je een for-lus parallelliseren met slechts een paar regels codes.

Mijn functie parallel_for()(definieer later in de post) splitst een for-lus in kleinere chunks (sub-lussen), en elk chunk wordt toegewezen aan een thread. Hier is het gebruik:

/// Say you want to parallelize this:
for(int i = 0; i < nb_elements; ++i)
    computation(i);    
/// Then you would do:
parallel_for(nb_elements, [&](int start, int end){ 
    for(int i = start; i < end; ++i)
        computation(i); 
});

Mijn parallel_for()werkt ook binnen een klas:

struct My_obj {
    /// Replacing:
    void sequential_for(){
        for(int i = 0; i < nb_elements; ++i)
            computation(i);
    }
    /// By:
    void process_chunk(int start, int end)
    {
        for(int i = start; i < end; ++i)
            computation(i);
    }
    void threaded_for(){
        parallel_for(nb_elements, [this](int s, int e){ 
            this->process_chunk(s, e); 
        } );
    }
};

Eindelijk is hier de implementatie van parallel_for(), plak het in een headerbestand en gebruik het naar believen:

#include <algorithm>
#include <thread>
#include <functional>
#include <vector>
/// @param[in] nb_elements : size of your for loop
/// @param[in] functor(start, end) :
/// your function processing a sub chunk of the for loop.
/// "start" is the first index to process (included) until the index "end"
/// (excluded)
/// @code
///     for(int i = start; i < end; ++i)
///         computation(i);
/// @endcode
/// @param use_threads : enable / disable threads.
///
///
static
void parallel_for(unsigned nb_elements,
                  std::function<void (int start, int end)> functor,
                  bool use_threads = true)
{
    // -------
    unsigned nb_threads_hint = std::thread::hardware_concurrency();
    unsigned nb_threads = nb_threads_hint == 0 ? 8 : (nb_threads_hint);
    unsigned batch_size = nb_elements / nb_threads;
    unsigned batch_remainder = nb_elements % nb_threads;
    std::vector< std::thread > my_threads(nb_threads);
    if( use_threads )
    {
        // Multithread execution
        for(unsigned i = 0; i < nb_threads; ++i)
        {
            int start = i * batch_size;
            my_threads[i] = std::thread(functor, start, start+batch_size);
        }
    }
    else
    {
        // Single thread execution (for easy debugging)
        for(unsigned i = 0; i < nb_threads; ++i){
            int start = i * batch_size;
            functor( start, start+batch_size );
        }
    }
    // Deform the elements left
    int start = nb_threads * batch_size;
    functor( start, start+batch_remainder);
    // Wait for the other thread to finish their task
    if( use_threads )
        std::for_each(my_threads.begin(), my_threads.end(), std::mem_fn(&std::thread::join));
}

Ten slotte kunt u macro’s definiëren om een ​​nog compactere uitdrukking te krijgen:

#define PARALLEL_FOR_BEGIN(nb_elements) parallel_for(nb_elements, [&](int start, int end){ for(int i = start; i < end; ++i)
#define PARALLEL_FOR_END()})

Bezig met het converteren van een sequentie voor:

for(int i = 0; i < nb_elements; ++i)
    computation(i);

Is alleen een kwestie van doen:

PARALLEL_FOR_BEGIN(nb_edges)
{
    computation(i);
}PARALLEL_FOR_END();

Antwoord 5, autoriteit 5%

Dit kan worden gedaan met behulp van threadsspecifiek pthreadsbibliotheekfunctie die kan worden gebruikt om gelijktijdig bewerkingen uit te voeren.

Je kunt hier meer over hen lezen: http://www.tutorialspoint.com/cplusplus/ cpp_multithreading.htm

std::thread kan ook worden gebruikt: http://www.cplusplus.com/ referentie/thread/thread/

Hieronder staat een code waarin ik de thread-ID van elke thread gebruik om de array in twee helften te splitsen:

#include <iostream>
#include <cstdlib>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 2
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
void *splitLoop(void *threadid)
{
   long tid;
   tid = (long)threadid;
   //cout << "Hello World! Thread ID, " << tid << endl;
   int start = (tid * 5);
   int end = start + 5;
   for(int i = start;i < end;i++){
      cout << arr[i] << " ";
   }
   cout << endl;
   pthread_exit(NULL);
}
int main ()
{
   pthread_t threads[NUM_THREADS];
   int rc;
   int i;
   for( i=0; i < NUM_THREADS; i++ ){
      cout << "main() : creating thread, " << i << endl;
      rc = pthread_create(&threads[i], NULL, 
                          splitLoop, (void *)i);
      if (rc){
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
   pthread_exit(NULL);
}

Onthoud ook tijdens het compileren u moet de -lpthreadvlag gebruiken.

Link naar oplossing op ideone: http://ideone.com/kcsw4p


Antwoord 6, autoriteit 3%

De Concurrency::parallel_for (PPL) is ook een van de leuke opties om taakparallellisme te doen.

Genomen uit C++-coderingsoefening – Parallel For – Monte Carlo PI-berekening

int main() {
    srand(time(NULL)); // seed
    const int N1 = 1000;
    const int N2 = 100000;
    int n = 0;
    int c = 0;
    Concurrency::critical_section cs;
    // it is better that N2 >> N1 for better performance
    Concurrency::parallel_for(0, N1, [&](int i) {
        int t = monte_carlo_count_pi(N2);
        cs.lock(); // race condition
        n += N2;   // total sampling points
        c += t;    // points fall in the circle
        cs.unlock();
    });
    cout < < "pi ~= " << setprecision(9) << (double)c / n * 4.0 << endl;
    return 0;
}

Other episodes