43 votes

Puis-je / devrais-je exécuter ce code sur un GPU?

Je travaille sur une application de la statistique contenant environ 10 - 30 millions de valeurs à virgule flottante dans un tableau.

Plusieurs méthodes de l'exécution des différents, mais indépendants, sur les calculs du tableau dans les boucles imbriquées, par exemple:

Dictionary<float, int> noOfNumbers = new Dictionary<float, int>();

for (float x = 0f; x < 100f; x += 0.0001f) {
    int noOfOccurrences = 0;

    foreach (float y in largeFloatingPointArray) {
        if (x == y) {
            noOfOccurrences++;
        }
    }

    noOfNumbers.Add(x, noOfOccurrences);
}

L'application est écrite en C#, fonctionne sur un PROCESSEUR Intel et a besoin de plusieurs heures pour terminer. Je n'ai aucune connaissance de la programmation sur GPU concepts et les Api, donc mes questions sont:

  • Est-il possible (et du sens) d'utiliser un GPU pour accélérer les calculs?
  • Si oui: est-ce que quelqu'un sait tutoriel ou obtenu un exemple de code (langage de programmation n'a pas d'importance)?

Toute aide serait très appréciée.

88voto

dreamcrash Points 8227

Mise à JOUR Version GPU

__global__ void hash (float *largeFloatingPointArray,int largeFloatingPointArraySize, int *dictionary, int size, int num_blocks)
{
    int x = (threadIdx.x + blockIdx.x * blockDim.x); // Each thread of each block will
    float y;                                         // compute one (or more) floats
    int noOfOccurrences = 0;
    int a;

    while( x < size )            // While there is work to do each thread will:
    {
        dictionary[x] = 0;       // Initialize the position in each it will work
        noOfOccurrences = 0;    

        for(int j = 0 ;j < largeFloatingPointArraySize; j ++) // Search for floats
        {                                                     // that are equal 
                                                             // to it assign float
           y = largeFloatingPointArray[j];  // Take a candidate from the floats array 
           y *= 10000;                      // e.g if y = 0.0001f;
           a = y + 0.5;                     // a = 1 + 0.5 = 1;
           if (a == x) noOfOccurrences++;    
        }                                      

        dictionary[x] += noOfOccurrences; // Update in the dictionary 
                                          // the number of times that the float appears 

    x += blockDim.x * gridDim.x;  // Update the position here the thread will work
    }
}

Celui-ci, je viens de tester pour les petites entrées, parce que je suis en train de tester, j'ai mon ordinateur portable. Néanmoins, il a fait un travail. Cependant, il est nécessaire de faire favorise les testicules.

Mise à JOUR de la Version Séquentielle

J'ai juste fait cette version naïve que d'effectuer votre algorithme pour de 30 000 000 en moins de 20 secondes (en comptant déjà de fonction pour générer des données).

En gros, c'est un peu votre tableau de float. Il sera du voyage sur le tableau trié, en analysant le nombre de fois qu'une valeur consécutivement apparaît dans le tableau et ensuite mettre cette valeur dans un dictionnaire, avec le nombre de fois où il apparaît.

Vous pouvez utiliser triés carte, au lieu de la unordered_map que j'ai utilisé.

Voici le code:

#include <stdio.h>
#include <stdlib.h>
#include "cuda.h"
#include <algorithm>
#include <string>
#include <iostream>
#include <tr1/unordered_map>


typedef std::tr1::unordered_map<float, int> Mymap;


void generator(float *data, long int size)
{
    float LO = 0.0;
    float HI = 100.0;

    for(long int i = 0; i < size; i++)
        data[i] = LO + (float)rand()/((float)RAND_MAX/(HI-LO));
}

void print_array(float *data, long int size)
{

    for(long int i = 2; i < size; i++)
        printf("%f\n",data[i]);

}

std::tr1::unordered_map<float, int> fill_dict(float *data, int size)
{
    float previous = data[0];
    int count = 1;
    std::tr1::unordered_map<float, int> dict;

    for(long int i = 1; i < size; i++)
    {
        if(previous == data[i])
            count++;
        else
        {
          dict.insert(Mymap::value_type(previous,count));
          previous = data[i];
          count = 1;         
        }

    }
    dict.insert(Mymap::value_type(previous,count)); // add the last member
    return dict;

}

void printMAP(std::tr1::unordered_map<float, int> dict)
{
   for(std::tr1::unordered_map<float, int>::iterator i = dict.begin(); i != dict.end(); i++)
  {
     std::cout << "key(string): " << i->first << ", value(int): " << i->second << std::endl;
   }
}


