• Nem Talált Eredményt

A programelemzés módszere

In document Párhuzamos algoritmusmodellek (Pldal 149-161)

Animation 3.2: The oscillating blinker pattern (periodlength: 2)

K. Jensen: Coloured Petri Nets and the Invariant-Method, Theoretical Computer Science 14 (1981), 317–336

6. fejezet - Párhuzamos programok

6.2. Szekvenciális programok párhuzamosítása

6.2.6.2.2. A programelemzés módszere

A programelemzés során egy szekvenciálisan reprezentált algoritmusról (programról) fogjuk megállapítani, hogy az egyes lépései (utasításai) milyen módon függenek egymástól.

A gyakorlatban készíthetünk olyan programokat, amelyekben szereplő utasításokról nem dönthető el egyszerűen, hogy megváltoztatható-e a végrehajtásuk sorrendje, azaz függőségben állnak-e egymással.

Tisztán elméleti szempontokat vizsgálva azok a programrészek hajthatók végre párhuzamosan, amelyek egymástól függetlenek a változóértékeiket tekintve. Világos, hogy a rekurzív részeket tartalmazó algoritmusok, illetve programok elemzése lényegesen bonyolultabb, mint az egyszerű iteratív komponensekből felépülőké.

Mutatók használatával és közvetlen memóriaelérés által, hasonló módon, meglehetősen komplikált programokat hozhatunk létre. Nem fogunk a jegyzetben kitérni az eljáráshívást tartalmazó esetekre sem, bár ezek kezelése nem jelent akkora nehézséget, ha néhány egyszerű szabályt betartunk.

Egyszerű megfigyelés alapján mondhatjuk, hogy az utasítások függőségének megállapítása nem a lineáris programrészletek esetén nehéz, hanem a ciklusokat is tartalmazó szakaszokon. Ez alapján ésszerűnek tűnik, ha a programok vizsgálata során első lépésként lineáris (ciklusmentes) szakaszokra bontjuk a kódot.

A felbontás után kezdhetünk bele az egyes szakaszok részletesebb elemzésébe.

Példa (buborékrendezés lineáris szakaszokra bontása)

I. szakasz

Amennyiben megtörtént a szakaszokra bontás, elkezdhetjük az egyes szakaszok önálló elemzését.

Ennek legfontosabb eleme, hogy létrehozunk egy függőségi gráfot.

A függőségi gráf csúcsai az egyes utasítások. Azon csúcsokat kötjük össze irányított éllel, amelyek közvetlen függőségben állnak egymással.

15. t ← b + g 16.

17. endif 18. endif

A 6.6. ábrán mutatjuk a program sorainak (utasításainak) függőségi gráfját.

6.6. ábra - A másodfokú egyenletet megoldó program utasításainak függőségi gráfja.

Az, hogy milyen függőségi gráfot hozunk létre, nemcsak magától a programtól, hanem az elemzés módszerétől is nagymértékben függ.

Példa (függőségi gráf többféle létrehozása) 1. x ← a

2. y ← b 3. t ← x 4. x ← x + y 5. x ← t 6. y ← xy

A 6.7. és 6.8 ábrák mutatnak két lehetséges függőségi gráfot.

6.7. ábra - A ,,függőségi gráf többféle létrehozása" példaprogram egyszerű szintaktikus

függésen alapuló függőségi gráfja.

6.8. ábra - A ,,függőségi gráf többféle létrehozása'' példaprogram mélyebb függésen alapuló függőségi gráfja.

A vizsgált egyszerűsített programok esetében az egyes utasítások függőségét tulajdonképpen csakis a változók függősége határozza meg. Mivel a vizsgált programszakaszok lineáris szerkezetűek, ezért az adatáramlás egyirányú bennük. Az egyes utasítások lehetséges végrehajtási sorrendjét az adatáramlás iránya határozza meg.

Két utasítás függőségi viszonyát még csak definiálni sem olyan egyszerű, mint elsőre látszik. A függőség ugyanis változik, ha más (mélyebb) elemzési módszert választunk. Egyelőre egy meglehetősen laza meghatározással dolgozunk: azt mondjuk, hogy a C2 utasítás közvetlenül függ a C1 utasítástól, ha a C1 kimenő adatai közül legalább egy változatlan formában bemenete C2-nek.

Példa (nem valódi függés)

1. x ← a 2. y ← b 3. x ← x + y 4. x ← x – y

