Sdílet prostřednictvím


Vlastní Partitioners PLINQ a TPL

Parallelize operace na zdroji dat jeden základní postup je oddíl zdroje do více oddílů, které může současně přistupovat více podprocesů. PLINQ a úkol paralelní knihovnu (TPL) poskytují výchozí partitioners, které pracují transparentně při psaní dotazu paralelní nebo ForEach smyčky. Pokročilejší scénáře můžete připojit vlastní partitioner.

Typy oddílů

Oddíl zdroje dat mnoha způsoby. Při nejúčinnější přístupy spolupracovat více podprocesů procesu původní zdroj posloupnost namísto fyzicky zdroj rozdělení do více dílčích sekvencí. Pro pole a další indexovaných zdrojů jako IList kolekcí, kde délka je známa předem, rozdělení oblasti je nejjednodušší druh rozdělení. Každý podproces obdrží jedinečný začínající a končící indexy, takže jeho rozsah zdroje může zpracovat bez přepsání nebo přepsání jiným podprocesem. Pouze potřebného v rozsahu rozdělení je počáteční práce vytváření rozsahů; Po, není nutná žádná další synchronizace. Proto může poskytnout dobrý výkon jako zatížení rovnoměrně rozdělit. Rozdělení oblasti Nevýhodou je, že pokud předčasné ukončení jednoho podprocesu nemůže pomoci jiných podprocesů dokončit svou práci.

Propojené seznamy nebo jiné kolekce souborů, jejichž délka je známa, lze použít bloku rozdělení. V rozdělení bloku každý podproces nebo úkol opakovat paralelní nebo dotazu spotřebovává některé počet prvků zdroje v jednom bloku, zpracuje je a zpět pochází načíst další prvky. Partitioner zajišťuje, že všechny prvky jsou distribuovány a že neexistují žádné duplicity. Blok může být libovolné velikosti. Například partitioner, který je znázorněn v Postupy: Jak zavést dynamické oddíly vytvoří bloky obsahují pouze jeden prvek. Tak dlouho, dokud nejsou příliš velké bloky dat, tento druh rozdělení je dědičně Vyrovnávání zatížení, protože přiřazení prvků podprocesů není pre-determined. Však partitioner utrpět režie synchronizace vždy potřebuje zjistit bloku jiného podprocesu. Částka synchronizace vzniklých v těchto případech je nepřímo úměrná velikosti bloky dat.

Obecně rozdělení rozsah je pouze rychlejší při doba provádění delegáta je malá, střední zdroj má velký počet prvků a zhruba odpovídá celkové práce jednotlivých oddílů. Rozdělení bloku je proto ve většině případů obecně rychlejší. Zdrojů s malý počet prvků nebo delší dobu spuštění delegát výkon bloku a rozsah rozdělení je o výši.

TPL partitioners také podporují dynamické počet oddílů. To znamená, že mohou vytvořit oddíly na průběžně, například, když ForEach smyčky založí novou úlohu. Tato funkce umožňuje partitioner měřítko spolu s smyčku sám. Dynamické partitioners jsou také dědičně Vyrovnávání zatížení. Při vytváření vlastní partitioner musí podporovat dynamické rozdělení se z spotřebního ForEach smyčky.

Konfigurace Partitioners PLINQ Vyrovnávání zatížení

Některé přetížení Partitioner.Create metody umožňují vytvořit partitioner pro pole nebo IList zdroje a určit, zda má pokusit o vyvážení pracovního vytížení mezi podprocesy. Vyvážit zatížení nastavena partitioner, používá rozdělení bloku a prvky odevzdány a každý oddíl malé bloky jako jsou požadovány. Tento přístup pomůže zajistit, aby všechny oddíly mají prvky procesu až do celé smyčky nebo dotaz dokončen. Další přetížení lze poskytovat služby Vyrovnávání zatížení všech oddílů IEnumerable zdroje.

