上節已說明不同 Run 的 paths 呼叫同一 shared object 的 method,則這些 path 間有直接依存關係,由不同的 shared object 所找出之有直接依存關係的 path 組合
間又可能再構成間接依存關係;有間接依存關係的 path 組合可能包含二個以上 的依存關係之 path 組合,需將它們過濾掉,才能找出最少組合。因此,這個 phase 又分成三個步驟:(1)先找出所有因存取 shared object 而產生的直接資料流;(2) 再由直接資料流所有可能的連接方式找出間接的資料流;(3)過濾多餘的依存關 係 path 組合。
當我們允許存取點的動作為存或取時,不同 Run 上的 path,只要共同存取 同一個 shared object,就會產生雙向的直接資料流。因此針對每一個 shared object,將存取它的 paths 兩兩配對,只要配對的兩條 paths 分屬於不同的 Run,
就會在它們之間產生雙向的直接資料流。
若兩個存取點P1[NOS1]與 P2[NOS2]對同一個 shared object 有存取動作,且 P1與P2屬於同一個 Run,則有兩種可能性:(1) P1與P2為同一條 path,不會因 shared object 而獲得額外其它 Run 的資料;(2) P1與P2為同 Run 上的不同 path,
由於並行程式執行時,一個 Run 僅會執行一條 path,因此 P1與P2不可能同時被 執行。由此可知,存取同一個 shared object 的 paths 屬於同一個 Run 時,不會因 shared object 而交換資料。考慮直接資料流時,應排除屬於同一個 Run 的 paths。
要找出 paths 間的資料流,首先必需對欲測試之 Java 程式碼作靜態分析,以 找出所有共享類別的 Run、各個 Run 所含的 expanded paths,以及每一條 path 呼 叫 shared object 的函式的 statement,而後對每一個 shared object/class,找出呼叫 這 個 shared object/class 的 二 條 paths 。 如 圖 3 - 5 所 示 , 對 每 一 個 shared object/class,建立存取點的相關資料,包括存取該 shared object 的 paths 及對應的 存取點位置,以 path[NOS] (NOS, number of statement)表示之。
圖3-5 shared object 資料結構示意圖
依序對一個 shared object Si之任二存取點APj, APk組成一個直接依存關係組 合,並將二條資料流APj.path[APj.NOS] Æ APk.path[APk.NOS]與 APj.path[APj.NOS]
Æ APk.path[APk.NOS]儲存於陣列 DF 中,如圖3-6所示,儲存的內容包括這 二個存取點所屬的 Run,path,及 statement number。找出直接資料流的演算法則 如圖3-7示。
access_count 3
本例中,S3 共被存
找出所有的直接data-flow DFj(k.src_NOS = APj.NOS;
DFj(k.dest_path = APk.path;
間接資料流 A1[100] Æ B1[80] Æ B1[130] Æ C1[150],是由 A1[100] Æ B1[80]與 B1[130] Æ C1[150]連接而成的,第一條直接資料流之 dest_path 的 NOS 必需小於 第二條直接資料流之src_path 的 NOS。兩者的關係可以類似 BNF 的表示法表示 如下:
DIRECT-DATA-FLOW ::=
PATH[NOS]ÆPATH[NOS]
INDIRECT-DATA-FLOW ::=
DIRECT-DATA-FLOWÆDIRECT-DATA-FLOW | DIRECT-DATA-FLOWÆINDIRECT-DATA-FLOW
圖3-8 直接與間接資料流之關係
據此,可使用資料流串鏈(data-flow chain)的方式來表示直接與間接的資料 流,如圖3-9。
DATA-FLOW-CHAIN ::=
DIRECT-DATA-FLOW
| DIRECT-DATA-FLOWÆDATA-FLOW-CHAIN 圖3-9 資料流串鏈
資料流串鏈係由一直接資料流與另一條資料流串鏈連接而成,可使用 linked-list 資料結構予以表示,如圖3-10所示。圖中 DFC1表含四條直接資料 流的資料流串鏈 A1[100] Æ B1[80] Æ B1[130] Æ C1[35] Æ C1[150] Æ D1[90] Æ D1[110] Æ E1[55],而 DFC4表示僅含一條直接資料流的資料流串鏈 D1[110] Æ E1[55]。有 n 條資料流的資料流串鏈,稱其長度為 n。
圖3-10 資料流串鏈示意圖
將所有直接資料流依dest_path 及 dest_NOS 排序後,便可依序從每一條直接 資料流之src_path 尋找可串接之直接資料流,若有找到則組成一資料流串鏈,再 用串鏈第一個直接資料流之src_path 去尋找可再串接之直接資料流,依此類推,
即可找到所有的資料流串鏈。上列資料結構,如圖3-11所示,在 path B1 上有 三條流入的直接資料流,data_flow_list 中表示流入順序。當所有間接資料流均找 完後,可用圖3-12之資料結構來儲存,以方便過濾有包含關係的資料流,圖 中 DFC1 是一個代表資料流串鏈 A1[100] Æ B1[80] Æ B1[130] Æ C1[35]的 linked-list item,起始於直接資料流 A1[100] Æ B1[80],並連接至另一資料流串鏈 DFC2:B1[130] Æ C1[35]。在 DFC1 中以兩個陣列記錄資料流串鏈在每一個 Run 所 經 過 的 path(path_of_run) , 以 及 每 條 經 過 的 path 上 最 早 的 存 取 點 位 置 (path_NOS)。尋找資料流串鏈的演算法如下圖3-13所列。
A1[100]ÆB1[80]
B1[130]ÆC1[35]
C1[150]ÆD1[90]
D1[110]ÆE1[55] NULL DFC1
DFC2
DFC3
DFC4
圖3-11 path 中的存取點資料結構示意圖
圖3-12 資料流串鏈資料結構示意圖 TARGET:
找出所有的data-flow chain INPUT: RunA RunB RunC RunD
×
/* 初始時,所有的 DFCi都為空陣列。 */
ALGORITHM:
{
// 首先從直接data-flow 產生長度為 1 的 data-flow chain for all data-flow DFi in DF
{
new data-flow chain DFC1i = (DFi Æ NULL);
set all items in DFC1i.path_of_run to NULL;
DFC1i.path_of_run[run of DFi.src_path] = DFi.src_path;
DFC1i.path_NOS[run of DFi.src_path] = DFi.src_NOS; DFC1i.path_of_run[run of DFi.dest_path] =
DFi.dest_path;
DFC1i.path_NOS[run of DFi.dest_path] = DFi.dest_NOS;
add DFC1i into DFC1; }
// 使用DP 觀念,每次從長度為 i 的 data-flow chain 產生長度為 // i + 1 的 data-flow chain。
for(i = 1; DFCi is not empty; i = i + 1) {
for all data-flow chain DFCij in DFCi {
// 連接條件(1)
Extract source path of DFCij as P;
for all data-flow DFk in P.data_flow_list, 0 ≤ k < P.data_flow_count, and
DFk.dest_NOS ≤ source NOS of DFCij
{
// 連接條件(2)
if DFCijhas not passed any other paths belonging to run of DFk.src_path {
// 連接條件(3)
if (DFCij has not passed DFk.src_path) or (DFCij
has passed DFk.src_path and DFk.src_NOS <
DFCij.path_NOS[run of DFk.src_path]) {
new data-flow chain DFCi+1n = DFk Æ DFCij; copy all items in DFCik.path_of_run into
DFCi+1n.path_of_run;
copy all items in DFCik.path_NOS into DFCi+1n.path_NOS;
DFCi+1n.path_of_run[run of DFj.src_path] = DFj.src_path;
DFCi+1n.path_NOS[run of DFj.src_path] = DFj.src_NOS;
DFCi+1n.path_of_run[run of DFj.dest_path] = DFj.dest_path;
DFCi+1n.path_NOS[run of DFj.dest_path] = DFj.dest_NOS;
add DFCi+1n into DFCi+1; }}
} } } }
圖3-13 演算法:尋找資料流串鏈
每一個資料流串鏈代表的即為系統中一條可能的資料流,因此經過的 paths 都會產生依存性關係,測試時必須被包含於同一條 concurrent path 中,以保證這 些 paths 同時執行的情況被測試過。針對一個資料流串鏈,其經過的所有 paths,
可以形成一組有依存性關係的 paths 的集合,稱為 dependent path set。如上節圖 3-4的範例,其依存性有(1) A1 Æ B1 Æ C1,(2) B1 Æ A1,(3) C1 Æ B1,(4) C1 Æ D1,(5) D1 Æ C1 Æ B1,可產生的 dependent path sets 為(1) {A1, B1, C1},(2) {A1, B1},(3) B1, C1},(4) {C1, D1},(5) {B1, C1, D1}。
兩個 dependent path sets 間可能有包含關係,如集合{A1, B1, C1}包含了集合 {A1, B1},若一條 concurrent path 包含集合{A1, B1, C1},必然也會包含集合{A1, B1},測試這條 concurrent path 可以同時驗證這兩個 dependent path sets。因此若 兩個 dependent path sets 互有包含關係,可以不考慮較小的集合。
檢查兩 dependent path sets 是否有包含關係,應對兩者包含的每一條 path 進 行檢查,原是相當耗時的工作。為了增進這個檢查的效率,可將 dependent path set 儲存為位元陣列的形式,每一條可能的 path 佔一個位元,若 dependent path set DPSi包含這條 path,則將此位元設為 1,否則設為 0。如圖3-14所示。
A1 A2 A3 B1 B2 B3 C1 C2 C3 D1 D2 D3
A1B1C1 1 0 0 1 0 0 1 0 0 0 0 0
A2B2C1 0 1 0 0 1 0 1 0 0 0 0 0
A1D2 1 0 0 0 0 0 0 0 0 0 1 0
B3C2D1 0 0 0 0 0 1 0 1 0 1 0 0
圖3-14 以位元陣列儲存 dependent path set
若 dependent path set DPSi⊆ DPSj,對於每一條 path P,在位元陣列分別以 bi
與bj代表,數對(pi, pj)應為(1, 1)、(0, 0)或(0, 1),亦即具有 pi ^ pj = pi的性質。藉 由位元運算指令,我們可以很快速地判斷DPSi與DPSj是否具有包含關係。
TARGET:
判斷dependent path sets 是否有包含關係 INPUT:
BitArray DPSi, DPSj; /* 兩個 dependent path set */
OUTPUT:
Relation; /* DPSi與 DPSj的包含關係 */
ALGORITHM:
check_DPS_inclusion(DPSi, DPSj) {
make a bit-and operation between DPSi and DPSj, and store
the result as R;
if R = DPSi {
Relation = INCLUDE_POST; /* 表示前者包含後者 (DPSi⊆ DPSj) */
} else if R = DPSj {
Relation = INCLUDE_PRIOR; /* 表示後者包含前者 (DPSj⊂ DPSi) */
} else {
Relation = NO_INCLUSION; /* 表示兩者無包含關係 */
}
return Relation; }
圖3-15 演算法:判斷兩 dependent path set 是何包含關係
根據上述原則,產生 dependent path sets 時,應對每一個資料流串鏈 DFCij
加以處理,首先利用資料流串鏈中記錄的path_of_run 資訊,可擷取出資料流串 鏈DFCij所經過的 paths 的集合,將之儲存於 DPSnew中。一個新找到的 dependent path set DPSnew應檢查是否與已儲存在陣列DPS 中的每一個 dependent path set DPSk有包含關係。其可能情形有三:(1) DPSnew⊆ DPSk,則新的DPSnew可以不予 考慮。(2) DPSk⊂DPSnew,原來的DPSk可不予考慮,所以從 DPS 中刪除之,而 DPSnew則應儲存至陣列DPS 中。(3) DPSnew與任一DPSk皆無包含關係,則DPSnew
應新增至陣列DPS 中。最後產生的陣列 DPS 即為所有互不包含的 dependent path sets,演算法如下所列:
TARGET:
找出所有的dependent path sets INPUT:
Array DFC; /* 儲存data-flow chain 的陣列 */
OUTPUT:
Array DPS; /* 儲存所有dependent path set 的陣列, */
/* 初始為空陣列。 */
ALGORITHM:
{
for all data-flow chain DFCij, 1 ≤ i and DFCi is not empty,
1 ≤ j ≤ number of data-flow chain in DFCi {
extract paths from DFCij.path_of_run and set as DPSnew; set ADD_NEW_DPS flag; /* Initially assume DPSnew should be added into DPS */
for all dependent path set DPSk in DPS {
// 呼叫前述演算法以檢查包含關係
Relation = check_DPS_inclusion(DPSnew, DPSk);
// 包含情況(1)
if Relation = INCLUDE_POST { /* DPSnew⊆ DPSk */
clear ADD_NEW_DPS flag;
exit for-loop;
}
// 包含情況(2)
if Relation = INCLUDE_PRIOR { /* DPSk⊂ DPSnew */
remove DPSk from DPS;
} }
// 在包含情況(2)與(3)皆應將 DPSnew加入 DPS 中 if flag ADD_NEW_DPS is set {
add DPSnew into DPS;
} } }
圖3-16 演算法:尋找 dependent path sets
經過上述處理可找出所有 dependent path set,一 dependent path set 代表這些 分屬不同 Run 的 paths 有相互影響關係,需被同時測試。