Itt 4. csak látszólag függ 3-tól, voltaképpen olyan, mintha 3. és 4. végre sem hajtódna.

Az utasításokat a gráfban való elhelyezkedésük alapján szintekre bonthatjuk. A szinteket rekurzív módon a következőképpen definiálhatjuk:

Definíció: Egy ciklust nem tartalmazó program utasításaihoz a következő rekurzív módon adjuk meg az utasításszinteket:

0. szint: azok az utasítások, amelyek semelyik másiktól nem függenek.

i. szint (0 < i): ha már definiáltuk az i – 1. szint utasításait, ide azok az utasítások tartoznak, amelyeknek még nem definiáltuk a szintjét, és csak olyan utasításoktól függenek, amelyeknek már definiáltuk.

Megjegyzés: Ha egy utasítás az i. szinthez tartozik, akkor mindenképpen függenie kell egy olyantól, ami az i – 1. szinten található. Ha nem így lenne, akkor a definíció alapján az i – 1. (vagy még korábbi) szinthez tartozna.

Megjegyzés: Az előbbi definíció minden utasításhoz egyértelműen rendel szintértéket.

Mivel egy program véges sok utasításból áll, ezért a gráfnak lesz egy legalsó szintje. Ezt a szintet kimeneti szintnek, a 0. szintet pedig bemeneti szintnek nevezzük.

Az utasítások függősége az adott utasítások által használt adatokon, változókon múlik. Leegyszerűsíthetjük tehát az utasítások függőségének vizsgálatát az adatok függőségének vizsgálatára. Ehhez szükségünk lesz egy újabb fogalomra, mégpedig az adatok pillanatnyi értékére.

Definíció: Legyen C egy program és Var(C) = {X1, X2, …, Xn} a C program által használt változók halmaza. A program egy állapota az (x1, x2, …, xn) érték n-es, ahol Xi = xi. Az értékek között megengedjük a # szimbólumot,

amely a definiálatlanságot jelenti.

Ciklust tartalmazó programok esetén az adatok függetlenségének eldöntéséhez a programjainkat blokkokra kell felbontanunk és elemeznünk, hogy az egyes blokkok lokális, vagy globális módon használják változóikat, azaz a szakaszban szereplő változók kívülről kapják-e értékeiket, vagy a szakaszon belüli független részeredményt tárolnak. Például egy ciklus indexváltozója tipikusan lokális jellegű változó. Ha egy blokkban csak lokális változók vannak, akkor a blokkot az előtte levő programrésztől függetlenül lehet végrehajtani.

Egy programblokkhoz megadhatjuk a be- és kimenő változóinak halmazát is. Szemléletesen azon változókat fogjuk kimenő változóknak nevezni, amelyek a blokk végrehajtása során új értéket kapnak, és azokat bemenő változónak, amelyek egy kimenő változó értékének előállításában részt vesznek.

Ezen belül azonban két szintet fogunk megkülönböztetni: amikor csak formális (szintaktikus), illetve amikor valódi (szemantikus) függőséget állapítunk meg a ki- és bemenő változók között.

Példák:

Példa (formális függés)

1.

T := X X := Y X := T

Kimenő változók: T, X Bemenő változók: X, Y, T

2.

X := Y Y := 2Y Y := Y - x

Kimenő változók: X, Y Bemenő változók: X, Y Példa (valódi függés)

3.

T := X X := Y X := T

Kimenő változók: T Bemenő változók: X 4.

X := Y Y := 2Y

Y := Y - X

Kimenő változók: X Bemenő változók: Y

Definíció (formális ki- és bemenet ciklusmentes programokra):

Legyen adott egy C utasításblokk. A C kimenő változóinak SOut(C) és bemenő változóinak SIn(C) halmaza a következő induktív definícióval adható meg:

1. SOut(skip) = ∅ SIn(skip) = ∅

2. SOut(X ← E) = {X}

SIn(X ← E) = Var(E)

3. SOut(C1;C2) = SOut(C1) ∪ SOut(C2) SIn(C1;C2) = SIn(C1) ∪ (SIn(C2) \ SOut(C1)) 4. SOut(if B then C1 else C2) = SOut(C1) ∪ SOut(C2) SIn(if B then C1 else C2) = Var(B) ∪ SIn(C1) ∪ SIn(C2)

