Terugwinnen van geheugen in een multithreaded omgeving - parallelle taken

Ik ben momenteel bezig met het ontwikkelen van een klein simulatiehulpprogramma, met behulp van de taakparallelle bibliotheek om de snelheid te verbeteren waarmee resultaten worden geproduceerd. De simulatie zelf is een lange, cpu-intensieve klus die in wezen bestaat uit duizenden kleinere klusjes met een simulatie met verschillende variabelen.

De bronnen die door elke taak worden gebruikt, worden echter niet vrijgegeven totdat alles is voltooid, wat leidt tot geheugenlekken en uit geheugenuitzonderingen als er voldoende variabelen worden gebruikt. Door een GC aan het einde van elke taak te forceren, worden bronnen vrijgemaakt, maar ik ben van mening dat dit alle threads moet onderbreken om uit te voeren, en resulteert zo in prestaties die dicht in de buurt komen van single thread!

Hoe kan ik middelen vrijgeven tijdens lange operaties als deze?

Onder 'bronnen' in deze context verwijs ik naar arrays van dubbelen ... gewoon veel van hen.

public List Questions; //Each variable combination is added as a Q

//Create a task for each simulation
Task[] tasks = new Task[Questions.Count]; 
foreach(var q in Questions)
{
    AnalysisTask temp = q
    tasks[taskCount] = Task.Factory.StartNew((t) =>
             {
                var result = EvaluateRules(temp);
                if(reults.Value > Leader[0].Value)
                    Leader[0] = result;
                else
                {
                    result.Dispose();
                    //This releases resources but interrupts threads
                    //GC.Collect(2, GCCollectionMode.Forced); 
                    return null;
                }
                return result;

             }
}

//Completion task
Task.Factory.ContinueWhenAll(tasks, (ant) =>
       {
          DoSomethingWithAnswer(Leader[0]);
       }

Misschien heb ik de verkeerde aanpak gekozen bij het opzetten van de taken? Ik zal dankbaar zijn voor elk advies of richting :)

0
Het probleem en de symptomen zijn beschreven in de tweede paragraaf. Alle 3 hieronder gegeven antwoorden waren nuttig bij het verbeteren van de code, maar het hoofdthema was gericht op het geaccepteerde antwoord met betrekking tot de referenties van de reeks taken.
toegevoegd de auteur Vok, de bron

3 antwoord

Uw huidige implementatie heeft een aantal problemen. Een daarvan is dat wanneer een uitwisseling plaatsvindt met Leader [0] , de referentie van de vorige leider verloren gaat en nooit wordt verwijderd. Dit kan de oorzaak zijn van uw geheugenlek. De tweede is dat de vergelijking en toewijzing aan Leader [0] niet atomair gebeuren. Het is mogelijk om deze reeks gebeurtenissen te hebben: thread 1 is te vergelijken met Leader [0] en wordt waar met een result.Value van 1, thread 2 is te vergelijken met Leider [0] en krijgt waar met een result.Value van 2, thread 2 schrijft naar Leader [0] , thread 1 schrijft naar Leader [0] . Het resultaat is dat Leader [0] de waarde 1 heeft als de maximale waarde 2 was.

Dus als we referenties correct verwijderen, hoeft u garbage collection mogelijk niet te forceren. De onderstaande code lost deze problemen op door een vergrendeling op te heffen bij het wijzigen van Leader en het opslaan van een verwijzing naar de vorige Leader [0] . Vervolgens wordt het ongebruikte resultaat of de vorige leider verwijderd. Vermoedelijk duurt het EvaluateRules enige tijd, dus er zou niet veel lock contention moeten zijn.

tasks[taskCount] = Task.Factory.StartNew(() =>
     {
        var result = EvaluateRules(temp);

        var toBeDisposed = result;
        lock(Leader)//should be locking on a private object
        {
           if (result.Value > Leader[0].Value)
           {
             toBeDisposed = Leader[0];
             Leader[0] = result;
           }
        }

        toBeDisposed.Dispose();       

     });

Moet u ook resultaat van elke taak retourneren? U lijkt alleen Leader [0] nodig te hebben voor uw vervolgtaak. Door result te retourneren, bewaar je een referentie die niet gc'd kan worden totdat de taken zelf gc'd zijn.

1
toegevoegd

Garbage collection stopt niet je hele proces. Zie hier voor meer informatie.

Als u een GC moet aanroepen (of uw proces sterft), en als een GC echt uw prestaties schaadt (het is onwaarschijnlijk dat u een GC de hele tijd uitvoert ), kunt u uw simulatie altijd in verschillende processen breken (gebruik natuurlijk geen proces per thread, maar elke X-thread kan bij één proces horen).

Ik moet echter toegeven dat je waarschijnlijk iets verkeerds doet met je geheugenbeheer, maar je moet meer informatie geven.

0
toegevoegd
Probeer bij elke taak meer dan één simulatie te verwerken.
toegevoegd de auteur zmbq, de bron
Naar aanleiding van je reactie en link merkte ik een GC interrupt op, beschreven als "pauzes blijven kort (bijv. Onder 50ms)" ... elke individuele simulatie kost misschien 2ms om uit te voeren, dus belde het verzamelen elke keer uitgelegd waarom het zo'n impact had. Ik had de oproep pas echt toegevoegd als test bij het identificeren van het lek, maar het noemen van elke x-taken helpt. Misschien is het nog niet zo dat het poolen van de arrays, zoals Martin James heeft gesuggereerd, meetelt als iets verkeerds doen met geheugenbeheer.
toegevoegd de auteur Vok, de bron

Als de arrays een constante grootte hebben, of een maximale grootte kan worden gedefinieerd, of een reeks van groottenbereik kan worden gedefinieerd, kunt u een pool van deze arrays maken bij het opstarten of een pool van lijsten met arrays van grootten opbouwen tijdens de run . Het is dan niet nodig de arrays te dealloceren - u kunt ze eenvoudigweg opnieuw indelen om ze later opnieuw te gebruiken. Een array met BlockingCollection-wachtrijen [sizeRange] zou hetzelfde doen als de pool.

0
toegevoegd
Als er een reeks pools van verschillende grootten is, is het het gemakkelijkst als elk dataArray-object een verwijzing naar zijn eigen pool als een privégegevenslid heeft. Op die manier hoeft u alleen maar een 'release ()' methode op elke dataArray aan te roepen om deze zichzelf naar zijn juiste pool te laten terugverdienen. Het is ook de moeite waard om tijdens het debuggen hoe dan ook een cheque op te nemen in de 'requeue (thisData)' methode van de pool dat 'thisData' nog niet in de pool is. Een dubbele release resulteert uiteindelijk in twee verschillende threads die dezelfde dataArray uit de pool halen met rampzalige gevolgen - ik deed dit vandaag in mijn embedded project :(
toegevoegd de auteur Martin James, de bron
Ik heb de array-grootte beperkt door een cyclische array te introduceren, dus het hebben van een pool van arrays lijkt een haalbare oplossing ... Ik zal hiernaar kijken!
toegevoegd de auteur Vok, de bron