Paralleelarvutus on pühat eesmärki täitev asi – hoiame oma niigi voolu õgivad protsessorid ja arvutustuumad maksimaalselt midagi asjalikku tegemas. Teen selle kohta ühe näite, mis aitab paremini mõista, kuidas .NET raamistikus tänaste vahenditega paralleelarvutus käib. Ühtlasi mõõdame oma tulemusi ja katsed viime läbi reaalelulise koodi peal.
.NET raamistikus on kaks vahendit paralleelarvutuse jaoks:
TPL on võimas raamistik, mis muudab paralleelarvutuse meie jaoks palju lihtsamaks ja koodi paremini mõistetavaks. PLINQ toob LINQ peale võimaluse töödelda andmeid paralleelselt. Seega võime öelda, et TPL on ülesannete põhine ja PLINQ on andmepõhine paralleelarvutus.
RSS-agregaator ja selle mõõtmine
Meie näite südamikuks on RSS-agregaator, mis loeb andmebaasist sisse blogide loendi, tuvastab nende RSS-i aadressid ning loeb sisse viimased postitused. Positused, mida veel andmebaasis pole, lisatakse sinna ära.
{
private readonly INewsService _newsService;
private const int FeedItemContentMaxLength = 255;
public FeedClient()
{
ObjectFactory.Initialize(container =>
{
container.PullConfigurationFromAppConfig = true;
});
_newsService = ObjectFactory.GetInstance<INewsService>();
}
public void Execute()
{
var blogs = _newsService.ListPublishedBlogs();
for (var index = 0; index <blogs.Count; index++)
{
ImportFeed(blogs[index]);
}
}
private void ImportFeed(BlogDto blog)
{
if(blog == null)
return;
if (string.IsNullOrEmpty(blog.RssUrl))
return;
var uri = new Uri(blog.RssUrl);
SyndicationContentFormat feedFormat;
feedFormat = SyndicationDiscoveryUtility.SyndicationContentFormatGet(uri);
if (feedFormat == SyndicationContentFormat.Rss)
ImportRssFeed(blog);
if (feedFormat == SyndicationContentFormat.Atom)
ImportAtomFeed(blog);
}
private void ImportRssFeed(BlogDto blog)
{
var uri = new Uri(blog.RssUrl);
var feed = RssFeed.Create(uri);
foreach (var item in feed.Channel.Items)
{
SaveRssFeedItem(item, blog.Id, blog.CreatedById);
}
}
private void ImportAtomFeed(BlogDto blog)
{
var uri = new Uri(blog.RssUrl);
var feed = AtomFeed.Create(uri);
foreach (var item in feed.Entries)
{
SaveAtomFeedEntry(item, blog.Id, blog.CreatedById);
}
}
}
Mõõtmiseks kasutame sellist vahendit nagu Stopwatch klass. Programmi alguses lööme kella käima ja kui programm lõpetab, paneme kella seisma.
Seriaalne agregeerimine
Esiteks mõõdame ära tulemused, mida annab eeltoodud kood. Seal tehakse kõik sammud järjest ja korraga midagi käima ei lasta. See tähendab seda, et kõik RSS-id laetakse alla üksteise järel ja nende sisu salvestatakse andmebaasi samuti üks postitus korraga.
Mõõtmisel saame tulemuseks 25.46 sekundit.
Task parallelism
Meie esimene analüüsiülesanne on leida üles punkt, kus me saaksime eraldada muust koodist sõltumatud osad. Need osad koodist võime me käima lasta paralleelselt (me võime ka sõltuvustega osi paralleelselt käivitada, kuid see on oluliselt keerukam ja riskantsem tegevus). Meie juhul on selleks kohaks, kus võiksime kaaluda paralleeltöötlust, näiteks RSS-ide lugemine ja andmebaasi sisestamine.
{
private readonly INewsService _newsService;
private const int FeedItemContentMaxLength = 255;
public FeedClient()
{
ObjectFactory.Initialize(container =>
{
container.PullConfigurationFromAppConfig = true;
});
_newsService = ObjectFactory.GetInstance<INewsService>();
}
public void Execute()
{
var blogs = _newsService.ListPublishedBlogs();
var tasks = new Task[blogs.Count];
for (var index = 0; index <blogs.Count; index++)
{
tasks[index] = new Task(ImportFeed, blogs[index]);
tasks[index].Start();
}
Task.WaitAll(tasks);
}
private void ImportFeed(object blogObject)
{
if(blog == null)
return;
var blog = (BlogDto)blogObject;
if (string.IsNullOrEmpty(blog.RssUrl))
return;
var uri = new Uri(blog.RssUrl);
SyndicationContentFormat feedFormat;
feedFormat = SyndicationDiscoveryUtility.SyndicationContentFormatGet(uri);
if (feedFormat == SyndicationContentFormat.Rss)
ImportRssFeed(blog);
if (feedFormat == SyndicationContentFormat.Atom)
ImportAtomFeed(blog);
}
private void ImportRssFeed(BlogDto blog)
{
var uri = new Uri(blog.RssUrl);
var feed = RssFeed.Create(uri);
foreach (var item in feed.Channel.Items)
{
SaveRssFeedItem(item, blog.Id, blog.CreatedById);
}
}
private void ImportAtomFeed(BlogDto blog)
{
var uri = new Uri(blog.RssUrl);
var feed = AtomFeed.Create(uri);
foreach (var item in feed.Entries)
{
SaveAtomFeedEntry(item, blog.Id, blog.CreatedById);
}
}
}
Muudatused, mis me tegime, on lihtsad – me defineerisime RSS-ide importimiseks eraldi ülesanded ja lasime need paralleelselt käima. Seejärel jäime ootama, millal kõik ülesanded on tehtud.
Kui aega mõõdame, siis saame tulemuseks 17.57 sekundit.
Data parallelism
Järgmiseks vaatame, kas meil on veel kohti, kus võime korraga mitut asja teha. Nagu tellitult vaatab meile vastu RSS-ist loetud postituste loend. Kuna postituste sisestamine andmebaasi on tehtav isolatsioonis (me ei kasuta teiste postituste andmeid), siis proovime, mis juhtub, kui laseme postitused andmebaasi sisestada paralleelselt.
{
private readonly INewsService _newsService;
private const int FeedItemContentMaxLength = 255;
public FeedClient()
{
ObjectFactory.Initialize(container =>
{
container.PullConfigurationFromAppConfig = true;
});
_newsService = ObjectFactory.GetInstance<INewsService>();
}
public void Execute()
{
var blogs = _newsService.ListPublishedBlogs();
var tasks = new Task[blogs.Count];
for (var index = 0; index <blogs.Count; index++)
{
tasks[index] = new Task(ImportFeed, blogs[index]);
tasks[index].Start();
}
Task.WaitAll(tasks);
}
private void ImportFeed(object blogObject)
{
if(blog == null)
return;
var blog = (BlogDto)blogObject;
if (string.IsNullOrEmpty(blog.RssUrl))
return;
var uri = new Uri(blog.RssUrl);
SyndicationContentFormat feedFormat;
feedFormat = SyndicationDiscoveryUtility.SyndicationContentFormatGet(uri);
if (feedFormat == SyndicationContentFormat.Rss)
ImportRssFeed(blog);
if (feedFormat == SyndicationContentFormat.Atom)
ImportAtomFeed(blog);
}
private void ImportRssFeed(BlogDto blog)
{
var uri = new Uri(blog.RssUrl);
var feed = RssFeed.Create(uri);
feed.Channel.Items.AsParallel().ForAll(a =>
{
SaveRssFeedItem(a, blog.Id, blog.CreatedById);
});
}
private void ImportAtomFeed(BlogDto blog)
{
var uri = new Uri(blog.RssUrl);
var feed = AtomFeed.Create(uri);
feed.Entries.AsParallel().ForAll(a =>
{
SaveAtomFeedEntry(a, blog.Id, blog.CreatedById);
});
}
}
Siin kasutasime TPL asemel PLINQ, sest selle abil on meil mugavam lasta andmete peal operatsioone tööle.
Tulemuseks saame 11.22 sekundit.
Tulemused
Kui tulemused graafikule toome, samme sellise graafiku.
![]()
Näeme, et RSS-ide allalaadimine paralleelselt annab meile umbes poolteist korda võitu. Kui andmete uuendamise ka paralleelseks muudame saame võrreldes algsega 2.3 kordse võidu.
Kokkuvõte
Me alustasime tavalise lihtsa koodiga, mis tegi kõike samme järjest. Esimese muudatusena viisime me paralleelsele kujule andmete allalaadimise ja uuendamise. Andmete uuendamine jäi seejuures ikkagi järjestikuseks. Seejärel viisime ka andmete uuendamise paralleelsele kujule ja saime veel võitu. Meil läks seekord hästi, sest erinevate thread-ide peal jooksev kood ei vajanud midagi, mis sealt välja jääks.
Kui oma lähtekoodi hoolikalt analüüsida, siis võib tihti leida kohti, kus paralleeltöötlus võib rakenduse jõudlust tõsta. Oluline on need kohad tuvastada ja mõõta ära, kas paralleelsus annab midagi juurde või ei.
Loe lisaks
- Võrdleme LINQ ja PLINQ jõudlust
- Joomla! 1.0.13 ja Joom!Fish probleem
- Visual Studio Task List
- Kuidas vigu mitte käsitleda - vigade mahasurumine
- Visual Studio 2010 ja .Net Framework 4.0 betad





Microsoft on teinud diili sellise firmaga nagu
Et reede kiiremini õhtusse saada, siis annan väikse vihje Visual Studio projektide kompileerimise kiirendamiseks. Sedapuhku pole tegu



Peale