Ahhoz, hogy egy programblokk valódi ki- és bemenő változóit megállapítsuk, a változók közötti szorosabb összefüggéseket kell felállítani. Ez helyenként igen komoly eszköztár igénybevételét is feltételezheti, és az is előfordulhat, hogy a feladat megoldhatatlan.

6.2.6.2.2.1. A változók függősége

A program változóinak függőségi viszonyát több szintű elemzéssel állapíthatjuk meg. A legegyszerűbb, amikor csak azt vizsgáljuk, hogy a szóban forgó változó kapott-e korábban értéket egy másik változó alapján. Ennek ismételt alkalmazásával azok az értékfüggőségek maradnak meg, amelyeknél a másik változó függősége is beépül a vizsgált változóba. Ezt szintaktikus függőségnek nevezzük. Az így megállapított függőség a szintaktikus ki- és bemenetek között írja le valamivel részletesebben a kapcsolatot.

Magasabb szintű függőségi viszonyt tudunk megállapítani, ha értelmezzük is a változók kapcsolatát, és nemcsak azt vizsgáljuk, hogy szerepelt-e korábban a másik változó valamilyen értékadásban, hanem azt is elemezzük, hogy milyenben. Ezt a fajta függőséget szemantikus függőségnek nevezzük.

A változók függőségének az előzőek alapján több típusát különböztethetjük meg:

0. X még szintaktikailag sem függ Y -tól;

1. X szintaktikailag függ Y-tól, a reprezentált adatok függősége esetenként programozástechnikai változtatással feloldható;

2. X strukturálisan függ Y-tól és nem mutatkozik lehetőség a függőség feloldására.

Bár a szintaktikai függőségtől erősebb kapcsolatot fejez ki a strukturális függőség, még ez utóbbi sem írja le mindig pontosan a változók közötti valódi függést.

6.2.6.2.2.2. Szintaktikus függés

A szintaktikus függés megállapítása a legegyszerűbb a függések között. Nem kapunk azonban általa pontos képet a valódi függőségről, mivel a tényleges viszonyt nem állapítja meg. Ezáltal bizonyos esetekben túl sok (nem valós) kapcsolatot állít fel.

Tekintsük pl. a következő programrészletet:

1. X ← X - Y 2. X ← X + Y

Világos, hogy ez a program semmilyen módon nem változtatja meg a változók értékét, vagyis nem változtat a függőségi viszonyon sem. Viszont, ha csak a tiszta szintaktikus függést nézzük, azt kapjuk, hogy X függ Y-tól.

Mégis van haszna azonban ennek a vizsgálatnak is, mivel első körben sok változót szűrhetünk ki vele a további elemzések köréből, ami által egyszerűsíthetünk a magasabb szintű függések megállapításánál.

Definíció: Var(E) -vel jelöljük az E kifejezésben szereplő változók halmazát. Az E összetettsége szerint a következő módon határozzuk meg:

1. Var(c) = ∅, ahol c egy konstans;

2. Var(X) = {X}, ahol X egy változó;

3.

ha f(.) egy függvény.

Definíció (ciklusmentes programok változóinak szintaktikai függősége):

Jelölje SDep(X,C) azon változók halmazát, amelyektől szintaktikailag függ az X kimeneti változó a C program végrehajtásakor. SDep(X, C)-t rekurzív módon a következőképpen definiáljuk:

1. SDep(X, skip) = {X};

2. SDep(X, (Y ← E)) = Var(E), ha Y = X és {X}, ha Y ≠ X;

3. Sdep(X, (C1; C2)) = ⋃Y∈SDep(X, C2)SDep(Y, C1);

4. SDep(X, (if B then C1 else C2)) = SDep(C1) ∪ SDep(C2).

Jóval bonyolultabb a változók tényleges függőségének megállapítása. Ehhez ugyanis nem csak azt kell meghatározni, hogy mely bemenő változóktól függ egy kimenő változó, hanem azt is, hogy milyen módon. A változók függősége alapján állapíthatunk meg azután kapcsolatot az adatok között. A független változók által reprezentált adatok tekinthetők függetlennek, a függő változók által reprezentált adatok viszont mélyebb elemzést igényelnek a tényleges függőség megállapításához.

Sokszor nem egyszerű (sőt, akár az is elképzelhető, hogy lehetetlen) megállapítani a változók függőségi viszonyát, vannak azonban olyan esetek, amikor ez megtehető.

Definíció (ciklusmentes programok változóinak tényleges függősége):

