Skip to content

future & promise vs async

Francisco Ruiz edited this page Sep 29, 2015 · 8 revisions

Statement

Divide and Conquer using futures and promises or async methods.

With this three new classes defined by C++11 we can get lot of benefits from multithreading programing but in an easier way. We are going to calculate the next mathematical formula in three different ways:

R = max(values) - min(values) + Sum(values) | values is a random list of 100 M of integers.

We are going to solve a very easy problem in three different ways, we will take note of the time needed to make the calculation:

  • First case we are going to calculate every part of R in a sequential way.

  • Second case we are going to calculate every part of R in a parallel way by using threads and the corresponding promise and future.

  • Third case we are going to calculate every part of R in a parallel way by using std::async

By running future we will get this times:

$ ./future
usual Result=2.14748e+09 Time=276 ms
promiseAndFuture Result=2.14748e+09 Time=184 ms
usingSync Result=2.14748e+09 Time=183 ms

$ ./future
usual Result=2.14748e+09 Time=276 ms
promiseAndFuture Result=2.14748e+09 Time=198 ms
usingSync Result=2.14748e+09 Time=179 ms

$ ./future
usual Result=2.14748e+09 Time=276 ms
promiseAndFuture Result=2.14748e+09 Time=207 ms
usingSync Result=2.14748e+09 Time=179 ms

$ ./future
usual Result=2.14748e+09 Time=278 ms
promiseAndFuture Result=2.14748e+09 Time=176 ms
usingSync Result=2.14748e+09 Time=189 ms

$ ./future
usual Result=2.14748e+09 Time=278 ms
promiseAndFuture Result=2.14748e+09 Time=173 ms
usingSync Result=2.14748e+09 Time=179 ms

You can see the first way is always the slowest one. The second one is slightly faster than the third one, but is easy to explain because std::async has to deal with lot of details that our custom solution can ignore.

#Conclusions

#The sequential algorithm

void usual (const InputValues& values)
{
   long init = millisecond ();

   double average = averageMethod (values);
   int max = maxMethod (values);
   int min = minMethod (values);

   double result = max - min / average;

   long end = millisecond ();

   std::cout << __func__ << " Result=" << result << " Time=" << end - init << " ms" << std::endl;
}

The algorithm calculates the expression R has about 10 lines of code, but it is around 30% slower that other parallel algorithms.

The future & promise algorithm

void averagePromise (std::promise <double>& prm, const InputValues& values)
{
   prm.set_value (averageMethod (values));
}

void maxPromise (std::promise <int>& promise, const InputValues& values)
{
   promise.set_value (maxMethod (values));
}

void minPromise (std::promise <int>& promise, const InputValues& values)
{
   promise.set_value (minMethod (values));
}

void promiseAndFuture (const std::vector<int>& values)
{
   long init = millisecond ();

   std::promise <double> prmAvg;
   std::future <double> futAvg = prmAvg.get_future ();  
   std::thread (averagePromise, std::ref (prmAvg), std::ref (values)).detach ();
     
   std::promise <int> prmMax;
   std::future <int> futMax = prmMax.get_future ();  
   std::thread (maxPromise, std::ref (prmMax), std::ref (values)).detach ();
   
   std::promise <int> prmMin;
   std::future <int> futMin = prmMin.get_future ();  
   std::thread (minPromise, std::ref (prmMin), std::ref (values)).detach ();
   
   double average = futAvg.get ();
   int max = futMax.get ();
   int min = futMin.get ();

   double result = max - min / average;

   long end = millisecond ();

   std::cout << __func__ << " Result=" << result << " Time=" << end - init << " ms" << std::endl;    
}

The function calculates the expression R has about 40 lines of code, it is around 30% faster that sequential algorithms, but you have to write lot of code to perform the tasks. You have to think about how to run threads, how to join them, etc, etc.

The async algorigthm

void usingSync (const std::vector<int>& values)
{
   long init = millisecond ();
   
   std::future <double> futAvg = std::async (std::launch::async, averageMethod, std::ref (values));
   std::future <int> futMax = std::async (std::launch::async, maxMethod, std::ref (values));
   std::future <int> futMin = std::async (std::launch::async, minMethod, std::ref (values));
   
   double average = futAvg.get ();
   int max = futMax.get ();
   int min = futMin.get ();

   double result = max - min / average;

   long end = millisecond ();

   std::cout << __func__ << " Result=" << result << " Time=" << end - init << " ms" << std::endl;    
}

The function calculates R expression has about 15 lines of code, it is around 30% faster that sequential algorithm. I think it is offering the best relation between performance and complexity of the code. You could get similar performance to a pure future&promise solution but you only has to use a very high abstraction level to get the best of multithreading.