2009-11-10 27 views
6

Ogni volta che scrivo un programma del modulo sottostante utilizzando LINQ to SQL, si finisce con un programma che acquisisce sempre più memoria mentre viene eseguito e cade in un heap consumando 2 GB dopo forse come poco più di 25.000 dischi. Finisco sempre per riscriverlo usando ADO.NET. Che cosa sto facendo di sbagliato?Elaborazione di set di dati di grandi dimensioni tramite LINQ

Precisazione: questa domanda non riguarda la velocità di elaborazione; le risposte su come farlo andare più veloce non hanno alcuna rilevanza.

foreach (int i=0; i<some_big_number; i++) 
{ 
    using (myDC dc = new myDC()) // my DataContext 
    { 
     myRecord record = (from r in dc.myTable where r.Code == i select r).Single(); 

     // do some LINQ queries using various tables from the data context 
     // and the fields from this 'record'. i carefully avoid referencing 
     // any other data context than 'dc' in here because I want any cached 
     // records to get disposed of when 'dc' gets disposed at the end of 
     // each iteration. 

     record.someField = newValueJustCalculatedAbove; 
     dc.SubmitChanges(); 
    } 
} 
+3

Penso che la risposta a "Cosa sto facendo di sbagliato?" è "Fare la stessa cosa e aspettarsi una risposta diversa". Questo è anche un segno di follia. Solo per i calci prova qualcos'altro. Ad esempio, scrivi il tuo codice di accesso sql in ADO.Net FIRST e salta tutto quel crow di linq. – NotMe

+4

Ma LINQ non è escremento, come suggerisci tu. È la più elegante delle tecnologie e persistere con essa è la mia testimonianza, piuttosto che un segno di follia. – Nestor

risposta

6

State esercitando pressione sul contesto dei dati per generare la query da zero ogni volta.

Prova invece a utilizzare una query compilata.

+0

Passare alle query compilate ha fatto la differenza, riducendo il consumo di memoria di almeno il 95%. Immagino che ricomporre continuamente le query sia comunque una cattiva idea, ma sono ancora curioso di sapere perché ha consumato così tanta memoria. Per il beneficio di altri, ecco una guida per iniziare con le query compilate: http://linqinaction.net/blogs/jwooley/archive/2007/09/04/linq-to-sql-compiled-queries. aspx – Nestor

+0

Le query compilate non sono sostituzioni drop-in per quelle non compilate, è necessario modificare il codice un po '. Questo articolo mi ha aiutato a capire perché: http://linqinaction.net/blogs/jwooley/archive/2007/09/04/linq-to-sql-compiled-queries.aspx – Nestor

+1

7 anni più tardi, e questo si è rivelato una manna dal cielo per aver risolto un problema di prestazioni in un'app legacy. Grazie mille. –

0

si potrebbe provare a mettere la creazione del DataContext al di fuori del ciclo for, non è sicuro la quantità di memoria che potrebbe risparmiare però.

Forse è possibile chiamare GC.Collect() dopo ogni ciclo, vedere se è possibile causare manualmente la garbage collection.

+1

Penso che qualsiasi cosa peggiori, dato che il contesto dei dati sopravvive abbastanza a lungo per creare migliaia di record memorizzati nella cache. Ma sto chiaramente fraintendendo qualcosa mentre qualcosa si sta ancora sviluppando. – Nestor

+0

@maxc - Hai almeno tentato di spostare la chiamata datacontext all'esterno del ciclo solo per vedere se fa alcuna differenza? – Breadtruck

+0

ho, si. Mi sono strappato i capelli periodicamente su questo per un anno. – Nestor

3

Si sta andando al database due volte per ogni iterazione del ciclo - una volta per recuperare la riga, quindi di nuovo per aggiornare la riga. Questo non è molto efficiente.

