2012-04-03 12 views
5

Sto lavorando a una sezione di un progetto che utilizza un gran numero di metodi di somma. Questi metodi somma vengono applicati su una DatatableConfronto dei metodi di somma in C#

Per testare il metodo migliore, uso la seguente

struttura Datatable

class LogParser 
{ 
    public DataTable PGLStat_Table = new DataTable(); 
    public LogParser() 
    { 
     PGLStat_Table.Columns.Add("type", typeof(string)); 
     PGLStat_Table.Columns.Add("desc", typeof(string)); 
     PGLStat_Table.Columns.Add("count", typeof(int)); 
     PGLStat_Table.Columns.Add("duration", typeof(decimal)); 
     PGLStat_Table.Columns.Add("cper", typeof(decimal)); 
     PGLStat_Table.Columns.Add("dper", typeof(decimal)); 
     PGLStat_Table.Columns.Add("occurancedata", typeof(string)); 
    }  
} 

seguente metodo viene utilizzato per riempire la tabella

LogParser pglp = new LogParser(); 
Random r2 = new Random(); 
for (int i = 1; i < 1000000; i++) 
{ 
    int c2 = r2.Next(1, 1000); 
    pglp.PGLStat_Table.Rows.Add("Type" + i.ToString(), "desc" + i , c2, 0, 0, 0, " "); 
} 
  • Sum viene applicato sulla colonna di conteggio, dove il valore di c2 viene aggiornato

Seguendo metodi utilizzati per calcolare Sum

Metodo 1 utilizzando Compute

Stopwatch s2 = new Stopwatch(); 
s2.Start(); 
object sumObject; 
sumObject = pglp.PGLStat_Table.Compute("Sum(count)", " "); 
s2.Stop(); 
long d1 = s2.ElapsedMilliseconds; 

Metodo 2 utilizzando il ciclo Foreach

s2.Restart(); 
int totalcount = 0; 
foreach (DataRow dr in pglp.PGLStat_Table.Rows) 
{ 
    int c = Convert.ToInt32(dr["count"].ToString()); 
    totalcount = totalcount + c; 
} 
s2.Stop(); 
long d2 = s2.ElapsedMilliseconds; 

Metodo 3 utilizzando Linq

s2.Restart(); 
var sum = pglp.PGLStat_Table.AsEnumerable().Sum(x => x.Field<int>("count")); 
MessageBox.Show(sum.ToString()); 
s2.Stop(); 
long d3 = s2.ElapsedMilliseconds; 

Dopo confronto i risultati sono

a) foreach è il più veloce 481ms

b) è prossimo 1016ms LINQ

c) e quindi Calcola 2253 ms


Query 1

ho accidentalmente cambiare "c2 per i" nella seguente dichiarazione

pglp.PGLStat_Table.Rows.Add("Type" + i.ToString(), "desc" + i , i, 0, 0, 0, " "); 

La dichiarazione Linq causano un errore

operazione aritmetica ha comportato un overflow.

Mentre il ciclo Compute e Foreach è ancora in grado di completare il calcolo anche se forse non è corretto.

È un tale comportamento un motivo di preoccupazione o mi manca una direttiva? (anche le cifre calcolate sono grandi)

Query 2

ero sotto l'impressione Linq fa più veloce, c'è un metodo o parametro ottimizzata che rende rendimento migliore.

grazie per consigli

Arvind

risposta

4

La somma più veloce è il prossimo (con Precompute DataColumn e cast diretto a int):

static int Sum(LogParser pglp) 
    { 
    var column = pglp.PGLStat_Table.Columns["count"]; 
    int totalcount = 0; 
    foreach (DataRow dr in pglp.PGLStat_Table.Rows) 
    { 
     totalcount += (int)dr[column]; 
    } 
    return totalcount; 
    } 

Statistica:

00:00:00.1442297, for/each, by column, (int) 
00:00:00.1595430, for/each, by column, Field<int> 
00:00:00.6961964, for/each, by name, Convert.ToInt 
00:00:00.1959104, linq, cast<DataRow>, by column, (int) 

Altro codice:

