Hilos OpenMP frente a C++ 11

4 minutos de lectura

avatar de usuario de user2588666
usuario2588666

En el siguiente ejemplo, los subprocesos C++11 tardan unos 50 segundos en ejecutarse, pero los subprocesos OMP solo 5 segundos. ¿Alguna idea de por qué? (Puedo asegurarle que sigue siendo cierto si está haciendo un trabajo real en lugar de doNothingo si lo hace en un orden diferente, etc.) También estoy en una máquina de 16 núcleos.

#include <iostream>
#include <omp.h>
#include <chrono>
#include <vector>
#include <thread>

using namespace std;

void doNothing() {}

int run(int algorithmToRun)
{
    auto startTime = std::chrono::system_clock::now();

    for(int j=1; j<100000; ++j)
    {
        if(algorithmToRun == 1)
        {
            vector<thread> threads;
            for(int i=0; i<16; i++)
            {
                threads.push_back(thread(doNothing));
            }
            for(auto& thread : threads) thread.join();
        }
        else if(algorithmToRun == 2)
        {
            #pragma omp parallel for num_threads(16)
            for(unsigned i=0; i<16; i++)
            {
                doNothing();
            }
        }
    }

    auto endTime = std::chrono::system_clock::now();
    std::chrono::duration<double> elapsed_seconds = endTime - startTime;

    return elapsed_seconds.count();
}

int main()
{
    int cppt = run(1);
    int ompt = run(2);

    cout<<cppt<<endl;
    cout<<ompt<<endl;

    return 0;
}

  • Supongo que OpenMP es lo suficientemente inteligente como para optimizar todo el ciclo, ya que es un NOP. Con threads estás sufriendo la sobrecarga de girar y derribar todos esos subprocesos NOP. Intente agregar código real a la función de prueba y vea qué sucede.

    – aruisdante

    24 de abril de 2014 a las 1:16


  • Bueno, una cosa es que estás usando un contenedor de cambio de tamaño dinámico para contener los hilos; eso no puede ayudar con el rendimiento.

    – RamblingMad

    24 de abril de 2014 a las 1:17

  • Intente simplemente usar una matriz de tamaño fijo e iniciar todos sus elementos cuando se creen.

    – RamblingMad

    24 de abril de 2014 a las 1:17

  • @aruisdante: Agregué código real y les puedo asegurar que la diferencia persiste (tenía mucho código y lo factoricé para publicarlo aquí), no se debe al NOP.

    – usuario2588666

    24 de abril de 2014 a las 1:18

  • @CoffeeandCode: lo hice (y lo intenté de nuevo), y la diferencia es insignificante, ya que la llamada a thread() llama new de todos modos. Sin embargo, es un buen punto, pero también puedo asegurarles que eso no afecta el rendimiento.

    – usuario2588666

    24 de abril de 2014 a las 1:22

avatar de usuario de aruisdante
aruisdante

MP abierto grupos de subprocesos para sus Pragmas (además aquí y aquí). Girar y desarmar hilos es costoso. OpenMP evita esta sobrecarga, por lo que todo lo que hace es el trabajo real y el mínimo desplazamiento de memoria compartida del estado de ejecución. En tus Threads código que está girando y derribando un nuevo conjunto de 16 subprocesos en cada iteración.

  • Gracias. Este posee para ser la respuesta, pero ¿no pensarías que requeriría un #pragma alrededor del bucle for externo? Además, ¿cómo sabe eso como un hecho? No veo información al respecto en la documentación, incluso en el sitio vinculado. Estoy seguro de que tienes razón, solo quiero respaldar la información. Nunca he leído, como un hecho, que hacen eso.

    – usuario2588666

    24 de abril de 2014 a las 1:26

  • Echa un vistazo al segundo enlace, hay algo de conversación allí. Puedo intentar encontrar documentación más sólida, sé que la he leído explícitamente en alguna parte.

    – aruisdante

    24 de abril de 2014 a las 1:27

  • aquí es otra discusión al respecto. Básicamente, en realidad no está definido por el estándar OpenMP, pero la mayoría de las implementaciones en la mayoría de las plataformas parecen hacerlo si es más eficiente.

    – aruisdante

    24 de abril de 2014 a las 1:29

  • Gracias de nuevo :). Supuse que tenían que ser subprocesos, pero sorprendentemente no pude encontrarlo indicado en ninguna parte. Después de buscar un poco más, encontré este. Voy a probarlo en una máquina que no sea de Intel y ver si todavía es cierto. Me ganaste: parece que está hecho básicamente en todas las implementaciones.

    – usuario2588666

    24 de abril de 2014 a las 1:31


  • PD. Puedo confirmar que la diferencia también existe en las máquinas AMD.

    – usuario2588666

    24 de abril de 2014 a las 1:40

Avatar de usuario de Cloud Cho
nube cho

Probé un código de un bucle 100 en
Elegir el marco de enhebrado adecuado y tomó OpenMP 0.0727, Intel TBB 0.6759 y la biblioteca de subprocesos C++ 0.5962 milisegundos.

También apliqué lo que sugirió AruisDante;

void nested_loop(int max_i, int band)  
{
    for (int i = 0; i < max_i; i++)
    {
        doNothing(band);
    }
}
...
else if (algorithmToRun == 5)
{
    thread bristle(nested_loop, max_i, band);
    bristle.join();
}

Parece que este código lleva menos tiempo que la sección de subprocesos original de C++ 11.

¿Ha sido útil esta solución?