C++
Współbieżność z OpenMP
Szukaj…
Wprowadzenie
Ten temat obejmuje podstawy współbieżności w C ++ przy użyciu OpenMP. OpenMP jest udokumentowany bardziej szczegółowo w znaczniku OpenMP .
Równoległość lub współbieżność oznacza wykonanie kodu w tym samym czasie.
Uwagi
OpenMP nie wymaga żadnych specjalnych nagłówków ani bibliotek, ponieważ jest wbudowaną funkcją kompilatora. Jeśli jednak korzystasz z funkcji API OpenMP, takich jak omp_get_thread_num()
, musisz dołączyć omp.h
i jego bibliotekę.
Instrukcje pragma
OpenMP są ignorowane, gdy opcja OpenMP nie jest włączona podczas kompilacji. Możesz skorzystać z opcji kompilatora w instrukcji kompilatora.
- GCC używa
-fopenmp
- Clang używa
-fopenmp
- MSVC używa
/openmp
OpenMP: sekcje równoległe
Ten przykład ilustruje podstawy wykonywania sekcji kodu równolegle.
Ponieważ OpenMP jest wbudowaną funkcją kompilatora, działa na wszystkich obsługiwanych kompilatorach bez dołączania bibliotek. Możesz dołączyć omp.h
jeśli chcesz skorzystać z dowolnej funkcji API openMP.
Przykładowy kod
std::cout << "begin ";
// This pragma statement hints the compiler that the
// contents within the { } are to be executed in as
// parallel sections using openMP, the compiler will
// generate this chunk of code for parallel execution
#pragma omp parallel sections
{
// This pragma statement hints the compiler that
// this is a section that can be executed in parallel
// with other section, a single section will be executed
// by a single thread.
// Note that it is "section" as opposed to "sections" above
#pragma omp section
{
std::cout << "hello " << std::endl;
/** Do something **/
}
#pragma omp section
{
std::cout << "world " << std::endl;
/** Do something **/
}
}
// This line will not be executed until all the
// sections defined above terminates
std::cout << "end" << std::endl;
Wyjścia
Ten przykład daje 2 możliwe wyniki i jest zależny od systemu operacyjnego i sprzętu. Dane wyjściowe ilustrują również problem warunków wyścigu , który wystąpiłby w wyniku takiej implementacji.
WYJŚCIE A | WYJŚCIE B |
---|---|
zacznij cześć koniec świata | zacznij świat witaj koniec |
OpenMP: sekcje równoległe
Ten przykład pokazuje, jak równolegle wykonywać części kodu
std::cout << "begin ";
// Start of parallel sections
#pragma omp parallel sections
{
// Execute these sections in parallel
#pragma omp section
{
... do something ...
std::cout << "hello ";
}
#pragma omp section
{
... do something ...
std::cout << "world ";
}
#pragma omp section
{
... do something ...
std::cout << "forever ";
}
}
// end of parallel sections
std::cout << "end";
Wynik
- zacznij cześć świat na zawsze koniec
- zacznij świat witaj na zawsze koniec
- zacznij cześć na zawsze koniec świata
- zacznij na zawsze cześć koniec świata
Ponieważ zlecenie wykonania nie jest gwarantowane, możesz obserwować dowolne z powyższych wyników.
OpenMP: Parallel For Loop
Ten przykład pokazuje, jak podzielić pętlę na równe części i wykonać je równolegle.
// Splits element vector into element.size() / Thread Qty
// and allocate that range for each thread.
#pragma omp parallel for
for (size_t i = 0; i < element.size(); ++i)
element[i] = ...
// Example Allocation (100 element per thread)
// Thread 1 : 0 ~ 99
// Thread 2 : 100 ~ 199
// Thread 2 : 200 ~ 299
// ...
// Continue process
// Only when all threads completed their allocated
// loop job
...
* Zachowaj szczególną ostrożność, aby nie modyfikować wielkości wektora używanego równolegle dla pętli, ponieważ indeksy przydzielonych zakresów nie aktualizują się automatycznie .
OpenMP: Parallel Gathering / Reduction
Ten przykład ilustruje koncepcję przeprowadzania redukcji lub gromadzenia przy użyciu std::vector
i OpenMP.
Załóżmy, że mamy scenariusz, w którym chcemy, aby wiele wątków pomogło nam wygenerować wiele rzeczy, int
jest tutaj stosowany dla uproszczenia i może być zastąpiony innymi typami danych.
Jest to szczególnie przydatne, gdy trzeba scalić wyniki z urządzeń podrzędnych, aby uniknąć błędów segmentacji lub naruszeń dostępu do pamięci i nie chce się używać bibliotek ani niestandardowych bibliotek kontenerów synchronizacji.
// The Master vector
// We want a vector of results gathered from slave threads
std::vector<int> Master;
// Hint the compiler to parallelize this { } of code
// with all available threads (usually the same as logical processor qty)
#pragma omp parallel
{
// In this area, you can write any code you want for each
// slave thread, in this case a vector to hold each of their results
// We don't have to worry about how many threads were spawn or if we need
// to repeat this declaration or not.
std::vector<int> Slave;
// Tell the compiler to use all threads allocated for this parallel region
// to perform this loop in parts. Actual load appx = 1000000 / Thread Qty
// The nowait keyword tells the compiler that the slave threads don't
// have to wait for all other slaves to finish this for loop job
#pragma omp for nowait
for (size_t i = 0; i < 1000000; ++i
{
/* Do something */
....
Slave.push_back(...);
}
// Slaves that finished their part of the job
// will perform this thread by thread one at a time
// critical section ensures that only 0 or 1 thread performs
// the { } at any time
#pragma omp critical
{
// Merge slave into master
// use move iterators instead, avoid copy unless
// you want to use it for something else after this section
Master.insert(Master.end(),
std::make_move_iterator(Slave.begin()),
std::make_move_iterator(Slave.end()));
}
}
// Have fun with Master vector
...