Parallel.For Break ja Stop

Teises artiklis vaatasime, kuidas kasutada Parallel.For() meetodit. Kuidas silmusest välja pääseda? Jadaprogrammis teeksime nii:

for (int i = 0, i < 20, i++)
{
Console.WriteLine(i);
if (i == 10)
break;
};

Rööplemises võib selle jaoks võib võtta objekti tüübiga ParallelLoopState, mis pakub meetodeid Break() ja Stop().

Break() lõpetab kõik lõimed, kuid garanteerib, et lõpetamise ajaks on iga lõim jõudnud sama väärtuseni, mis oli lõimel, mis kutsus välja katkestuse.

Parallel.For(0, 20, (i, state) =>
{
Console.WriteLine(i);
if (i == 10)
state.Break();
}

Minu katsetuse puhul tähendas see, et väljastati arvud 0, 2, 3, 4, 6, 7, 9, 5, 1, 15, 16, 17, 18, 19, 10, 12, 14, 8. Nagu näed, 11 ja 13 on puudu, sest need lõimed olid juba surnud, aga 0 kuni 10 on kindlalt olemas.

Vastupidiselt sellele katkestab Stop() silmuse töö, sõltumata sellest, millisele tasemele teised lõimed on jõudnud. Seega, kui Stop() juhtub lõimes, mis on teistest parasjagu ees, võivad mõned väärtused vahemikust 0 kuni 10 isegi puudu jääda.

Break() ega Stop() ei garanteeri, et ei käivitada rohkem tsükleid kui vaja, ja see on täiesti loomulik, sest kõik lõimed hakkavad kohe täisvõimsusel hagu andma.

Eeltoodud näidet võiks kiirema pidamasaamise huvides muuta siis nii:

if (i >= 10)
state.Stop();

Kui jadaprogrammis on garanteeritud, et i ==10 ja seejärel i ==11, siis rööpes võib vabalt juhtuda, et esiteks i > 10 ja alles seejärel i==10.

Advertisements
Rubriigid:Alustus Sildid:, , ,

AsParallel()

IEnumerable<T> meetod AsParallel() tagastab ParallelQuery<T>, millele saab LINQi teha nagu tavaliselt. See on eriti mõnus, sest kasutada võib sisseharjunud lähenemist, samas kui kõik päringud tehakse rööpselt.

Katsetame seda asja pisut. Siin on meie eelmisest korrast tuttav rakendus. Loome massiivi ja teeme talle sama päringut nii jadas kui rööbis.

class Program

{

static void Main(string[] args)

{

double[] values = new double[0xFFFFFF];

Parallel.For(0, values.Length, i => values[i] = i);

DateTime start;

start = DateTime.Now;

values.Count();

Console.WriteLine((DateTime.Now – start).TotalMilliseconds.ToString(“0”));

1

start = DateTime.Now;

values.AsParallel().Count();

Console.WriteLine((DateTime.Now – start).TotalMilliseconds.ToString(“0”));

8

Console.ReadLine();

}

}

Mida me näeme? Seda, et rööpselt võtab töö 8 korda kauem, mis pole ka ime, sest sellist operatsiooni nagu Count() on mõttetu rööpselt teha.

Proovime nüüd midagi natuke keerukamat, võtame näiteks ruutjuure:

values.Select(s => Math.Sqrt(s)).Count();

550

values.AsParallel().Select(s => Math.Sqrt(s)).Count();

133

Ahaa, rööplemine on 4 korda kiirem. (Count() oli vajalik selleks, et päringut reaalselt käivitataks, nimelt on IEnumerable nii kaval, et ei hakka tõmblema, enne kui kirjeid tegelikult vajatakse.)

 

Nagu näete, on AsParallel() mõnus lisa, mis võimaldab juba olemasolevatele ja sissseharjunud päringutele rööplemise lisada, muutmata päringute koodi rohkem kui ühe sõna võrra. Loomulikult toimib see ka sellisel viisil:

 

from s in values.AsParallel() select Math.Sqrt(s)

 

Ja tänase katsetuse õppetund oli see, et päris igale poole ei tasu rööplemist lisada, aga isegi väiksemate tööülesannete puhul võib rööplemine märkimisväärselt aega kokku hoida.

Parallel.For()

Rööplemise katsetamiseks loo uus käsurearakendus. Sul läheb vaja Visual Studio 2010 ja .NET Framework 4. Võta menüüst File/New/Project ja sealt Visual C#/Windows/Console Application. Pane talle nimeks näiteks Rööp.

Paralleelprogrammeerimise vahendite namespace on System.Threading.Tasks.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Rööp
{
class Program
{
static void Main(string[] args)
{
Parallel.For(0, 100, i => Console.WriteLine(i));
Console.ReadLine();
}
}
}

See on sinu esimene rööprakendus. Sama asi jadas oleks

for (int i = 0; i < 100; i++) Console.WriteLine(i);

Milles on siis erinevus? Rööplemise korral kaasab .NET ülesande teostamisse kõik protsessorid. Proovi mõlemaid variante käivitada ja sa näed, et rööbeldes algab arvude loetelu küll ühest, kuid järjestus on suvaline (kuigi üldjoontes kasvav).

Samuti võid täheldada, et reeglite vastu pole eksitud (iga i väärtus esineb ainult ühe korra).

Ja veel võid ilmselt täheldada, et algus läheb üsna järjest 1, 2, 3, 4… kuni tööle hakkavad teised lõimed.

Kui nii rööplemine kui ka jada annavad sul täpselt sama tulemuse, on sul mingi veeuputusaegne arvuti, millel on võimalik ainult üks lõim Smile.

See lihtne katse tegi selgeks rööplemise kasu

Kujutle, et tegemist on tõeliselt aeganõudva ülesandega. Selle asemel et oodata, kuni üks prosetuum 100% peal seda krõmpsutab, võid kõik 8 tuuma tööle panna, millele peatselt järgneb ventilaatori pöörete tõus.

Kogu asja ilu on selles, et rööplemine saab läbi täpselt sel hetkel kui kõik lõimed on lõpetanud (ja nad lõpetavadki enam-vähem üheaegselt). Selle teostamine käsitsi tähendaks parajat portsu koodi, aga nüüd kulub selleks sama vähe vaeva kui jadakoodi puhul.

Vinge, kas pole? Stay tuned for more…

Rubriigid:Alustus Sildid:

Rööp vs jada vs lõimed ehk alustus

Tere tulemast minu uude blogisse, mis on pühendatud C# paralleelprogrammeerimisele ehk rööplemisele. See ajaveeb on mõeldud inimesele, kes on kodus C# kasutamises, kuid ei tea veel, mis see rööplemine on.

Selles ajaveebis kutsun ma teid koos minuga avastama fantastilist maailma. Tulge ja õppige iga blogikirjega natuke. Aga selles esimeses artiklis vaatame siis, mis selle kõige mõte on.

Niisiis, milleks mulle rööplemine?

Tavaline programmikood on jadakood, see tähendab, et järgmisele koodireale ei pääse protsessor enne kui eelmine on lõpetatud.

Taustalõimed tädi Maalil abis

Kuna juba ammusest ajast on leiutatud lõimlevad prosed, on programmeerijad õppinud panema ressursimahukaid ülesandeid taustalõimedesse.

Seda võiks võrrelda sellega, kui tädi Maalile tulevad sõbrannad külla. Muidugi hakatakse kohe süüa tegema ja teised memmed küsivad Maali käest, mida nad saaksid teha. Maali jagab kogenult käsklusi: “Väga hea. Laine, võta sina siis kartulid, koori ära ja keeda ära. Ja sina, Hermiine, tee kapsas ära. Mina tegelen lihaga.” Maali on pealõim ja Laine ning Hermiine on kõrvallõimed. Iga lõim jookseb omasoodu ja see, millal nad valmis saavad, on teistest lõimedest sõltumatu (eeldades, et neil on piisavalt ruumi ja pliidipinda).

Maali paneb memmed rööplema

Rööplemine on midagi lõimlemise ja jada vahepealset. Rööplemise korral tegelevad kõik saadaolevad lõimed ühe või mitme ette antud tööga ja rööplemine saab läbi kui kõik ülesanded on lõpetatud. Rööplemisülesanded võib ühendada jadasse tavaliste jadaülesannete vahele. Vaata nüüd meie memmesid.

Maali ütleb: “Esiteks paneme liha ahju, siis võtame kapsa ja kõige lõpus teeme kartuli.” Maali jagab memmedele välja lõikelauad ja noad, ja nad asuvad kolmekesi lihaga tegelema. Pärast võetakse ette kapsas ja seejärel koorivad memmed kibekähku kartulid ära. Kartulikoorimise ülesande lõpetavad kõik memmed ühekorraga, hetkel kui kõik kartulid on kooritud. Ja on selge, et see läheb kolm korda kiiremini. Memmed võivad koorida kartuleid erineva kiirusega ja iga memme kooritud kartulite hulk võib olla erinev, aga see pole oluline. Tähtis on, et igaüks annab panuse.

Rööplemise võlu

See ongi rööplemise võlu: pole oluline, millisele lõimele milline detail satub ja mis järjekorras neid käsitletakse, tähtis on, et saavutatakse ajavõit. Ülejäänu eest hoolitseb .NET.

Kui taustalõimede loomine ja selle jälgimine, millal need lõpetavad, on tavatehnikatega päris koodiküllane ettevõtmine, siis rööplemisega pole tarvis muud kui lihtsalt .NETile ütelda: “Teeme nüüd seda tööd mitmekesi.” Pärast ülesande täitmist võib jätkata rahumeeli jadas nagu varemgi.

Loe minu ajaveebi järgmisi artikleid, kus me hakkame rööplemist reaalselt katsetama.