Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
A PLINQ sok esetben jelentős teljesítménybeli javulást tud nyújtani az objektumlekérdezések szekvenciális LINQ-jában. A lekérdezés végrehajtásának párhuzamosítása azonban összetettséghez vezet, amely olyan problémákhoz vezethet, amelyek szekvenciális kódban nem olyan gyakoriak, vagy egyáltalán nem jelentkeznek. Ez a témakör felsorol néhány olyan eljárást, amely elkerülhető a PLINQ-lekérdezések írásakor.
Ne feltételezzük, hogy a párhuzamosság mindig gyorsabb
A párhuzamosítás néha azt eredményezi, hogy a PLINQ-lekérdezés lassabban fut, mint a LINQ to Objects megfelelője. Az alapszabály az, hogy a kevés forráselemet tartalmazó és felhasználók által gyorsan végzett tevékenységek lekérdezései nem valószínű, hogy sokat gyorsulnak. Mivel azonban a teljesítményben számos tényező játszik szerepet, javasoljuk, hogy mérje meg a tényleges eredményeket, mielőtt eldöntené, hogy használja-e a PLINQ-t. További információ: Understanding Speedup in PLINQ.
Ne írjon megosztott memóriahelyekre
A szekvenciális kódban nem ritka, hogy statikus változókból vagy osztálymezőkből olvasnak vagy írnak. Azonban, ha több szál is egyszerre hozzáfér az ilyen változókhoz, nagy a lehetősége a versenyfeltételeknek. Annak ellenére, hogy zárolásokkal szinkronizálhatja a változóhoz való hozzáférést, a szinkronizálás költsége ronthatja a teljesítményt. Ezért azt javasoljuk, hogy a lehető legnagyobb mértékben kerülje vagy legalábbis korlátozza a megosztott állapothoz való hozzáférést a PLINQ-lekérdezésekben.
Kerülje a túl párhuzamosítást
A metódus használatával AsParallel a forrásgyűjtemény particionálásának és a feldolgozószálak szinkronizálásának többletköltségei merülnek fel. A párhuzamosítás előnyeit tovább korlátozza a számítógépen lévő processzorok száma. Nincs gyorsulás, ha több számítás-intenzív szálat futtat egyetlen processzoron. Ezért óvatosnak kell lennie, hogy ne túlzottan párhuzamosítsa a lekérdezést.
A túl párhuzamosítás leggyakoribb forgatókönyve a beágyazott lekérdezésekben fordul elő, ahogyan az az alábbi kódrészletben is látható.
var q = from cust in customers.AsParallel()
from order in cust.Orders.AsParallel()
where order.OrderDate > date
select new { cust, order };
Dim q = From cust In customers.AsParallel()
From order In cust.Orders.AsParallel()
Where order.OrderDate > aDate
Select New With {cust, order}
Ebben az esetben a legjobb, ha csak a külső adatforrást (ügyfeleket) párhuzamosítja, kivéve, ha az alábbi feltételek közül egy vagy több érvényes:
A belső adatforrás (cust.Orders) ismerten nagyon hosszú.
Minden egyes megrendeléshez költséges számítást végez. (A példában látható művelet nem költséges.)
A célrendszerről ismert, hogy elegendő processzorral rendelkezik a lekérdezés
cust.Orderspárhuzamosításával előállított szálak számának kezeléséhez.
Az optimális lekérdezési alakzat meghatározásának legjobb módja minden esetben a tesztelés és a mérés. További információ : A PLINQ-lekérdezés teljesítményének mérése.
A nem szálbiztos metódusokra irányuló hívások elkerülése
PLINQ-lekérdezésből nem szálbiztos példánymetódusokba való írás adatsérüléshez vezethet, amely észlelhető vagy sem a programban. Ez kivételekhez is vezethet. Az alábbi példában több szál is megpróbálja egyszerre meghívni a FileStream.Write metódust, amelyet az osztály nem támogat.
Dim fs As FileStream = File.OpenWrite(…)
a.AsParallel().Where(...).OrderBy(...).Select(...).ForAll(Sub(x) fs.Write(x))
FileStream fs = File.OpenWrite(...);
a.AsParallel().Where(...).OrderBy(...).Select(...).ForAll(x => fs.Write(x));
A szálbiztos metódusokra irányuló hívások korlátozása
A .NET legtöbb statikus metódusa szálbiztos, és egyszerre több szálból is meghívható. Az érintett szinkronizálás azonban még ezekben az esetekben is jelentős lassulást eredményezhet a lekérdezésben.
Megjegyzés:
Ezt saját maga is tesztelheti, ha beszúr néhány hívást WriteLine a lekérdezéseibe. Bár ezt a módszert bemutató célokra használják a dokumentációs példákban, ne használja PLINQ-lekérdezésekben.
A szükségtelen rendelési műveletek elkerülése
Amikor a PLINQ párhuzamosan hajt végre egy lekérdezést, a forrásütemezést partíciókra osztja, amelyek egyszerre több szálon is működtethetők. Alapértelmezés szerint a partíciók feldolgozásának és az eredmények kézbesítésének sorrendje nem kiszámítható (kivéve az operátorokat, például OrderBy). Utasíthatja a PLINQ-t, hogy őrizze meg a forrásütemezések sorrendjét, de ez negatív hatással van a teljesítményre. Az ajánlott eljárás az, hogy a lekérdezéseket, amikor csak lehetséges, úgy strukturáljuk, hogy azok ne támaszkodjanak a sorrend megőrzésére. További információ: Order Preservation in PLINQ.
A ForAll és a ForEach előnyben részesítése, ha lehetséges
Bár a PLINQ több szálon hajt végre lekérdezést, ha egy foreach ciklusban (Visual Basicben For Each) használja fel az eredményeket, akkor a lekérdezési eredményeket vissza kell egyesíteni egy szálba, és az enumerátornak sorban kell elérni őket. Bizonyos esetekben ez elkerülhetetlen; azonban, amikor csak lehetséges, használja a ForAll módszert, hogy minden szál saját eredményeket állítson elő, például egy szálbiztos gyűjteménybe, mint pl. System.Collections.Concurrent.ConcurrentBag<T>.
Ugyanez a probléma a következőre Parallel.ForEachvonatkozik: . Más szóval, source.AsParallel().Where().ForAll(...) határozottan előnyben kell részesíteni .Parallel.ForEach(source.AsParallel().Where(), ...)
Vegye figyelembe a szál affinitási problémáit
Egyes technológiák, például az egyszálas apartman (STA) összetevők COM-interoperabilitása, a Windows Forms és a Windows megjelenítési alaprendszer (WPF) szál affinitási korlátozásokat vezetnek be, amelyek megkövetelik, hogy a kód egy adott szálon fusson. A Windows Formsban és a WPF-ben például egy vezérlő csak azon a szálon érhető el, amelyen létrejött. Ha PLINQ-lekérdezésben próbál hozzáférni egy Windows Forms-vezérlő megosztott állapotához, kivétel keletkezik, ha a hibakeresőben fut. (Ez a beállítás kikapcsolható.) Ha azonban a lekérdezés a felhasználói felületi szálon végrehajtódik, akkor a vezérlőt elérheti a lekérdezés eredményeit enumeráló foreach ciklusban, mert a kód csak egy szálon fut.
Ne feltételezze, hogy a ForEach, a For és a ForAll iterációi mindig párhuzamosan futnak
Fontos szem előtt tartani, hogy a Parallel.For, Parallel.ForEach, vagy ForAll hurok egyes iterációi végrehajtásra kerülhetnek, de nem szükségszerűen párhuzamosan. Ezért kerülje az olyan kód írását, amely a helyes működéshez az iterációk párhuzamos végrehajtásától vagy az iterációk meghatározott sorrendben történő végrehajtásától függ.
Ez a kód például valószínűleg holtpontra kerül:
Dim mre = New ManualResetEventSlim()
Enumerable.Range(0, Environment.ProcessorCount * 100).AsParallel().ForAll(Sub(j)
If j = Environment.ProcessorCount Then
Console.WriteLine("Set on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j)
mre.Set()
Else
Console.WriteLine("Waiting on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j)
mre.Wait()
End If
End Sub) ' deadlocks
ManualResetEventSlim mre = new ManualResetEventSlim();
Enumerable.Range(0, Environment.ProcessorCount * 100).AsParallel().ForAll((j) =>
{
if (j == Environment.ProcessorCount)
{
Console.WriteLine("Set on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j);
mre.Set();
}
else
{
Console.WriteLine("Waiting on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j);
mre.Wait();
}
}); //deadlocks
Ebben a példában az egyik iteráció beállít egy eseményt, és az összes többi iteráció várakozik az eseményen. A várakozási iterációk egyike sem fejeződhet be, amíg az eseménybeállítási iteráció be nem fejeződik. Előfordulhat azonban, hogy a várakozási iterációk blokkolják a párhuzamos ciklus végrehajtásához használt összes szálat, mielőtt az eseménybeállítási iterációnak lehetősége lett volna végrehajtani. Ez holtpontot eredményez – az eseménybeállítási iteráció soha nem lesz végrehajtva, és a várakozó iterációk soha nem fognak felébredni.
Különösen fontos, hogy a párhuzamos ciklus egy iterációjának sose kelljen várnia egy másik iterációra a haladás érdekében. Ha a párhuzamos hurok úgy dönt, hogy egymás után ütemezi az iterációkat, de ellenkező sorrendben, holtpont jön létre.