我正在嘗試使用 openmp 學習并行化。
此代碼執行二維蒙特卡羅積分。程式執行(使用 omp)時間約為 25 秒 N = 9 億
#include <cmath>
#include <cstdint>
#include <iostream>
#include <omp.h>
#include <random>
double func(double x, double y) {
return sqrt(x y);
}
using std::cout;
using std::endl;
const double a = 0.0, b = 1.0, c = 3.0, d = 5.0;
const uint64_t N = 900000000;
int main() {
double sum = 0.0;
std::random_device rd;
#pragma omp parallel
{
uint32_t seed;
#pragma omp critical
{
seed = rd();
}
std::mt19937 gen(seed);
std::uniform_real_distribution<double> dist(0.0, 1.0);
#pragma omp for reduction( : sum)
for (int64_t i = 0; i < N; i ) {
double x = a (b - a) * dist(gen);
double y = c (d - c) * dist(gen);
sum = func(x, y);
// sum - local for each thread
// initialized to 0 in each thread
// and will be added to the global sum when exiting the loop
}
}
// The result is calculated after the parallel section
double ans = sum * (b - a) * (d - c)/ N;
cout << ans<<endl;
return 0;
}
如何將其轉換為執行緒安全代碼(教授說它不是執行緒安全的)?
uj5u.com熱心網友回復:
據我所知,你的教授錯了。代碼是執行緒安全的。歸約條款寫得正確。
我唯一的問題是初始化亂數生成器的方式。
random_device每個執行緒呼叫一次效率低下- 它打開了生日問題的機會。兩個執行緒有??一個(非常小的)機會以相同的隨機狀態開始,因為它們接收到相同的隨機值。
創建多個隨機序列的更有效方法是將每個執行緒的種子增加 1。一個好的亂數生成器會將其轉換為截然不同的隨機值。例如,請參見此處:https : //www.johndcook.com/blog/2016/01/29/random-number-generator-seed-mistakes/
另一個小問題是您可以將減少移動到平行部分的末端。然后您可以將 for 回圈宣告為 nowait 以避免在回圈結束時出現一個隱式 openmp 障礙。這種優化是可能的,因為您不需要并行部分內的最終總和。但是,這種變化只是一種優化。它不會改變您代碼的正確性。
代碼看起來像這樣:
#include <cmath>
// using std::sqrt
#include <random>
// using std::random_device, std::mt19937, std::uniform_real_distribution
#include <iostream>
// using std::cout
#include <omp.h>
// using omp_get_thread_num
int main() {
const double a = 0.0, b = 1.0, c = 3.0, d = 5.0;
const int64_t N = 900000000;
double sum = 0.0;
std::random_device rd;
// initial seed
const std::random_device::result_type seed = rd();
# pragma omp parallel reduction( : sum)
{
std::uniform_real_distribution<double> dist(0.0, 1.0);
/*
* We add the thread number to the seed. This is better than using multiple
* random numbers because it avoids the birthday problem. See for example
* https://www.johndcook.com/blog/2016/01/29/random-number-generator-seed-mistakes/
*/
std::mt19937 gen(seed static_cast<unsigned>(omp_get_thread_num()));
# pragma omp for nowait
for(int64_t i = 0; i < N; i) {
const double x = a (b - a) * dist(gen);
const double y = c (d - c) * dist(gen);
sum = std::sqrt(x y);
}
}
const double ans = sum * (b - a) * (d - c)/ N;
std::cout << ans << '\n';
return 0;
}
另請注意如何縮進編譯指示以提高可讀性。只需將 # 保留在行首。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/388760.html
