Partager via


Comment : exécuter des opérations de mappage et de réduction en parallèle

Cet exemple montre comment utiliser les algorithmes concurrency::parallel_transform et concurrency::parallel_reduce et la classe concurrency::concurrent_unordered_map pour compter les occurrences des mots dans les fichiers.

Une opération de mappage s'applique la fonction à chaque valeur dans une séquence. Une opération de réduction combine les éléments d'une séquence dans une valeur. Vous pouvez utiliser les classes de (STL) std::transform std::accumulate de Standard TEMPLATE Library (STL) pour effectuer le mappage et les opération de réductions. Toutefois, pour améliorer les performances dans de nombreuses situations, vous pouvez utiliser l'algorithme parallel_transform pour exécuter l'opération de mappage en parallèle et l'algorithme parallel_reduce pour exécuter l'opération de réduction en parallèle. Dans certains cas, vous pouvez utiliser concurrent_unordered_map pour effectuer le mappage et la réduction d'une opération.

Exemple

L'exemple suivant compte les occurrences des mots dans les fichiers. Il utilise std::vector pour représenter le contenu de deux fichiers. L'opération de mappage calcule les occurrences de chaque mot de chaque vecteur. L'opération de réduction cumule les comptes de mots entre les deux vecteurs.

// parallel-map-reduce.cpp 
// compile with: /EHsc
#include <ppl.h>
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <numeric>
#include <unordered_map>
#include <windows.h>

using namespace concurrency;
using namespace std;

class MapFunc 
{ 
public:
    unordered_map<wstring, size_t> operator()(vector<wstring>& elements) const 
    { 
        unordered_map<wstring, size_t> m;
        for_each(begin(elements), end(elements), [&m](const wstring& elem)
        { 
            m[elem]++;
        });
        return m; 
    }
}; 

struct ReduceFunc : binary_function<unordered_map<wstring, size_t>, 
                    unordered_map<wstring, size_t>, unordered_map<wstring, size_t>>
{
    unordered_map<wstring, size_t> operator() (
        const unordered_map<wstring, size_t>& x, 
        const unordered_map<wstring, size_t>& y) const
    {
        unordered_map<wstring, size_t> ret(x);
        for_each(begin(y), end(y), [&ret](const pair<wstring, size_t>& pr) {
            auto key = pr.first;
            auto val = pr.second;
            ret[key] += val;
        });
        return ret; 
    }
}; 

int wmain()
{ 
    // File 1 
    vector<wstring> v1;
    v1.push_back(L"word1"); //1 
    v1.push_back(L"word1"); //2 
    v1.push_back(L"word2"); 
    v1.push_back(L"word3"); 
    v1.push_back(L"word4"); 

    // File 2 
    vector<wstring> v2; 
    v2.push_back(L"word5"); 
    v2.push_back(L"word6"); 
    v2.push_back(L"word7"); 
    v2.push_back(L"word8"); 
    v2.push_back(L"word1"); //3 

    vector<vector<wstring>> v;
    v.push_back(v1);
    v.push_back(v2);

    vector<unordered_map<wstring, size_t>> map(v.size()); 

    // The Map operation
    parallel_transform(begin(v), end(v), begin(map), MapFunc()); 

    // The Reduce operation 
    unordered_map<wstring, size_t> result = parallel_reduce(
        begin(map), end(map), unordered_map<wstring, size_t>(), ReduceFunc());

    wcout << L"\"word1\" occurs " << result.at(L"word1") << L" times. " << endl;
} 
/* Output:
   "word1" occurs 3 times.
*/

Compilation du code

Pour compiler le code, copiez-le puis collez-le dans un projet Visual Studio , ou collez-le dans un fichier nommé parallel-map-reduce.cpp puis exécutez la commande suivante dans une fenêtre d'invite de commandes Visual Studio.

cl.exe /EHsc parallel-map-reduce.cpp

Programmation fiable

Dans cet exemple, vous pouvez utiliser la classe concurrent_unordered_map qui est définie dans concurrent_unordered_map.h-to pour effectuer le mappage et réduire dans une opération.

// File 1 
vector<wstring> v1;
v1.push_back(L"word1"); //1 
v1.push_back(L"word1"); //2 
v1.push_back(L"word2"); 
v1.push_back(L"word3"); 
v1.push_back(L"word4"); 

// File 2 
vector<wstring> v2; 
v2.push_back(L"word5"); 
v2.push_back(L"word6"); 
v2.push_back(L"word7"); 
v2.push_back(L"word8"); 
v2.push_back(L"word1"); //3 

vector<vector<wstring>> v;
v.push_back(v1);
v.push_back(v2);

concurrent_unordered_map<wstring, size_t> result;
for_each(begin(v), end(v), [&result](const vector<wstring>& words) {
    parallel_for_each(begin(words), end(words), [&result](const wstring& word) {
        InterlockedIncrement(&result[word]);
    });
});

wcout << L"\"word1\" occurs " << result.at(L"word1") << L" times. " << endl;

/* Output:
   "word1" occurs 3 times.
*/

En général, vous ne parallélisez que la boucle interne ou externe. Parallélisez la boucle interne si vous avez relativement peu de fichiers et si chaque fichier contient plusieurs mots. Parallélisez la boucle externe si vous avez de nombreux de fichiers et si chaque fichier contient peu de mots.

Voir aussi

Référence

parallel_transform, fonction

parallel_reduce, fonction

concurrent_unordered_map, classe

Concepts

Algorithmes parallèles