Vyrovnávání zatížení obecně vyžaduje oddíly požádat prvky relativně často partitioner. Naopak partitioner, který statické rozdělení můžete přiřadit prvky každé partitioner najednou pomocí rozsahu nebo rozdělení bloku. To vyžaduje menší nároky než zatížení, ale může trvat delší dobu spuštění, pokud jeden podproces končí podstatně více práce, než ostatní. Ve výchozím nastavení při předávání IList nebo pole, PLINQ vždy používá rozdělení oblasti bez zátěže. Vyrovnávání zatížení pro PLINQ povolit, použijte Partitioner.Create Metoda, jak ukazuje následující příklad.

        ' Static number of partitions requires indexable source.
        Dim nums = Enumerable.Range(0, 100000000).ToArray()

        ' Create a load-balancing partitioner. Or specify false For  Shared partitioning.
        Dim customPartitioner = Partitioner.Create(nums, True)

        ' The partitioner is the query's data source.
        Dim q = From x In customPartitioner.AsParallel()
                Select x * Math.PI

        q.ForAll(Sub(x) ProcessData(x))

            // Static partitioning requires indexable source. Load balancing
            // can use any IEnumerable.
            var nums = Enumerable.Range(0, 100000000).ToArray();

            // Create a load-balancing partitioner. Or specify false for static partitioning.
            Partitioner<int> customPartitioner = Partitioner.Create(nums, true);

            // The partitioner is the query's data source.
            var q = from x in customPartitioner.AsParallel()
                    select x * Math.PI;

            q.ForAll((x) =>
            {
                ProcessData(x);
            });

Nejlepší způsob, jak určit, zda použít vyrovnávání zatížení v daný scénář je experimentovat a změřit, jak dlouho trvá operace dokončena za reprezentativní zatížení a konfigurací počítače. Například statické rozdělení může poskytnout významné Vančurovou vícejádrové počítače, který má pouze několik jádra, ale může dojít v počítačích s relativně mnoho jader zpomalování.

Následující tabulka obsahuje seznam dostupných přetížení Create metody. Tyto partitioners nejsou omezeny pouze pomocí PLINQ nebo ForEach. Můžete také používají s jakékoli vlastní paralelní konstrukce.

Přetížení

Použití vyrovnávání zatížení

Create<TSource>(IEnumerable<TSource>)

Vždy

Create<TSource>(TSource[], Boolean)

Pokud logická argument zadán jako true

Create<TSource>(IList<TSource>, Boolean)

Pokud logická argument zadán jako true

Create(Int32, Int32)

Nikdy

Create(Int32, Int32, Int32)

Nikdy

Create(Int64, Int64)

Nikdy

Create(Int64, Int64, Int64)

Nikdy

Konfigurace statického rozsahu Partitioners pro Parallel.ForEach

V For smyčky, tělo smyčky podle metody jako delegát. Vyvolání přidělíte náklady je přibližně stejná jako volání virtuální metody. V některých případech může být subjekt paralelní smyčky dostatečně malá, aby náklady vyvolání delegovat na každé opakování smyčky se významné. V takových situacích můžete použít jeden z Create přetížení vytvořit IEnumerable<T> rozsah oddílů přes prvky zdroj. Pak můžete předat tuto kolekci se pohybuje ForEach Metoda, jehož orgán se skládá z pravidelně for smyčky. Výhodou tohoto přístupu je, že delegát vyvolání náklady nabíhají pouze jednou za oblast spíše než jednou za prvek. Následující příklad ukazuje základní vzor.

Imports System.Threading.Tasks
Imports System.Collections.Concurrent

Module PartitionDemo

    Sub Main()
        ' Source must be array or IList.
        Dim source = Enumerable.Range(0, 100000).ToArray()

        ' Partition the entire source array. 
        ' Let the partitioner size the ranges.
        Dim rangePartitioner = Partitioner.Create(0, source.Length)

        Dim results(source.Length - 1) As Double

        ' Loop over the partitions in parallel. The Sub is invoked
        ' once per partition.
        Parallel.ForEach(rangePartitioner, Sub(range, loopState)

                                               ' Loop over each range element without a delegate invocation.
                                               For i As Integer = range.Item1 To range.Item2 - 1
                                                   results(i) = source(i) * Math.PI
                                               Next
                                           End Sub)
        Console.WriteLine("Operation complete. Print results? y/n")
        Dim input As Char = Console.ReadKey().KeyChar
        If input = "y"c Or input = "Y"c Then
            For Each d As Double In results
                Console.Write("{0} ", d)
            Next
        End If

    End Sub