Jelölje RDep(X, C) azt a függvényt, amelyet a C program megvalósít az X kimeneti változóra nézve. RDep(X, C) -t rekurzív módon a következőképpen definiáljuk:

1. RDep(X, skip) = X;

2. RDep(X, (Y ← E)) = f(X1, …, Xn), ha Y = X, ahol E az f(X1, … Xn) függvényt valósítja meg, és X, ha Y ≠ X;

3. RDep(X, (C1; C2)) = f(g1(X1, …, Xn), …, gk(X1, …, Xn)), ahol

f(Y1, …, Yk) = RDep(X, C2) és gi(X1, …, Xn);

4. RDep(X, (if B then C1 else C2)) = f(X1, … Xn), ahol f(X1, …, Xn) = RDep(X, C1), ha B(X1, …, Xn) = I (illetve 1) és f(X1, …, Xn) = RDep(X, C2), ha B(X1, …, Xn) = H (illetve 0).

Megjegyzés: Amennyiben a kifejezések értékei valós számok, a 4. pontban levő definíció egyszerűen felírható a

f(X1, … Xn) = B(X1, …, Xn) ⋅ RDep(X, C1) + (1 – B(X1, …, Xn)) ⋅ RDep(X, C2).

Amennyiben a változók függőségét sikerült megállapítani, definiálhatjuk a valódi ki- és bemenő változókat.

Definíció (valódi ki- és bemenet ciklusmentes programokra):

Legyen adott egy C utasításblokk. A C valódi kimenő változóinak ROut(C) és valódi bemenő változóinak RIn(C) halmaza a következő definícióval adható meg:

ROut(C) = {X|RDep(X, C) ≠ X}.

RIn(C) = ⋃X∈Rout(C)Dom(RDep(X, C)).

A változófüggőség gyakorlati meghatározására több lehetőség is adódik. Egyik ilyen módszer a Dijkstra-féle leggyengébb előfeltétel kalkulus megfelelő módosítása, amit a későbbiekben röviden ismertetünk. Segítségével megpróbálhatjuk megállapítani, hogy a végeredmény meghatározásához mely számítások milyen sorrendű elvégzésére van szükség, és az ehhez használt változók milyen hatókörrel rendelkeznek.

Természetesen egy-egy konkrét algoritmus esetén sokkal hatékonyabb párhuzamosítási lehetőségek is adódhatnak. Egyik szép példája ennek a gyors Fourier-transzformáció.

A gyors Fourier-transzformáció során (eltekintve most a konkrét gyakorlati felhasználástól, illetve a művelet értelmezésétől) egy M ⋅ v szorzatot kell kiszámolni, ahol M egy n × n-es mátrix, míg v egy n-dimenziós vektor.

A dolog különlegessége a mátrix alakján múlik. M szerkezetileg a következő mátrixhoz hasonlítható:

A párhuzamosíthatóság szempontjából fontos, hogy rekurzív módon definiálható, az alábbi összefüggést használva:

Legyen n = 2s.

M0j = M00 megfelelő együttható lista

A rekurzió során – az összefüggés alapján is megfigyelhetően: Mj = Msj.

Az összefüggés alapján az eredeti n2 elemi szorzásművelet helyett elegendő n darabot végrehajtani, míg az additív jellegű műveletekből n2 helyett csak n ⋅ s = n ⋅ log n darabot. Az M rekurzív megadásának alapján ráadásul arra is rájöhetünk, hogy az összes elemi szorzásművelet egy időben, egymástól függetlenül végrehajtható, míg az additív műveletek – a rekurzív megadás lépéseit követve – szintenként haladva függetlenek, vagyis egy időben egyszerre n darab hajtható végre. Az algoritmus processzorigénye ennek megfelelően n, míg az időbonyolultsága log n.

A feladat jellegéből adódóan így jelentős redukció érhető el a végrehajtandó elemi műveletek, valamint a szükséges processzorok számában. Az időbonyolultságon javítani viszont még ezzel sem tudunk.

6.2.6.2.2.3. Banerjee-féle függőségi teszt

A változók függőségének megállapítását ciklusmentes programokra megvizsgáltuk. Jóval nehezebb a feladat, ha programunk ciklusokat is tartalmaz.