int main(int argc, char** argv)
{
  int size = 1000000; 
  if(argc > 1) size = atoi(argv[1]);
  printf("Size = %d",size);

  float data[size];
  using namespace __gnu_cxx;

  std::tr1::unordered_map<float, int> dict;

  generator(data,size);

  sort(data, data + size);
  dict = fill_dict(data,size);

  return 0;
}

Si vous avez de la bibliothèque de poussée installés dans votre machine, vous devez utiliser ceci:

#include <thrust/sort.h>
thrust::sort(data, data + size);

au lieu de cela

sort(data, data + size);

Pour sûr, ça sera plus rapide.

Post Original

"Je travaille sur une application de la statistique, qui dispose d'un large éventail containin 10 - 30 millions de valeurs à virgule flottante".

"Est-il possible (et du sens) d'utiliser un GPU pour accélérer les calculs?"

Oui, il est. Il y a un mois, j'ai mis une simulation Dynamique Moléculaire entièrement sur le GPU. L'un des noyaux, qui calcule la force entre les paires de particules, de recevoir 6 de la matrice de chacune de 500 000 chambres doubles, un total de 3 Millions doubles (22 MO).

Si vous êtes rabotage à mettre 30 Millions de flotteur de points est d'environ 114 MO de Mémoire globale, donc ce n'est pas un problème, même mon portable ont 250MB.

Le nombre de calcul peut être un problème dans votre cas? Basé sur mon expérience avec la Dynamique Moléculaire (MD) je dis non. Le séquentiel MD version prend environ 25 heures pour terminer tandis que dans le GPU a duré 45 Minutes. Vous avez dit que votre demande a fallu quelques heures, également basé dans le code de l'exemple, il semble plus doux que la Dynamique Moléculaire.

Voici la force de l'exemple de calcul:

__global__ void add(double *fx, double *fy, double *fz,
                    double *x, double *y, double *z,...){

     int pos = (threadIdx.x + blockIdx.x * blockDim.x); 

     ...

     while(pos < particles)
     {

      for (i = 0; i < particles; i++)
      {
              if(//inside of the same radius)
                {
                 // calculate force
                } 
       }
     pos += blockDim.x * gridDim.x;  
     }        
  }

Un exemple simple de code Cuda peut être la somme de deux tableaux 2D:

En c:

for(int i = 0; i < N; i++)
    c[i] = a[i] + b[i]; 

Dans Cuda:

__global__ add(int *c, int *a, int*b, int N)
{
  int pos = (threadIdx.x + blockIdx.x)
  for(; i < N; pos +=blockDim.x)
      c[pos] = a[pos] + b[pos];
}

Dans Cuda vous a pris pour chaque itération et de diviser par chaque thread,

1) threadIdx.x + blockIdx.x*blockDim.x;

Chaque bloc ont un Id de 0 à N-1 (N le nombre maximum de blocs) et chaque bloc ont un X nombre de threads avec un id de 0 à X-1.

1) vous Donne la pour l'itération que chaque thread de calcul basé sur l'id et le bloc id où le fil est dans, la blockDim.x est le nombre de threads d'un bloc ont.

Donc si vous avez 2 pâtés de maisons, chacune avec 10 threads et N = 40, l:

Thread 0 Block 0 will execute pos 0
Thread 1 Block 0 will execute pos 1
...
Thread 9 Block 0 will execute pos 9
Thread 0 Block 1 will execute pos 10
....
Thread 9 Block 1 will execute pos 19
Thread 0 Block 0 will execute pos 20
...
Thread 0 Block 1 will execute pos 30
Thread 9 Block 1 will execute pos 39

À la recherche de votre code, j'ai fait cette ébauche de ce que pourrait être dans cuda:

__global__ hash (float *largeFloatingPointArray, int *dictionary)
    // You can turn the dictionary in one array of int
    // here each position will represent the float
    // Since  x = 0f; x < 100f; x += 0.0001f
    // you can associate each x to different position
    // in the dictionary:

    // pos 0 have the same meaning as 0f;
    // pos 1 means float 0.0001f
    // pos 2 means float 0.0002f ect.
    // Then you use the int of each position 
    // to count how many times that "float" had appeared 


   int x = blockIdx.x;  // Each block will take a different x to work
    float y;

while( x < 1000000) // x < 100f (for incremental step of 0.0001f)
{
    int noOfOccurrences = 0;
    float z = converting_int_to_float(x); // This function will convert the x to the
                                          // float like you use (x / 0.0001)

    // each thread of each block
    // will takes the y from the array of largeFloatingPointArray

    for(j = threadIdx.x; j < largeFloatingPointArraySize; j += blockDim.x)
    {
        y = largeFloatingPointArray[j];
        if (z == y)
        {
            noOfOccurrences++;
        }
    }
    if(threadIdx.x == 0) // Thread master will update the values
      atomicAdd(&dictionary[x], noOfOccurrences);
    __syncthreads();
}

