Data Parallelism Practical


#include <algorithm>
#include <future>
#include <iostream>
#include <numeric>
#include <random>
#include <thread>
#include <utility>
#include <vector>

static std::mt19937 mt;
std::uniform_real_distribution<double> dist(0, 100);

// Compute the sum of a range of elements
double accum(double *beg, double *end) {
  return std::accumulate(beg, end, 0.0);
}

// Divide the data into 4 parts
// Use a separate part to process each subset
// Using std::async
double add_paralel(std::vector<double> &vec) {
  // First element of the vector
  auto vec0 = &vec[0];

  // Number of elements
  auto vsize = vec.size();

  // Start the threads
  // Pass the subset's range as argument to the task function
  auto fut1 = std::async(std::launch::async, accum, vec0, vec0 + vsize / 4);
  auto fut2 = std::async(std::launch::async, accum, vec0 + vsize / 4,
                         vec0 + 2 * vsize / 4);
  auto fut3 = std::async(std::launch::async, accum, vec0 + 2 * vsize / 4,
                         vec0 + 3 * vsize / 4);
  auto fut4 =
      std::async(std::launch::async, accum, vec0 + 3 * vsize / 4, vec0 + vsize);

  // Reduce step
  return fut1.get() + fut2.get() + fut3.get() + fut4.get();
}

// Using std::packaged_task
double add_parallel_2(std::vector<double> &vec) {
  using task_type = double(double *, double *);

  std::packaged_task<task_type> ptask1(accum);
  std::packaged_task<task_type> ptask2(accum);
  std::packaged_task<task_type> ptask3(accum);
  std::packaged_task<task_type> ptask4(accum);

  auto fut1 = ptask1.get_future();
  auto fut2 = ptask2.get_future();
  auto fut3 = ptask3.get_future();
  auto fut4 = ptask4.get_future();

  auto vec0 = &vec[0];
  auto vsize = vec.size();

  std::thread thr1(std::move(ptask1), vec0, vec0 + vsize / 4);
  std::thread thr2(std::move(ptask2), vec0 + vsize / 4, vec0 + 2 * vsize / 4);
  std::thread thr3(std::move(ptask3), vec0 + 2 * vsize / 4,
                   vec0 + 3 * vsize / 4);
  std::thread thr4(std::move(ptask4), vec0 + 3 * vsize / 4, vec0 + vsize);

  thr1.join();
  thr2.join();
  thr3.join();
  thr4.join();

  return fut1.get() + fut2.get() + fut3.get() + fut4.get();
}

int main() {
  // populate a vector with elements 1, 2 ... 16
  std::vector<double> vec(16);
  std::iota(vec.begin(), vec.end(), 1.0);

  // populate a vector with 10,000 random elements
  std::vector<double> vrand(10'000);
  std::generate(vrand.begin(), vrand.end(), [&vrand]() { return dist(mt); });

  std::cout << "Sum of first 16 ints: " << add_parallel_2(vec) << '\n';
  std::cout << "Sum of 10,000 random nums: " << add_parallel_2(vrand) << '\n';
}