End Module
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {

        // Source must be array or IList.
        var source = Enumerable.Range(0, 100000).ToArray();

        // Partition the entire source array.
        var rangePartitioner = Partitioner.Create(0, source.Length);

        double[] results = new double[source.Length];

        // Loop over the partitions in parallel.
        Parallel.ForEach(rangePartitioner, (range, loopState) =>
        {
            // Loop over each range element without a delegate invocation.
            for (int i = range.Item1; i < range.Item2; i++)
            {
                results[i] = source[i] * Math.PI;
            }
        });

        Console.WriteLine("Operation complete. Print results? y/n");
        char input = Console.ReadKey().KeyChar;
        if (input == 'y' || input == 'Y')
        {
            foreach(double d in results)
            {
                Console.Write("{0} ", d);
            }           
        }
    }
}

Každý podproces ve smyčce obdrží vlastní Tuple<T1, T2> , která obsahuje počáteční a koncové hodnoty indexu v zadaném použitém dílčí rozsah. Vnitřní for smyčka používá fromInclusive a toExclusive hodnoty pole smyčku nebo IList přímo.

Jeden z Create přetížení umožňuje určit velikost oddílů a počet oddílů. Toto přetížení lze použít v situacích, kde je tak malý, že volání metody i jeden virtuální za prvek má znatelný vliv na výkon práce za prvek.

Vlastní rozdělovače

V některých případech pravděpodobně zabere nebo dokonce nutné implementovat vlastní partitioner. Můžete mít například třídy vlastní kolekce, která můžete efektivněji než výchozí partitioners mohou na základě znalosti vnitřní struktura třídy oddílu. Nebo chcete vytvořit oddíly rozsah znalost jak dlouho bude trvat na různých místech ve zdrojové kolekci prvků procesu na základě různých velikostí.

Chcete-li vytvořit základní vlastní partitioner odvození třídy z System.Collections.Concurrent.Partitioner<TSource> a přepsat virtuální metody popsané v následující tabulce.

GetPartitions

Tato metoda se nazývá hlavní podproces jednou a vrátí IList(IEnumerator(TSource)). Každý podproces smyčky nebo dotazu můžete volat GetEnumerator v seznamu načtení IEnumerator<T> přes odlišné oddílu.

SupportsDynamicPartitions

Vrátit true Pokud implementujete GetDynamicPartitions, jinak false.

GetDynamicPartitions

Pokud SupportsDynamicPartitions je true, tuto metodu lze volat volitelně namísto GetPartitions.

Výsledky musí být sortable nebo vyžadují indexovaný přístup do prvků, potom odvodit z System.Collections.Concurrent.OrderablePartitioner<TSource> a přepsat virtuální metody popsané v následující tabulce.

GetPartitions

Tato metoda se nazývá hlavní podproces jednou a vrátí IList(IEnumerator(TSource)). Každý podproces smyčky nebo dotazu můžete volat GetEnumerator v seznamu načtení IEnumerator<T> přes odlišné oddílu.

SupportsDynamicPartitions

Vrátit true Pokud implementujete GetDynamicPartitions; jinak false.

GetDynamicPartitions

Obvykle stačí volá GetOrderableDynamicPartitions.

GetOrderableDynamicPartitions

Pokud SupportsDynamicPartitions je true, tuto metodu lze volat volitelně namísto GetPartitions.

Následující tabulka obsahuje další podrobnosti o tři druhy zátěže implementace partitioners OrderablePartitioner<TSource> Třída

Metody/vlastnosti

Objekty IList / pole bez zátěže