static int Sum_ForEach_ByColumn_Field(LogParser pglp) 
    { 
    var column = pglp.PGLStat_Table.Columns["count"]; 
    int totalcount = 0; 
    foreach (DataRow dr in pglp.PGLStat_Table.Rows) 
    { 
     totalcount += dr.Field<int>(column); 
    } 
    return totalcount; 
    } 
    static int Sum_ForEach_ByName_Convert(LogParser pglp) 
    { 
    int totalcount = 0; 
    foreach (DataRow dr in pglp.PGLStat_Table.Rows) 
    { 
     int c = Convert.ToInt32(dr["count"].ToString()); 
     totalcount = totalcount + c; 
    } 
    return totalcount; 
    } 
    static int Sum_Linq(LogParser pglp) 
    { 
    var column = pglp.PGLStat_Table.Columns["count"]; 
    return pglp.PGLStat_Table.Rows.Cast<DataRow>().Sum(row => (int)row[column]); 
    } 


    var data = GenerateData(); 
    Sum(data); 
    Sum_Linq2(data); 
    var count = 3; 
    foreach (var info in new[] 
     { 
     new {Name = "for/each, by column, (int)", Method = (Func<LogParser, int>)Sum}, 
     new {Name = "for/each, by column, Field<int>", Method = (Func<LogParser, int>)Sum_ForEach_ByColumn_Field}, 
     new {Name = "for/each, by name, Convert.ToInt", Method = (Func<LogParser, int>)Sum_ForEach_ByName_Convert}, 
     new {Name = "linq, cast<DataRow>, by column, (int)", Method = (Func<LogParser, int>)Sum_Linq}, 
     }) 
    { 
     var watch = new Stopwatch(); 
     for (var i = 0; i < count; ++i) 
     { 
     watch.Start(); 
     var sum = info.Method(data); 
     watch.Stop(); 
     } 
     Console.WriteLine("{0}, {1}", TimeSpan.FromTicks(watch.Elapsed.Ticks/count), info.Name); 
    } 
+0

accetto questo come risposta perché hai mostrato un altro modo per calcolare il risultato – arvind

1

Query 1.

Come si può vedere nella documentazione Enumerable.Sum metodo di estensione genera OverflowException su integer overflow. DataTable.Compute non ha una funzionalità come così come le operazioni di interi si utilizza in Metodo 2.


UPDATE: Query 2.

ho avuto l'impressione Linq fa più veloce, è lì un metodo o parametro ottimizzato che lo rende migliore.

AFAIK, non esiste alcun metodo per ottimizzare l'algoritmo di sommatoria di array (senza utilizzare il calcolo parallelo). Linq raddoppia il tempo utilizzato da foreach. Quindi, non penso che riguardi le prestazioni di linq ma l'inefficienza di calcolo (si noti che c'è un sovraccarico per l'interpretazione della stringa di query).

+0

è lo stesso dopo un certo intervallo. per favore nota che il tipo int è ancora in uso per Foreach and Compute e entrambi calcolano e producono risultati corretti. – arvind

+0

Vorrei aggiungere che il livello complessivo di performance è davvero molto grande quando si sommano almeno 5 o più colonne per un report usato frequentemente. Foreach funziona quasi 3 volte meglio di linq e 5x meglio del calcolo. sebbene il calcolo migliori se lo stesso calcolo viene applicato sulla stessa colonna ma raramente si verifica per un report – arvind

+1

@arvind, cosa intendi per corretto? Quando cambi da c2 a i ottieni la somma di tutti i numeri interi da 1 a 1000000. Sono 500.000,500,000 mentre Int32.MaxValue è 2.147.483.647. La differenza è che linq rileva un overflow mentre la sommatoria dei calcoli e degli interi calcola silenziosamente il risultato errato. –

1

bene si potrebbe migliorare un po 'nell'esempio di linq (AsEnumerable) ma questo è il comportamento previsto - Linq (2objects) non può essere più veloce come un ciclo (si potrebbe fare ancora meglio utilizzando un ciclo for(var i = ...) invece del foreach) - Immagino che cosa intendessi fare usando Linq2Sql - quindi l'aggregazione (somma) sarà fatta sul database e dovrebbe essere più veloce - ma come sembra non usare i dati del database ...

+0

Voglio dire, sembra gettare un'eccezione per le figure grandi, ho persino provato a cambiare il minimo, il livello massimo casuale in un intervallo più grande di 1.100.000 e ci sono risultati che sono corretti sia per il calcolo sia per il calcolo, ma il comando linq genera un errore Anche se il database non è in uso per questo scenario, ma vorrei verificare se questo errore è prodotto anche per una colonna di tabella. – arvind

+0

sono d'accordo che linq potrebbe essere migliore è calcoli relativi a Sql, e forse se c'è una clausola orderby e where – arvind