D. schedule 子句
平行區域在其結尾至少有一個屏障,而且可能會有額外的屏障。 在每個屏障上,小組的其他成員必須等候最後一個線程到達。 若要將此等候時間降到最低,應該散發共用工作,讓所有線程同時到達屏障。 如果部分共用工作包含在建構中 for
, schedule
子句就可用於此目的。
當相同物件重複參考時,建構的排程 for
選擇主要取決於記憶體系統的特性,例如快取的存在和大小,以及記憶體存取時間是否統一或不一致。 這類考慮可能會讓每個線程一致地參考一系列迴圈中陣列的相同元素集,即使某些線程在某些迴圈中指派的工作相對較少也一樣。 您可以使用排程搭配所有迴圈的相同界限來完成 static
此設定。 在下列範例中,零會當做第二個迴圈中的下限使用,即使 k
排程並不重要,還是比較自然。
#pragma omp parallel
{
#pragma omp for schedule(static)
for(i=0; i<n; i++)
a[i] = work1(i);
#pragma omp for schedule(static)
for(i=0; i<n; i++)
if(i>=k) a[i] += work2(i);
}
在其餘範例中,假設記憶體存取不是主要考慮。 除非另有說明,否則會假設所有線程都會收到可比較的計算資源。 在這些情況下,建構的排程選擇取決於在最接近前一個 for
屏障與隱含關閉屏障或最近的即將推出的屏障之間執行的所有共用工作,如果有 nowait
子句。 針對每種排程,簡短範例會顯示該排程種類可能是最佳選擇。 每個範例都會進行簡短的討論。
排 static
程也適用於最簡單的案例,這是包含單 for
一建構的平行區域,而每個反覆專案都需要相同的工作量。
#pragma omp parallel for schedule(static)
for(i=0; i<n; i++) {
invariant_amount_of_work(i);
}
排 static
程的特點是每個線程取得的反覆項目數目與任何其他線程大致相同的屬性,而且每個線程都可以獨立判斷指派給它的反覆專案。 因此,不需要同步處理才能散發工作,而且假設每個反覆專案都需要相同的工作量,所有線程都應該同時完成。
針對 p 線程小組,let ceiling(n/p) 是整數 q,其滿足 n = p*q - r,0< = r < p。 此範例的static
一個排程實作會將q反覆專案指派給第一個 p-1 線程,並將q-r反覆專案指派給最後一個線程。 另一個可接受的實作會將 q 反覆運算指派給第一個 p-r 線程,並將 q-1 反覆運算指派給其餘的 r 線程。 此範例說明程序為何不應該依賴特定實作的詳細數據。
此 dynamic
排程適用於具有需要不同甚至無法預測工作量之反覆專案的建構案例 for
。
#pragma omp parallel for schedule(dynamic)
for(i=0; i<n; i++) {
unpredictable_amount_of_work(i);
}
排 dynamic
程的特點是 屬性,沒有線程在屏障等候的時間比執行其最終反覆專案需要另一個線程的時間更長。 這項需求表示反覆項目必須一次指派給線程,因為它們可供使用,且每個指派的同步處理。 您可以藉由指定大於 1 的區塊大小下限來減少同步處理額外負荷,以便一次指派 k 個線程,直到保留少於 k。 這可確保沒有線程在屏障上等候的時間比執行另一個線程執行其最後一個區塊 (最多) k 反覆運算的時間還要長。
dynamic
如果線程收到不同的計算資源,排程可能會很有用,這與每次反覆運算的工作量大相等。 同樣地,如果線程以不同的時間抵達 for
建構,動態排程也很有用,不過在這些情況下, guided
排程可能比較好。
排 guided
程適用於線程可能會在建構時以不同時間 for
抵達,且每個反覆專案都需要大約相同工作量的反覆專案。 例如,如果建構前面有一或多個區段或for
具有 nowait
子句的建構,for
就可能發生這種情況。
#pragma omp parallel
{
#pragma omp sections nowait
{
// ...
}
#pragma omp for schedule(guided)
for(i=0; i<n; i++) {
invariant_amount_of_work(i);
}
}
和 一樣dynamic
,排guided
程保證沒有線程在屏障上等候的時間比執行其最終反覆運算所花費的時間還長,如果指定 k 區塊大小,則為最後 k 個反覆運算。 在這些排程中, guided
排程的特點是其需要最少同步處理的屬性。 針對區塊大小 k,一般實作會將 q = ceiling(n/p) 反覆運算指派給第一個可用的線程、將 n 設定為較大的 n-q 和 p*k,並重複直到指派所有反覆項目為止。
當最佳排程的選擇不像這些範例那麼清楚時, runtime
排程就方便試驗不同的排程和區塊大小,而不需要修改和重新編譯程式。 當最佳排程取決於套用程式之輸入數據的一些可預測方式時,它也很有用。
若要查看不同排程之間的取捨範例,請考慮在八個線程之間共用 1000 個反覆專案。 假設每個反覆專案中都有不可變的工作量,並使用該工作作為時間單位。
如果所有線程同時啟動,排 static
程會導致建構以125個單位執行,且沒有同步處理。 但假設一個線程在抵達後是100個單位。 然後,剩餘的七個線程會在屏障等候 100 個單位,而整個建構的運行時間則增加到 225。
dynamic
由於和 guided
排程都可確保沒有任何線程在屏障上等候一個以上的單位,因此延遲線程會導致建構的執行時間只增加至 138 個單位,而可能會因為同步處理的延遲而增加。 如果這類延遲不可以忽略,則當預設區塊大小為1000 dynamic
時,同步處理的數目就變得很重要,但對於而言只有41 guided
個。 區塊大小為 25, dynamic
且 guided
兩者都以 150 個單位完成,再加上所需同步處理的任何延遲,現在分別編號為 40 和 20。