Objekty IList / pole s vyrovnáváním zatížení

IEnumerable

GetOrderablePartitions

Používá rozdělení oblasti

Použití bloku rozdělení optimalizované pro seznamy pro zadané partitionCount

Použití oddílů bloku vytvořením statické počet oddílů.

OrderablePartitioner<TSource>.GetOrderableDynamicPartitions

Není podporována vyvolá výjimku.

Chunk používá rozdělení optimalizované pro seznamy a dynamické oddíly

Použití oddílů bloku vytvořením dynamické počet oddílů.

KeysOrderedInEachPartition

Vrátítrue

Vrátítrue

Vrátítrue

KeysOrderedAcrossPartitions

Vrátítrue

Vrátífalse

Vrátífalse

KeysNormalized

Vrátítrue

Vrátítrue

Vrátítrue

SupportsDynamicPartitions

Vrátífalse

Vrátítrue

Vrátítrue

Dynamické oddíly

Pokud máte v úmyslu partitioner pro použití v ForEach Metoda, musí být schopen vrátit dynamický počet oddílů. To znamená, že partitioner může dodat čítač výčtu nový oddíl na požádání kdykoli během provádění smyčky. V podstatě kdykoli smyčky přidá nový úkol paralelní, požadavky nový oddíl pro tento úkol. Pokud požadujete data CR, potom odvodit z System.Collections.Concurrent.OrderablePartitioner<TSource> tak, aby každá položka v každém oddílu je přiřazen jedinečný index.

Další informace a příklady naleznete v Postupy: Jak zavést dynamické oddíly.

Smlouva Partitioners

Při implementaci vlastních partitioner, postupujte podle těchto pokynů zajistit správné interakce s PLINQ a ForEach v TPL:

  • Pokud GetPartitions je jen s argumentem nula nebo méně partitionsCount, vyvoláním ArgumentOutOfRangeException. Ačkoli PLINQ a TPL nebude nikdy předat partitionCount rovna 0, přesto doporučujeme můžete chránit proti možnost.

  • GetPartitionsa GetOrderablePartitions by měl vždy vrátit partitionsCount počet oddílů. Pokud partitioner dojdou data, nelze vytvořit oddíly tolik podle požadavku by metoda vrátí prázdný výčet všech zbývajících oddíly. V opačném případě bude vyvolávají PLINQ a TPL InvalidOperationException.

  • GetPartitions, GetOrderablePartitions, GetDynamicPartitions, a GetOrderableDynamicPartitions by nikdy vrátit null ()Nothing v jazyce Visual Basic). Pokud ano, PLINQ / vyvolají TPL InvalidOperationException.

  • Oddíly, které lze plně a jednoznačně výčet zdroj dat vždy metody, které oddíly vrátit zpět. Je třeba bez duplikace přeskočené položky zdroje dat nebo pokud výslovně požaduje návrh partitioner. Pokud toto pravidlo není dodržena, může výstupní objednávku kódována.

  • Následující mechanismy získání logická vždy přesně musí vrátit následující hodnoty tak, že nejsou kódována výstupní objednávky:

    • KeysOrderedInEachPartition: Každý oddíl vrátí rostoucími klíče indexy prvků.

    • KeysOrderedAcrossPartitions: Pro všechny oddíly, které jsou vraceny indexy klíčů v oddílu i vyšší než indexy klíčů v oddílu i-1.

    • KeysNormalized: Všechny indexy klíčů jsou monotónně zvyšování bez mezer, počínaje nulou.

  • Všechny indexy musí být jedinečný. Pravděpodobně není duplicitní indexy. Pokud toto pravidlo není dodržena, může výstupní objednávku kódována.

  • Všechny indexy musí být nezáporná. Pokud toto pravidlo není dodržena, může PLINQ/TPL vyvoláním výjimky.

Viz také

Úkoly

Postupy: Jak zavést dynamické oddíly

Postupy: Jak implementovat rozdělovač se statickým počtem oddílů

Koncepty

Paralelní programování v rozhraní .NET Framework