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。