Vous devez utiliser atomicAdd parce que les différents threads de différents blocs peut écrire/lire noOfOccurrences en même temps, si vous avez des doutes exclusion mutuelle.

Ce n'est qu'une approche, vous pouvez même donner les itérations de la boucle externe pour les threads au lieu de blocs.

Tutoriels

Le Dr Dobbs Journal de la série CUDA: calcul intensif pour les masses par Rob Agriculteur est excellent et couvre à peu près tout dans ses quatorze ans de versements. Elle commence plutôt doucement et est donc assez débutant-friendly.

et d'autres:

Prendre un coup d'oeil sur la dernière rubrique, vous trouverez de nombreux lien pour en savoir CUDA.

OpenCL: OpenCL Tutoriels | MacResearch

11voto

AlliedEnvy Points 256

Je ne sais pas beaucoup de chose sur le traitement parallèle ou GPGPU, mais pour cet exemple précis, vous pouvez épargner beaucoup de temps en effectuant un seul passage sur le tableau d'entrée plutôt que de boucler sur un million de fois. Avec de grands ensembles de données, vous aurez généralement à faire les choses en un seul passage, si possible. Même si vous êtes en train de faire plusieurs calculs indépendants, si c'est sur le même jeu de données que vous pourriez obtenir une meilleure vitesse de les faire toutes dans le même passage, que vous obtiendrez le meilleur de la localité de référence de cette façon. Mais il peut ne pas être à la hauteur de la complexité accrue dans votre code.

En outre, vous ne voulez vraiment pas à ajouter une petite quantité à un nombre à virgule flottante de façon répétitive comme ça, l'erreur d'arrondi s'ajouter et que vous n'obtenez pas ce que vous souhaitiez. J'ai ajouté une instruction if pour mon exemple ci-dessous afin de vérifier si les entrées correspondent à votre modèle de l'itération, mais l'omettre si vous n'avez pas vraiment besoin de ça.

Je ne connais pas le C#, mais un seul passage de la mise en œuvre de votre échantillon devrait ressembler à quelque chose comme ceci:

Dictionary<float, int> noOfNumbers = new Dictionary<float, int>();

foreach (float x in largeFloatingPointArray)
{
    if (math.Truncate(x/0.0001f)*0.0001f == x)
    {
        if (noOfNumbers.ContainsKey(x))
            noOfNumbers.Add(x, noOfNumbers[x]+1);
        else
            noOfNumbers.Add(x, 1);
    }
}

Espérons que cette aide.

9voto

Pragmateek Points 5188

Est-il possible (et du sens) pour utiliser un GPU pour accélérer de tels calculs?

  • Certainement OUI, ce genre d'algorithme est généralement le candidat idéal pour les grands volumes de données-le parallélisme de traitement, la chose Gpu sont aussi bonnes.

Si oui: est-ce que quelqu'un sait tutoriel ou obtenu un exemple de code (langage de programmation n'a pas d'importance)?

  • Lorsque vous voulez aller à la GPGPU façon, vous avez deux solutions : CUDA ou OpenCL.

    CUDA est mature avec beaucoup d'outils, mais est NVidia Gpu centric.

    OpenCL est un standard en cours d'exécution sur NVidia et AMD Gpu et Cpu trop. Donc, vous devriez vraiment favorables.

  • Pour le tutoriel, vous avez une excellente série sur CodeProject par Rob Farber : http://www.codeproject.com/Articles/Rob-Farber#Articles

  • Pour votre cas d'utilisation, il y a beaucoup d'échantillons pour les histogrammes de l'immeuble avec OpenCL (notez que la plupart sont des histogrammes de l'image, mais les principes sont les mêmes).

  • Comme vous l'utilisation de C#, vous pouvez utiliser les fixations comme les OpenCL.Net ou Cloo.

  • Si votre tableau est trop grand pour être stockées dans la mémoire graphique, vous pouvez bloquer-partition et relancez votre OpenCL noyau pour chaque partie facilement.

6voto

Eli Algranti Points 3284

En plus de la suggestion ci-dessus affiche l'utilisation de la TPL (task parallel library) le cas échéant, de s'exécuter en parallèle sur plusieurs cœurs.

L'exemple ci-dessus pourrait utiliser en Parallèle.Foreach et ConcurrentDictionary, mais de plus en plus complexe réduire la carte de configuration où le tableau est divisé en morceaux à chaque génération d'un dictionnaire qui serait ensuite réduit à un seul dictionnaire vous donnera de meilleurs résultats.

Je ne sais pas si tous vos calculs carte correctement le GPU capacités, mais vous aurez à utiliser une carte-réduire l'algorithme de toute façon à la carte les calculs pour le GPU cores et puis de réduire les résultats partiels à un résultat unique, de sorte que vous pourriez aussi bien le faire que sur le CPU avant de passer à une moins plate-forme familière.

