Cómo: Realizar operaciones de asignación y reducción en paralelo

Este ejemplo muestra cómo se debe usar los algoritmos concurrency::parallel_transform y concurrency::parallel_reduce, y la clase concurrency::concurrent_unordered_map para contar las apariciones de las palabras en los archivos.

Una operación deasignación aplica una función a cada valor de una secuencia. Una operación dereducción combina los elementos de una secuencia en un valor. Puede usar la Biblioteca estándar C++ std::transform y las funciones std::accumulate para realizar operaciones de asignación y reducción. Sin embargo, si desea mejorar el rendimiento ante los diversos problemas que pueden surgir, use los algoritmos parallel_transform y parallel_reduce para realizar las operaciones map y reduce en paralelo, respectivamente. En algunos casos, puede usar concurrent_unordered_map para realizar map y reduce en una sola operación.

Ejemplo

En el ejemplo siguiente se cuentan las apariciones de las palabras en los archivos. Se usa std::vector para representar el contenido de dos archivos. La operación map calcula las apariciones de cada palabra en cada vector. La operación reduce suma los recuentos de palabras de los dos vectores.

// 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 {
      L"word1", // 1
      L"word1", // 1
      L"word2",
      L"word3",
      L"word4"
    };

    // File 2 
    vector<wstring> v2 {
      L"word5",
      L"word6",
      L"word7",
      L"word8",
      L"word1" // 3
    };

    vector<vector<wstring>> v { v1, 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.
*/

Compilar el código

Para compilar el código, cópielo y péguelo en un proyecto de Visual Studio, o en un archivo denominado parallel-map-reduce.cpp. Después, ejecute el comando siguiente en una ventana del símbolo del sistema de Visual Studio.

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

Programación sólida

En este ejemplo, puede usar la clase concurrent_unordered_map que se define en concurrent_unordered_map.h para realizar map y reduce en una sola operación.

// File 1 
vector<wstring> v1 {
  L"word1", // 1
  L"word1", // 2
  L"word2",
  L"word3",
  L"word4",
};

// File 2 
vector<wstring> v2 {
  L"word5",
  L"word6",
  L"word7",
  L"word8",
  L"word1", // 3
}; 

vector<vector<wstring>> v { v1, 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.
*/

Por lo general, solo se ejecutan en paralelo los bucles internos o externos. Ejecute en paralelo el bucle interno si dispone de relativamente pocos archivos y cada uno de ellos contiene muchas palabras. Sin embargo, si dispone de un número razonable de archivos y cada uno de ellos contiene pocas palabras, ejecute en paralelo el bucle externo.

Consulte también

Algoritmos paralelos
función parallel_transform
función parallel_reduce
concurrent_unordered_map (clase)