Pontosan erre a célra használható a Banerjee-féle függőségi teszt. Ez tulajdonképpen egy leegyszerűsített, és az alkalmazható esetekben igen hatékony adatfüggőségi teszt, melyet széles körben használnak programok ciklusainak automatikus párhuzamosítására. Működése azon alapul, hogy az indexelt változók (pl. tömbök) által okozott függőséget az indexek megfigyelésén keresztül próbáljuk detektálni. megállapítunk egy egyenletekkel kifejezhető összefüggést a változók között. A függőséget a kapott egyenletrendszer megoldásai alapján nyerjük. Mivel az egyenletekben szereplő változók által felvehető értékek egészek, ezért az egyenletrendszer megoldása viszonylag egyszerű, diofantikus egyenletekre vonatkozó módszerekkel meghatározható.

A szekvenciális programot egy olyan fordító segítségével alakíthatjuk át párhuzamos architektúrájú gépen hatékonyan futtatható programmá, amelyik átszerkeszti a kódunkat.

A fordító feladata a rejtett párhuzamosságok felfedezése és nyitottá tétele.

Az eljárás első lépéseként meghatározzuk a szekvenciális programként való végrehajtásnál kialakuló függőségeket.

A program párhuzamos változatának ugyanazon függőségi viszonyoknak kell eleget tennie, aminek következményeként ugyanazt a végeredményt számolja ki, mint az eredeti program.

Ha a szekvenciális programban egy B művelet függ az AA -tól, akkor A-t a párhuzamos programban is hamarabb kell végrehajtani.

Két művelet függőségének megállapításához tudnunk kell azt, hogy használják-e ugyanazt a memóriaterületet, és ha igen, milyen sorrendben.

6.2.6.2.2.4. A módosított Dijkstra-féle leggyengébb előfeltétel kalkulus

A Dijkstra-féle leggyengébb előfeltétel kalkulus a programhelyesség bizonyítás jól ismert és alaposan tanulmányozott módszere. A benne leírtak végrehajtására több automatizált rendszer létezik, érdemes tehát kiterjesztenünk a párhuzamosíthatóság vizsgálatára.

Definíció:

1. wp(skip, Q) = Q

2. wp(X ← E, Q) = Q(X ← E)

3. wp((C1; C2), Q) = wp(C1, wp(C2, Q))

4. wp((if B then C1 else C2), Q) = (B ∧ wp(C1, Q)) ∨ (¬ B ∧ wp(C2, Q))

5. wp((while B do C), Q) = ∃k (k ≥ 0)∧pk, ahol P0 & ¬ B ⋀ Q és Pk+1 = B⋀wp(C, Pk) (informálisan: ∃k (k ≥ 0)

Pk = P0 ∨ P1 ⋁ P2 ∨ …)

Ilyen módon blokkonként megállapítható, hogy a kimeneti változók mely bemenő változóktól függenek, ami alapján felírhatjuk a blokkok egymásra épülésének struktúráját.

Tekintsük például a következő egyszerű programrészletet:

Példa (programrészlet)

1. X ← 3 2. Y ← 2 ⋅ X

3. if Y < 4 then 4. X ← 2

Ebben az utasításblokkban kimenetként az X és Y változók kapnak értéket, bemenete viszont nincs – legalábbis ami részt vesz a kimenet kialakításában.

Megállapítható, hogy X és Y értéke egymástól függetlenül, egy időben számítható:

X = 3 és Y = 6 értékeket kapnak.

A kimenő változók: Xr, Yr

A 3. sor alapján a függőség: Dx = ((Y < 4)⋀(Xr = X))⋁((Y ≥ 4)⋀(Xr = 2))

Dy = (Yr = Y)

A 2. sor alapján a függőség: Dx = ((2 ⋅ X < 4)⋀(Xr = X))⋁((2 ⋅ X ≥ 4)⋀(Xr = 2)) Dy = (Yr = 2 ⋅ X)

Az 1. sor alapján a függőség: Dx = ((2 ⋅ 3 < 4)⋀(Xr = 3))⋁((2 ⋅ 3 < 4)⋀(Xr = 3))⋁((2 ⋅ 3 ≥ 4)⋀(Xr = 2))

Dy = (Yr = 2 ⋅ 3)

Látható, hogy amíg csak 1–4 típusú utasításokat tartalmaz egy program, addig a függőségi viszonyokat könnyű meghatározni. A probléma a ciklusok használatánál kezdődik.

Vizsgáljuk meg a következő három egyszerű ciklust:

I.)

Az első esetben világosan látszik, hogy a ciklusmag utasításai egy időben végrehajthatók, a T[X] ← 2 ⋅ T[X]

