我假設你想要做的是檢查collatz猜想是否適用於所有數字。您發佈的程序在多個層面上都是錯誤的,無論是串行還是並行。
if (mpz_cmp_ui(x.get_mpz_t(), 1)) break;
意味着它會在x != 1
中斷。如果用正確的0 == mpz_cmp_ui
代替它,代碼將會一遍又一遍地繼續測試2
。無論如何,你必須有兩個變量,一個用於表示你想要檢查的外部循環,另一個用於執行檢查的內部循環。它更容易得到這個權利,如果你做了一個功能:
void check_collatz(mpz_class n) {
while (n != 1) {
n = collatz(n);
}
}
int main() {
mpz_class x = 1;
while (true) {
std::cout << x.get_str(10);
check_collatz(x);
x++;
}
}
的while (true)
環是壞推理和並行化,所以我們只讓一個等效for
循環:
for (mpz_class x = 1;; x++) {
check_collatz(x);
}
現在,我們可以談談並行化的代碼。 OpenMP並行化的基礎是工作共享構造。你不能只在一個while循環中打#pragma omp parallel
。幸運的是,您可以使用#pragma omp parallel for
輕鬆標記某些規範的循環。對於這一點,但是,你不能使用mpz_class
作爲循環變量,你必須爲循環指定結束:
#pragma omp parallel for
for (long check = 1; check <= std::numeric_limits<long>::max(); check++)
{
check_collatz(check);
}
注意check
是隱式私有,則每個線程它的工作副本。另外,OpenMP將負責分發工作[1 ...2^63]之間的線程。當一個線程調用check_collatz
時,將爲其創建一個新的私有對象mpz_class
。
現在,您可能會注意到,在每次循環迭代中反覆創建一個新的mpz_class
對象代價很高(內存分配)。您可以重新使用它(再次打破check_collatz
)並創建一個線程專用工作對象mpz_class
。對於這一點,拆分該化合物parallel for
成獨立parallel
和for
編譯指示:
#include <gmpxx.h>
#include <iostream>
#include <limits>
// Avoid copying objects by taking and modifying a reference
void collatz(mpz_class& n)
{
if (mpz_odd_p(n.get_mpz_t()))
{
n *= 3;
n += 1;
}
else
{
n /= 2;
}
}
int main()
{
#pragma omp parallel
{
mpz_class x;
#pragma omp for
for (long check = 1; check <= std::numeric_limits<long>::max(); check++)
{
// Note: The structure of this fits perfectly in a for loop.
for (x = check; x != 1; collatz(x));
}
}
}
注意,在並行區域聲明x
將確保它是隱含私人和正確初始化。你應該更喜歡在外面宣佈並標記它private
。這通常會導致混淆,因爲來自外部作用域的變量明確地爲private
爲單位。
你可能會抱怨說這隻會檢查前2^63個數字。讓它運行。這給你足夠的時間來掌握OpenMP到專家級別,併爲GMP對象編寫自己的定製工作共享。
您擔心每個線程都有額外的對象。這是基本良好的性能。你無法用鎖/關鍵部分/原子來有效地解決這個問題。你必須保護每一個讀寫到你唯一的相關變量。沒有平行性。
注意:巨大的循環可能會有負載不平衡。所以有些線程可能會比其他線程早幾個世紀才能完成。你可以通過動態調度或者更小的靜態塊來解決這個問題。
編輯:對於學術的緣故,這裏是一個知道如何直接實現在GMP對象的工作共享:
#pragma omp parallel
{
// Note this is not a "parallel" loop
// these are just separate loops on distinct strided
int nthreads = omp_num_threads();
mpz_class check = 1;
// we already checked those in the other program
check += std::numeric_limits<long>::max();
check += omp_get_thread_num();
mpz_class x;
for (; ; check += nthreads)
{
// Note: The structure of this fits perfectly in a for loop.
for (x = check; x != 1; collatz(x));
}
}
雖然這打破了使用GMP的目的,不是嗎?由於for循環只能運行到'long'的最大值。我想使用多精度庫以避免受到可變大小的限制。 –
那麼,它仍然使用GMP來進行實際的拼貼運行。爲了演示,我添加了一個草圖,您可以如何手動進行基於GMP對象的工作共享。您可以在完成第一個2^63數字後開始。 – Zulan
非常感謝您提供的這樣一個全面而且寫得很好的答案! –