GPU621/CamelCaseTeam

From CDOT Wiki
Revision as of 19:58, 28 July 2021 by Htruong (talk | contribs) (C++11 Threading - adding example)
Jump to: navigation, search


GPU621/DPS921 | Participants | Groups and Projects | Resources | Glossary

Project Name

C++11 Threads Library Comparison to OpenMP

Group Members

Andrei Fedchenko Hung Truong Ren Ren

Overview

Our team will do a research on comparison between c++ 11 threads library and openmp. We will code problems with both libraries to demonstrate the coding differences and do the performance analysis on those two approaches. We will highlight the techniques and best practices to achieve the best performance and avoid the pitfalls.

Get start with c++ threading

C++ Threading is supported after c++11 standard, previously, it used pthread and windows api or boost library to do multithreading.

In order to start with threading in c++, we need to include <thread>, this includes thread functions and classes. For protecting shared data, we will need to include other headers like <mutex> <condition_variable>, etc.

std::thread work with all callable type, lambda, functor(function object), function pointer. Here are some examples:

1. function pointer

void hello() {
	std::cout << "hello wolrd" << std::endl;
}
std::thread a(hello);

2. lambda

int num1 = 0;
int num2 = 1;
int num3 = 0;

std::thread b([]{
	num3 = num1 + num2;
});

3. functor

class Func {
	void operator()() {
		std::cout << "print in class f operator" << std::endl;
	}
}
Func f();
std::thread c(f);

We need to decide If we want to wait for it to finish (join) or If we want it to run in the background (detach) before the thread object being deconstructed, because it will call std::terminate in the deconstructor and throw a error in runtime.

t.join() -------- synchronized function called in parent thread to wait for the child thread to finish.

t.detach() ------- let threads to run in the background.

C++11 Threading

C++ threading consists of the creation of a thread that works on a function. By having multiple threads, multiple functions are able to be performed at the same time which enables parallel processing.

template<class Function, class... args> 
explicit thread( Function&& f, Args&&... args);

As seen above, a single thread takes a function through overloading and performs the task by passing the arguments to the function parameter which would execute separate to other threads. Once these threads are finished performing their function, the child thread must be joined using the .join() function in the thread library to the parent thread.

#include <thread>
#include <iostream>
#include <chrono>

void thread1() {
	std::this_thread::sleep_for(std::chrono::seconds(2));
	std::cout << "Hello from thread 1" << std::endl;
}

void thread2() {
	std::this_thread::sleep_for(std::chrono::seconds(3));
	std::cout << "Hello from thread 2" << std::endl;
}

void thread3() {
	std::this_thread::sleep_for(std::chrono::seconds(1));
	std::cout << "Hello from thread 3" << std::endl;
}

int main() {
	std::cout << "Creating thread 1: " << std::endl;
	std::thread t1(thread1); //appears 2nd

	std::cout << "Creating thread 2: " << std::endl;
	std::thread t2(thread2); //appears 3rd

	std::cout << "Creating thread 3: " << std::endl;
	std::thread t3(thread3); //appears 1st

	t1.join();
	t2.join();
	t3.join();
}

//Creating thread 1:
//Creating thread 2:
//Creating thread 3:
//Hello from thread 3
//Hello from thread 1
//Hello from thread 2

The code above gives a simple example on how threads are created and joined back to the parent thread in the end.

Usage of C++11 Threading

#include <thread>
#include <iostream>
#include <chrono>
#include <vector>
#include <mutex>

#define MAX_THREAD 16
std::mutex mtx; //creates critical section

int main() {
	const int iteration = 100000000;
	const int runs = 5;
	for (int x = 0; x < MAX_THREAD; x++) { //iteration over the max amount of cores.
		auto t = 0;
		int count = 0;
		for (int y = 0; y < runs; y++) {
			count = 0;
			std::vector<std::thread> threads;


			//setting the remainder count outside of count, neglectible at larger iteration, max value of number of thread - 1
			for (int i = 0; i < iteration % (x + 1); i++) {
				count++;
			}

			std::chrono::steady_clock::time_point ts = std::chrono::steady_clock::now();
			for (int j = 0; j < x + 1; j++) {
				threads.push_back(
					std::thread([x, iteration, &count]() {
						mtx.lock();
						for (int k = 0; k < iteration / (x + 1); k++) {
							count++;
						}
						mtx.unlock();
						}));
			}

			for (auto& thrd : threads) {
				thrd.join();
			}

			std::chrono::steady_clock::time_point te = std::chrono::steady_clock::now();

			auto temp = std::chrono::duration_cast<std::chrono::milliseconds>(te - ts);
			t += temp.count();
		}
		std::cout <<"C11 Thread - Counting to: " << count << " - Number of threads created: " << x+1 << " | elapsed time: " << t/runs << " ms" << std::endl;
	}
}
C11 Thread - Counting to: 100000000 - Number of threads created: 1 | elapsed time: 243 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 2 | elapsed time: 238 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 3 | elapsed time: 241 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 4 | elapsed time: 224 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 5 | elapsed time: 225 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 6 | elapsed time: 224 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 7 | elapsed time: 228 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 8 | elapsed time: 225 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 9 | elapsed time: 223 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 10 | elapsed time: 231 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 11 | elapsed time: 226 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 12 | elapsed time: 225 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 13 | elapsed time: 222 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 14 | elapsed time: 225 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 15 | elapsed time: 226 ms
C11 Thread - Counting to: 100000000 - Number of threads created: 16 | elapsed time: 223 ms

OpenMP Threading

OpenMP threads consists of a parallel region that forks a master thread into multiple child threads that each performs a function in parallel amongst each other and joins back to its parent thread after reaching the end of the parallel region.

#pragma omp construct [clause, ...]
structured block

The construct identifies what block of code is being executed in parallel and the clause qualifies the parallel construct. In comparison to the C++ 11 thread library, the threads are performed in a region while the C++ 11 thread library have to be specifically created and joined back to the parent thread.

#include <iostream>
#include <omp.h>

void main() {
	omp_set_num_threads(3);
	#pragma omp parallel
	{
		int tid = omp_get_thread_num();
		#pragma omp critical
		std::cout << "Thread " << tid + 1 << std::endl;
	}
}

//Thread 1
//Thread 2
//Thread 3

The code above shows the creation of 3 threads in a parallel region which are performed in sequence using critical. This highlights the difference between the c++11 thread library as no joining is required after the threads concludes their function.

References

Thread Support Library https://en.cppreference.com/w/cpp/thread