2012-05-29 35 views
19

C++ 2011 incluye nuevas características muy interesantes, pero no puedo encontrar muchos ejemplos para paralelizar un bucle for. Así que mi pregunta muy ingenua es: ¿cómo se paraleliza un bucle for simple (como usar "omp parallel for") con std :: thread? (Busco un ejemplo).C++ 2011: std :: thread: ejemplo simple para paralelizar un ciclo?

Muchas gracias.

+1

¿Hay alguna sección crítica en el ciclo for? –

+4

Cuanto más miro a OMG, creo que std :: thread no está destinado a reemplazarlo. 'std :: thread' no está destinado a optimizar operaciones CRUD de bajo nivel. –

+4

puede publicar un bucle que está intentando paralelizar? –

Respuesta

29

std::thread no está necesariamente destinado a parallize bucles. Se supone que es la abstracción de bajo nivel para construir construcciones, como un algoritmo parallel_for. Si desea paralisar sus bucles, debe usar un algoritmo parallel_for o usar libraires existentes que ofrezcan parallismo basado en tareas.

El siguiente ejemplo muestra cómo se puede parallize un bucle simple, pero por otro lado también muestra las desventajas, como la falta de equilibrio de carga y la complejidad de un bucle simple.

typedef std::vector<int> container; 
    typedef container::iterator iter; 

    container v(100, 1); 

    auto worker = [] (iter begin, iter end) { 
    for(auto it = begin; it != end; ++it) { 
     *it *= 2; 
    } 
    }; 


    // serial 
    worker(std::begin(v), std::end(v)); 

    std::cout << std::accumulate(std::begin(v), std::end(v), 0) << std::endl; // 200 

    // parallel 
    std::vector<std::thread> threads(8); 
    const int grainsize = v.size()/8; 

    auto work_iter = std::begin(v); 
    for(auto it = std::begin(threads); it != std::end(threads) - 1; ++it) { 
    *it = std::thread(worker, work_iter, work_iter + grainsize); 
    work_iter += grainsize; 
    } 
    threads.back() = std::thread(worker, work_iter, std::end(v)); 

    for(auto&& i : threads) { 
    i.join(); 
    } 

    std::cout << std::accumulate(std::begin(v), std::end(v), 0) << std::endl; // 400 

El uso de una biblioteca que ofrece una plantilla parallel_for, se puede simplificar a

parallel_for(std::begin(v), std::end(v), worker); 
3

no puede proporcionar una respuesta específica C++ 11 ya que estamos todavía en su mayoría utilizando pthreads. Pero, como una respuesta independiente del lenguaje, se puede paralelizar algo al configurarlo para que se ejecute en una función separada (la función de subprocesamiento).

En otras palabras, usted tiene una función como:

def processArraySegment (threadData): 
    arrayAddr = threadData->arrayAddr 
    startIdx = threadData->startIdx 
    endIdx = threadData->endIdx 

    for i = startIdx to endIdx: 
     doSomethingWith (arrayAddr[i]) 

    exitThread() 

y, en su código principal, se puede procesar la matriz en dos trozos:

int xyzzy[100] 

threadData->arrayAddr = xyzzy 
threadData->startIdx = 0 
threadData->endIdx = 49 
threadData->done  = false 
tid1 = startThread (processArraySegment, threadData) 

// caveat coder: see below. 
threadData->arrayAddr = xyzzy 
threadData->startIdx = 50 
threadData->endIdx = 99 
threadData->done  = false 
tid2 = startThread (processArraySegment, threadData) 

waitForThreadExit (tid1) 
waitForThreadExit (tid2) 

(teniendo en cuenta la salvedad que debe asegurarse de hilo 1 se ha cargado los datos en su almacenamiento local antes el hilo principal comienza modificándolo para el hilo 2, posiblemente con un mutex o utilizando un array de estructuras, uno por hilo).

En otras palabras, es muy raro que una simple cuestión de simplemente modificando un bucle for para que se ejecute en paralelo, aunque eso sería bueno, algo así como:

for {threads=10} ({i} = 0; {i} < ARR_SZ; {i}++) 
    array[{i}] = array[{i}] + 1; 

su lugar, se requiere un poco de reordenación su código para aprovechar los hilos.