Si dovrebbe operare in lotti:

  • Prendi un set di righe sulla parte anteriore selezionando su una gamma piuttosto che un singolo valore, cioè 0-100 per il primo lotto, 101-200 per la prossima lotto e così via. Questo sarà più veloce se si dispone di un indice cluster definito nella colonna Codice.

  • Creare il contesto dati prima di entrare nel ciclo

  • All'interno del ciclo, basta aggiornare gli oggetti

  • SubmitChanges call() dopo il ciclo è finito, questo invierà tutti gli aggiornamenti al database in una singola connessione/transazione

  • Ripetere l'operazione per il lotto successivo

Si dovrebbe rendere configurabile la dimensione del batch, in quanto non si può essere sicuri di quale dimensione del batch produrrà le migliori prestazioni - non fissarlo con hardcode nell'applicazione.

Inoltre, vorrei utilizzare SingleOrDefault() con null-checking anziché Single(), a meno che non si possa garantire che ci sarà sempre una riga per qualsiasi valore di i.

EDIT:

In termini di utilizzo della memoria, che è molto più difficile da controllare, ma non è peculiare di LINQ to SQL, qualsiasi algoritmo di elaborazione batch ha a che fare con questo. Mentre non è consigliabile utilizzare GC.Collect() nella pratica, di solito è sufficiente come soluzione alternativa dopo l'elaborazione di un batch di grandi dimensioni.

Si potrebbe anche cercare di ridurre la quantità di dati che si recuperano per riga (a seconda di quanto deve cominciare). È possibile creare una nuova entità che esegue il mapping su un insieme molto più piccolo di colonne della stessa tabella, potenzialmente solo uno o due, in modo che quando si seleziona quell'entità si stiano recuperando solo le colonne con le quali si intende iniziare. Ciò migliorerebbe la velocità e l'impronta della memoria man mano che meno dati viaggiano sul filo e gli oggetti sono molto più piccoli.

+0

Certo, sono tutte ottime ottimizzazioni della velocità, ma l'ottimizzazione dovrebbe venire dopo che il programma è stato fatto funzionare. Al momento tutta la memoria disponibile viene rapidamente consumata in modo che non possa continuare affatto. – Nestor

+1

+1 per "Chiama SubmitChanges() DOPO che il ciclo è terminato" – Breadtruck

+0

E chiamare SubmitChanges() dopo che il ciclo è terminato farà esattamente ciò che si utilizza la memoria? Non stiamo parlando di velocità qui. – Nestor

1

Non è stato possibile replicare il problema. L'utilizzo della memoria era piatto. Prestazioni lente, ma memoria costante.

Sei sicuro di non perdere altrove? Potete produrre un campione di codice minimale che riproduca il problema?

Edit:

ho usato il praticamente lo stesso codice di esempio:

for (int ii = 1; ii < 200000; ii++) 
{ 
    using (var dc = new PlayDataContext()) 
    { 
     var record = 
      (from r in dc.T1s where r.Id == ii select r).SingleOrDefault(); 
     if (record != null) 
     { 
      record.Name = "S"; 
      dc.SubmitChanges(); 
     } 
    } 
} 

senza alcun problema.

Quindi le cose per escludere:

  • Framework versione. Sono in ultima posizione.
  • DataContext/complessità entità. La mia tabella di test è composta da soli due campi e Id (int) e un nome (nvarchar (max)).

È possibile riprodurre il problema con l'ultimo FW, con un piccolo esempio di DataContext?

+0

Sono d'accordo che penso che avremmo bisogno di vedere più del tuo codice per diagnosticare il problema. Ho usato LTS in grandi loop giganti come questo e non ho ancora incontrato questo problema di "perdita di memoria" che hai citato ... – Funka

+0

Ecco un esempio completo per te, anche se ovviamente non sono stato in grado di includere il contesto dati stesso. Sulla mia macchina, consuma più di 1 MB al secondo e utilizza 720 MB mentre scrivo questo. for (int corsa = 0; eseguire <1000; run ++) for (int i = 0; i <50000; i ++) { usando (MyDC MyDC = new MyDC()) { tblRecords registrare = (dalla r in myDC.tblRecords dove r.Code == seleziono r) .SingleOrDefault(); } } – Nestor

Problemi correlati