6voto

Arun Taylor Points 1144

Je ne suis pas sûr que ce soit en utilisant les Gpu serait un bon match étant donné que 'largerFloatingPointArray valeurs doivent être récupérées à partir de la mémoire. Ma compréhension est que les Gpu sont les mieux adaptés pour les contenus eux-mêmes calculs.

Je pense que le tournant de ce processus unique de l'application dans une application distribuée fonctionnant sur de nombreux systèmes et en les modifiant l'algorithme devrait accélérer les choses considérablement, selon la façon dont beaucoup de systèmes sont disponibles.

Vous pouvez utiliser le classique "diviser pour régner" approche. L'approche générale que je prendrais est comme suit.

Utiliser un système de prétraitement de 'largeFloatingPointArray" dans une table de hachage ou une base de données. Ce serait fait en une seule passe. Elle serait d'utiliser de virgule flottante valeur de la clé et le nombre d'occurrences dans le tableau de la valeur. Le pire scénario est que chaque valeur se produit seulement une fois, mais c'est peu probable. Si largeFloatingPointArray ne cesse de changer à chaque fois que l'application est exécutée alors en mémoire une table de hachage à sens. Si elle est statique, la table peut être enregistré dans une valeur-clé de la base de données telles que Berkeley DB. Appelons cela une "recherche" du système.

Sur un autre système, appelons-la "main", de créer des morceaux de travail et la "scatter" les éléments de travail à travers les N systèmes, et de "rassembler" les résultats à mesure qu'ils deviennent disponibles. E. g un élément de travail pourrait être aussi simple que de deux chiffres indiquant la gamme qu'un système devrait travailler. Lorsqu'un système exécute le travail, il envoie au tableau d'occurrences et il est prêt à travailler sur un autre morceau de travail.

La performance est améliorée parce que nous ne gardons pas d'itération sur largeFloatingPointArray. Si le système de recherche devient un goulet d'étranglement, alors il pourrait être reproduit sur autant d'ordinateurs que nécessaire.

Avec assez grand nombre de systèmes en parallèle, il devrait être possible de réduire le temps de traitement à quelques minutes.

Je suis en train de travailler sur un compilateur pour la programmation parallèle en C ciblées pour beaucoup-core de base de systèmes, souvent désigné comme micro serveurs, qui sont ou seront construites à l'aide de multiples "système-sur-une-puce" pour les modules au sein d'un système. BRAS module fournisseurs Calxeda, AMD, l'AMCC, etc. Intel aura probablement aussi une offre similaire.

J'ai une version du compilateur de travail, qui pourrait être utilisé pour une telle application. Le compilateur, basé sur le C prototypes de fonction, génère du C code réseau qui met en œuvre la communication inter-processus (code CIP) pour l'ensemble des systèmes. L'un des mécanisme IPC est disponible socket/tcp/ip.

Si vous avez besoin d'aide dans la mise en œuvre d'une solution distribuée, je serais heureux d'en discuter avec vous.

Ajouté Le 16 Novembre 2012.

J'ai pensé un peu plus à propos de l'algorithme et je pense que cela devrait le faire en un seul passage. Il est écrit en C et il devrait être très rapide par rapport à ce que vous avez.

/*
 * Convert the X range from 0f to 100f in steps of 0.0001f
 * into a range of integers 0 to 1 + (100 * 10000) to use as an
 * index into an array.
 */

#define X_MAX           (1 + (100 * 10000))

/*
 * Number of floats in largeFloatingPointArray needs to be defined
 * below to be whatever your value is.
 */

#define LARGE_ARRAY_MAX (1000)

main()
{
    int j, y, *noOfOccurances;
    float *largeFloatingPointArray;

    /*
     * Allocate memory for largeFloatingPointArray and populate it.
     */

    largeFloatingPointArray = (float *)malloc(LARGE_ARRAY_MAX * sizeof(float));    
    if (largeFloatingPointArray == 0) {
        printf("out of memory\n");
        exit(1);
    }

    /*
     * Allocate memory to hold noOfOccurances. The index/10000 is the
     * the floating point number.  The contents is the count.
     *
     * E.g. noOfOccurances[12345] = 20, means 1.2345f occurs 20 times
     * in largeFloatingPointArray.
     */

    noOfOccurances = (int *)calloc(X_MAX, sizeof(int));
    if (noOfOccurances == 0) {  
        printf("out of memory\n");
        exit(1);
    }

    for (j = 0; j < LARGE_ARRAY_MAX; j++) {
        y = (int)(largeFloatingPointArray[j] * 10000);
        if (y >= 0 && y <= X_MAX) {
            noOfOccurances[y]++;
        }   
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X