műveletek végrehajtási sorrendje lényegtelen, egyik sem használja a másik eredményét.

A második esetben a probléma már nem oldható fel, legalábbis nem ilyen egyszerűen. A műveletek végrehajtási sorrendje itt is tetszőleges lehet, viszont az S változó egyfajta gyűjtő változó, akkumulátor szerepe van.

Végül a harmadik példa során nem értelmezhető a műveletek sorrendjének felcserélése, hiszen egyértelműen az előző számítás eredményét kell felhasználni a következő iterációs lépésben. Itt párhuzamosítás már csak magasabb szintű matematikai megfontolások és elemzés után lehetséges, a kiszámolandó feladattól függő egyedi megoldással.

Természetesen elképzelhető olyan utasításszerkezet, ahol a változók függőségét alaposan elrejtettük, így az szinte kitalálhatatlan. Feltételezhetjük azonban, hogy a programkód elkészítése során nem az volt a cél, hogy minél nehezebben visszafejthető legyen, hanem egyfajta együttműködési szándék a párhuzamosíthatósággal kapcsolatban.

Megállapíthatjuk tehát, hogy egyes ciklusok esetén egyedi utasításszervezéssel érhetünk el magasabb szintű párhuzamosítást.

Az utasításblokkok párhuzamosításának következő szintjét képezi a III. példaprogram 5–7. sorából álló blokk.

Itt – ha egyszerűbb módon is – egy komplexebb összefüggés van a változók értékei között. A Dijkstra-típusú elemzés azonban azt mutatja, hogy a kimeneti értékek viszonylag tisztán függenek a bemeneti értékektől:

Látható, hogy itt is lehetséges a párhuzamosítás, viszont szükség van egyfajta szinkronizálásra, illetve az eredmények tárolása előtt megfelelő értékű késleltetésre.

6.2.6.2.2.5. Programszerkezet-gráf felépítése

Amennyiben a változók függőségét sikeresen meghatároztuk, rátérhetünk az utasítások függőségének meghatározására.

A változók direkt függőségét használva közvetlenül is felépíthetünk egy egyszerű függőségi gráfot.

Bontsuk fel a programot maximális lineáris szakaszokra. (Egy szakaszt akkor nevezünk lineárisnak, ha nem tartalmaz ciklust. Maximális akkor lesz, ha semelyik irányba nem bővíthető tovább úgy, hogy lineáris maradjon.)

Egy lineáris szakaszon belül az i. utasítást összekötjük a j. utasítással (irányított módon, i → j formában), ha j >

i, az i. utasításban valamelyik (mondjuk X) változó értéket kap, a j. utasításban X értéke felhasználásra kerül és az i + 1 … j – 1 utasítások során X nem kap új értéket. Így a szakaszon belüli szintaktikus függőséget teljes mértékben leírhatjuk. (A szemantikus függést ezzel még nem tudjuk megvizsgálni.)

Amennyiben szakaszonként felépítettük az utasítások függőségi viszonyát, megállapítjuk az egyes szakaszok ki- és bemeneti változóit.

Párhuzamosítás szempontjából látszólag ellentmondásos módon, egyszerűbben kezelhető, és hatékonyabb, kevesebb változót tartalmazó be- és kimenő változó halmazokat kapunk, ha a feltételes utasítás-végrehajtás két ágát külön-külön szakasznak tekintjük, és egymás utáni végrehajtással reprezentáljuk. Ehhez szükség van egy ideiglenes logikai változóra, amiben a feltétel értékét tároljuk. Tekintsük például a következő programrészletet:

C =

Ha az előbbi definíció alapján nézzük, a be- és kimenő változók halmazai:

RIn(C) = {X, Y, Z}

Ennek az átírásnak az előnye akkor jelentkezik, amikor az egyes utasítások függőségi gráfját konstruáljuk meg.

Ekkor ugyanis nem kell a két utasításágat együtt kezelni a kapcsolódás megállapításánál, hanem ugyanúgy dolgozhatunk vele, mintha egyszerű utasítások lennének.

Meg kell azonban jegyeznünk, hogy mivel még mindig csak szintaktikai elemzést végzünk, a 1. if X < 10 then

A szintaktikus be- és kimenő változók halmaza a következő metódussal határozható meg:

Legyenek az utasítások az adott szakaszon belül 1-től n-ig számozva.

1. SIn ← ∅

In document Párhuzamos algoritmusmodellek (Pldal 149-161)