none
Paralleler Ansatz für das Abarbeiten einer IList<T> gesucht RRS feed

  • Frage

  • Hallo Community,

    ich bekomme in eine Methode eine IList<TIn> (TIn ist ein Typ mit wiederum Listen drin und ein paar Eigenschaften) gereicht und muss daraus eine neue Liste<TOut> bauen. Klassisch sieht der Code ungefähr so aus:

    var result = new List<TOut>();
    foreach (var i in items) // items sei die List<TIn>
    {
        var x = i.SomeList.Where(p => p.Enum == Enum.Hugo).ToList();
        if (x.Count > 0 && x[0].Prop == prop)
            result.Add(x);
    }
    

    Ich habe das jetzt mal in eine Lösung mit Parallel.ForEach umgebaut und aus dem result ein ConcurrentDictionary<TOut, bool> gemacht (Missbrauch als Liste, ConcurrentQueue<TOut> ist ca. 15% langsamer, kennt man ja schon von Dictionary<...> und List<...>).

    Auf meinem 8-Kerner braucht die Lösung ca. 35% der Zeit wie die Variante oben. Nicht schlecht, aber geht's besser? Und wenn ja: wie? Könnte ein Ansatz mit Partitionern fixer sein? Die eingehende Liste kann unter Umständen SEHR lang sein.

    Thx in advance,
    Carsten

    Mittwoch, 2. November 2011 14:36

Antworten

  • Hi Carsten,

    Partitioner? Vielleicht gehts schneller, das kommt drauf an, wieviel mehr oder weniger du in wirklichkeit in dieser Liste wirklich tust, wie groß die Listen innerhalb der Liste sind, etc... Ja, es KÖNNTE mit Partinioer schneller sein. Vermutlich nicht sehr viel schneller.

    Was ich Dir empfehlen würde, wäre auf das ConcurrentDictionary zu verzichten und stattdessen mit dem Threadlocal-space zu arbeiten... und dann erst danach das ergebnis zusammenzuführen. Damit sparst du Dir die Synchronisation innerhalb der einzelnen Durchläufe und musst nur am Ende synchronisieren:

     

                //foreach (var i in items) // items sei die List<TIn>
                Parallel.ForEach(items,
                    //Init Threadlocal list
                () => { return new List<TOut>(); },
                //Your body
                (i, state, localList) =>
                {
                    var x = i.SomeList.Where(p => p.Enum == Enum.Hugo).ToList();
                    if (x.Count > 0 && x[0].Prop == 1)
                    {
                        localList.Add(new TOut(x));
                    }
                    return localList;
                },
                //Take locallist and add to resultlist
                (localList) =>
                {
                    lock (result)
                    {
                        result.AddRange(localList);
                    }
                }
                );
    


    • Bearbeitet GreatVolk Donnerstag, 3. November 2011 10:59
    • Als Antwort markiert CPosingies Freitag, 4. November 2011 16:52
    Donnerstag, 3. November 2011 10:58

Alle Antworten

  • Hi Carsten,

    Partitioner? Vielleicht gehts schneller, das kommt drauf an, wieviel mehr oder weniger du in wirklichkeit in dieser Liste wirklich tust, wie groß die Listen innerhalb der Liste sind, etc... Ja, es KÖNNTE mit Partinioer schneller sein. Vermutlich nicht sehr viel schneller.

    Was ich Dir empfehlen würde, wäre auf das ConcurrentDictionary zu verzichten und stattdessen mit dem Threadlocal-space zu arbeiten... und dann erst danach das ergebnis zusammenzuführen. Damit sparst du Dir die Synchronisation innerhalb der einzelnen Durchläufe und musst nur am Ende synchronisieren:

     

                //foreach (var i in items) // items sei die List<TIn>
                Parallel.ForEach(items,
                    //Init Threadlocal list
                () => { return new List<TOut>(); },
                //Your body
                (i, state, localList) =>
                {
                    var x = i.SomeList.Where(p => p.Enum == Enum.Hugo).ToList();
                    if (x.Count > 0 && x[0].Prop == 1)
                    {
                        localList.Add(new TOut(x));
                    }
                    return localList;
                },
                //Take locallist and add to resultlist
                (localList) =>
                {
                    lock (result)
                    {
                        result.AddRange(localList);
                    }
                }
                );
    


    • Bearbeitet GreatVolk Donnerstag, 3. November 2011 10:59
    • Als Antwort markiert CPosingies Freitag, 4. November 2011 16:52
    Donnerstag, 3. November 2011 10:58
  • Hi GreatVolk,

    das wird morgen gleich mal ausprobiert. Es ist spannend, was das .NET-Framework inzwischen alles an Parallelität her gibt.

    Frage: Ist es wirklich sicher, den Lock auf das result zu setzen? Springt mich auf den ersten Blick an wie ein lock(this), aber vielleicht gucke ich ja auch nur schief :-)

    Thx!
    Carsten

    Donnerstag, 3. November 2011 21:08
  • Ja das ist sicher.

    "lock(this)" ist auch sicher. Das sollte man aber tunlichst vermeiden, eben damit jemand anderes sowas machen kann, wie ich hier, ohne dass es gleich komische Konflikte/Effekte gibt.

    BTW. "lock(this)" ginge hier ja auch gar nicht... ich habe es ja in einer statischen Methode getestet ;)

     

    Wenn es Dir um noch mehr Performance geht (und weniger um Speicherplatzverschwendung), dann würde ich Dir noch empfehlen, Deine Listen mit der entsprechenden Kapazität zu erschaffen:

    // var result = new List<TOut>();
    var result = new List<TOut>(items.Count);
    
    //...
    
    // () => { return new List<TOut>(); },
    () => { return new List<TOut>(items.Count); },
    
    //...
    
    // var x = i.SomeList.Where(p => p.Enum == Enum.Hugo).ToList();
    var x = new List<WasAuchImmerPIst>(i.SomeList.Count);
    x.AddRange(i.SomeList.Where(p => p.Enum == Enum.Hugo));
    
    

     

    Donnerstag, 3. November 2011 23:05
  • Hi!

    Danke für die Info und die Extra-Tipps. Collections initialisiere ich eigentlich immer so, wenn ich abschätzen kann, wieviele Items da drin landen werden. Speicher ist ja heute kein Thema mehr, wird's wohl erst wieder mit Win 8 auf Smart Devices.

    Thx again und ein schickes WE wünsche ich

    Carsten

    Freitag, 4. November 2011 16:52
  • Hallo,

    noch eine kurze Anmerkung. Es dabei ja nicht nur um den Speicher, sondern auch um Rechenzeit. Das erweitern von Listen kann sehr aufwendig, und bei großen Collections durchaus spürbar sein.

    Viele Grüße
    Holger M. Rößler


    Kaum macht man es richtig, schon funktioniert es
    Samstag, 5. November 2011 09:13