Latency in Multithreading Execution vs Sequential Execution:

Goal: In this post, we will understand the difference in execution time in case of multithreading execution vs sequential execution.

To understand which execution takes more time, we will take one simple assumption i.e. the program that we are going to execute does not have any IO operations.

We are going to write two functions:

  1. Function1 (it will calculate the sum of all the odd numbers from 1 to 100).
  2. Function2 (it will calculate the sum of all the even numbers from 1 to 100).

So following is the code for the above operations:

#include<iostream>
#include <pthread.h>
#include<thread>
using namespace std;
using namespace std::chrono;

int oddSum;
int evenSum;

void function1(int val)
{
	oddSum = 0;

	while(val>=0)  //iterate through all numbers from 100 to 0
	{
		if((val&1)==0) //check if number is divisible by 2 or not
		{
			oddSum = oddSum+val;
		}
		val--;
	}
}

void function2(int val)
{
	evenSum = 0;

	while(val>=0) //iterate through all numbers from 100 to 0
	{
		if((val&1)==1) //check if number is divisible by 2 or not
		{
			evenSum = evenSum+val;
		}
		val--;
	}
}

int main()
{
	//calling the function using multithreading
	cout<<"\n***********************************************************"<<endl;
	auto startTime1 = std::chrono::system_clock::now();
	std::thread t1(function1,100);
	std::thread t2(function2,100);

	t1.join();
	t2.join();
	
	cout<<"Sum of all even numbers from 1 to 100:\t"<<evenSum<<endl;
	cout<<"Sum of all odd numbers from 1 to 100:\t"<<oddSum<<endl;
	auto endTime1 = std::chrono::system_clock::now();

	auto duration1 = duration_cast<microseconds>(endTime1-startTime1);
	cout<<"Time taken using Multithreaded Execution:\t"<<duration1.count()<<endl;

	//calling the function using sequential execution

	cout<<"\n***********************************************************\n"<<endl;

	auto startTime2 = std::chrono::system_clock::now();
	function1(100);
	function2(100);

	cout<<"Sum of all even numbers from 1 to 100:\t"<<evenSum<<endl;
	cout<<"Sum of all odd numbers from 1 to 100:\t"<<oddSum<<endl;
	auto endTime2 = std::chrono::system_clock::now();

	auto duration2 = std::chrono::duration_cast<microseconds>(endTime2-startTime2);
	cout<<"Time taken using Sequential Execution:\t"<<duration2.count()<<endl; 

	return 0;
}

Let’s understand the above code:

At line number 42, we are recording the start time of the multithreaded execution. We call function1 and function2 at Line numbers 43 and 44 respectively. Once the execution of thread t1 and t2 completes, we join both the threads to the main thread and print the result. After the completion of all the steps, we again record the time and print it in microseconds.

Now, we perform the same operations again, but this time in the sequential fashion and, print the execution time and result.

Now, think about the scenario, the code is not having any I/O operations, which one (Multithreaded or Sequential Execution) will have more execution time. You can run the above code on your system using the below command:

g++ -std=c++14 -pthread multithreadingVsSequentialExe.cpp
./a.out

Below are the results when we execute the code on our system(results might be different on different machines due to the hardware change):

***********************************************************
Sum of all even numbers from 1 to 100:	2500
Sum of all odd numbers from 1 to 100:	2550
Time taken using Multithreaded Execution:	255

***********************************************************

Sum of all even numbers from 1 to 100:	2500
Sum of all odd numbers from 1 to 100:	2550
Time taken using Sequential Execution:	6

When you will look at the results, you will wonder that multithreaded execution is taking more time as compared to sequential execution. Why is that the case?

Let’s understand it.

So when we are calling function1 and function2 using thread t1 and t2. Many context switches take place between thread t1 and t2 i.e. for x microseconds t1 runs and y microseconds t2 runs and so on. But when a context switch takes place, there is some latency associated with the context switch. It results in increasing the execution time for multithreaded execution.

So let suppose k numbers of context switches take place during the multithreaded execution then total execution time can be calculated using the below formula:

Execution time using Multithreading = function1 exe. time + function2 execution time + Number of context Switches * Latency in One Context Switch

Time Taken In Multithreaded Execution

But in case of sequential execution, function1 executes first and then function2. There is no context switch associated with the sequential execution. So time taken in sequential execution can be calculated using the below formula:

Execution time using Sequential Execution= function1 execution time + function2 execution time

Time in Sequential Execution

This blog might be an interesting read: Multithreading and MultiProcessing using Python

Posted in C++