Y, por supuesto, debe asegurarse de que tenga sentido que los datos se procesen en paralelo. Si está configurando cada elemento de la matriz en el anterior más 1, no será de gran ayuda el procesamiento en paralelo, simplemente porque tiene que esperar a que se modifique primero el elemento anterior.

Este ejemplo particular anteriormente utiliza simplemente un argumento pasado a la función del hilo para especificar qué parte de la matriz se debe procesar. La función de hilo en sí contiene el bucle para hacer el trabajo.

4

Bueno, obviamente, depende de lo que hace su lazo, cómo se decide paralellize, y cómo gestionar el tiempo de vida roscas.

Estoy leyendo the book from the std C++11 threading library (que también es uno de los boost.thread mantenedor y escribí Just Thread) y puedo ver que "depende".

Ahora para darle una idea de lo básico utilizando la nueva rosca estándar, yo recomiendo leer el libro, ya que da un montón de ejemplos. También, echar un vistazo a http://www.justsoftwaresolutions.co.uk/threading/ y https://stackoverflow.com/questions/415994/boost-thread-tutorials

1

yo sepa la forma más sencilla para paralelizar un bucle, si está seguro de que no hay acceso concurrente posible, es usando OpenMP.

Es compatible con todos los compiladores principales, excepto LLVM (desde agosto de 2013).

Ejemplo:

for(int i = 0; i < n; ++i) 
{ 
    tab[i] *= 2; 
    tab2[i] /= 2; 
    tab3[i] += tab[i] - tab2[i]; 
} 

Esto se parallelized muy fácilmente de esta manera:

#pragma omp parallel for 
for(int i = 0; i < n; ++i) 
{ 
    tab[i] *= 2; 
    tab2[i] /= 2; 
    tab3[i] += tab[i] - tab2[i]; 
} 

Sin embargo, tenga en cuenta que esto sólo es eficaz con un gran número de valores.

Si utiliza g ++, otra forma muy C++ 11-ish de hacerlo sería mediante un lambda y un for_each, y el uso de extensiones paralelas GNU (que puede utilizar OpenMP detrás de la escena):

__gnu_parallel::for_each(std::begin(tab), std::end(tab), [&]() 
{ 
    stuff_of_your_loop(); 
}); 

Sin embargo, for_each está pensado principalmente para matrices, vectores, etc. ... Pero puede "engañarlo" si solo quiere iterar a través de un rango creando una clase Range con begin y end método que incrementará principalmente un int.

Tenga en cuenta que para bucles simples que hacen cosas matemáticas, los algoritmos en #include <numeric> y #include <algorithm> pueden todos ser paralelizados con G ++.

3

Usando this clase puede hacerlo como:

Range based loop (read and write) 
pforeach(auto &val, container) { 
    val = sin(val); 
}; 

Index based for-loop 
auto new_container = container; 
pfor(size_t i, 0, container.size()) { 
    new_container[i] = sin(container[i]); 
}; 
+0

El enlace funciona nuevamente. –

+0

esta clase no está disponible (404). ¿Podría por favor elaborar qué clase usar? – BernhardWebstudio

0

Definir macro utilizando std :: hilo y expresión lambda:

#ifndef PARALLEL_FOR 
#define PARALLEL_FOR(INT_LOOP_BEGIN_INCLUSIVE, INT_LOOP_END_EXCLUSIVE,I,O)   \                \ 
    {                    \ 
     int LOOP_LIMIT=INT_LOOP_END_EXCLUSIVE-INT_LOOP_BEGIN_INCLUSIVE;    \ 
     std::thread threads[LOOP_LIMIT]; auto fParallelLoop=[&](int I){ O; };  \ 
     for(int i=0; i<LOOP_LIMIT; i++)            \ 
     {                   \ 
      threads[i]=std::thread(fParallelLoop,i+INT_LOOP_BEGIN_INCLUSIVE);  \ 
     }                   \ 
     for(int i=0; i<LOOP_LIMIT; i++)            \ 
     {                   \ 
      threads[i].join();              \ 
     }                   \ 
    }                    \ 
#endif 

uso:

int aaa=0; 
PARALLEL_FOR(0,90,i, 
{ 
    aaa+=i; 
}); 

su fea pero funciona.