Perché Where and Select hanno prestazioni superiori a Select?


145

Ho una lezione, in questo modo:

public class MyClass
{
    public int Value { get; set; }
    public bool IsValid { get; set; }
}

In realtà è molto più grande, ma questo ricrea il problema (stranezza).

Voglio ottenere la somma di Value, dove l'istanza è valida. Finora ho trovato due soluzioni a questo.

Il primo è questo:

int result = myCollection.Where(mc => mc.IsValid).Select(mc => mc.Value).Sum();

Il secondo, tuttavia, è questo:

int result = myCollection.Select(mc => mc.IsValid ? mc.Value : 0).Sum();

Voglio ottenere il metodo più efficiente. Inizialmente pensavo che il secondo sarebbe stato più efficiente. Quindi la parte teorica di me ha iniziato ad andare "Bene, uno è O (n + m + m), l'altro è O (n + n). Il primo dovrebbe funzionare meglio con più invalidi, mentre il secondo dovrebbe funzionare meglio con meno". Ho pensato che si sarebbero comportati allo stesso modo. EDIT: E poi @Martin ha sottolineato che il Where e il Select sono stati combinati, quindi in realtà dovrebbe essere O (m + n). Tuttavia, se guardi sotto, sembra che questo non sia correlato.


Quindi l'ho messo alla prova.

(Sono più di 100 righe, quindi ho pensato che fosse meglio pubblicarlo come Gist.)
I risultati sono stati ... interessanti.

Con tolleranza allo 0%:

Le scale sono a favore di Selecte Where, di circa ~ 30 punti.

How much do you want to be the disambiguation percentage?
0
Starting benchmarking.
Ties: 0
Where + Select: 65
Select: 36

Con una tolleranza del 2%:

È lo stesso, tranne che per alcuni erano entro il 2%. Direi che è un margine minimo di errore. Selecte Whereora hanno solo un vantaggio di ~ 20 punti.

How much do you want to be the disambiguation percentage?
2
Starting benchmarking.
Ties: 6
Where + Select: 58
Select: 37

Con una tolleranza del 5%:

Questo è ciò che direi essere il mio massimo margine di errore. Lo rende un po 'meglio per il Select, ma non molto.

How much do you want to be the disambiguation percentage?
5
Starting benchmarking.
Ties: 17
Where + Select: 53
Select: 31

Con una tolleranza del 10%:

Questo è fuori dal mio margine di errore, ma sono ancora interessato al risultato. Perché dà il vantaggio di Selecte Whereil venti punti che ha avuto per un po 'di tempo.

How much do you want to be the disambiguation percentage?
10
Starting benchmarking.
Ties: 36
Where + Select: 44
Select: 21

Con una tolleranza del 25%:

Questo è un modo, molto al di fuori del mio margine di errore, ma sono ancora interessato al risultato, perché Selecte Where ancora (quasi) mantengono il vantaggio di 20 punti. Sembra che lo stia surclassando in pochi e questo è ciò che gli dà il vantaggio.

How much do you want to be the disambiguation percentage?
25
Starting benchmarking.
Ties: 85
Where + Select: 16
Select: 0


Ora, immagino che il vantaggio di 20 punti sia arrivato dal centro, dove entrambi sono tenuti a aggirare la stessa prestazione. Potrei provare a registrarlo, ma sarebbe un sacco di informazioni da prendere. Un grafico sarebbe meglio, immagino.

Quindi è quello che ho fatto.

Seleziona vs Seleziona e dove.

Mostra che la Selectlinea rimane stabile (prevista) e che la Select + Wherelinea sale (prevista). Tuttavia, ciò che mi confonde è il motivo per cui non si incontra con il Select50 o precedente: in effetti mi aspettavo prima del 50, poiché è stato creato un enumeratore extra per Selecte Where. Voglio dire, questo mostra il vantaggio di 20 punti, ma non spiega il perché. Questo, immagino, è il punto principale della mia domanda.

Perché si comporta così? Dovrei fidarmi? In caso contrario, dovrei usare l'altro o questo?


Come menzionato da @KingKong nei commenti, puoi anche usare Sumil sovraccarico che prende un lambda. Quindi le mie due opzioni sono ora cambiate in questo:

Primo:

int result = myCollection.Where(mc => mc.IsValid).Sum(mc => mc.Value);

Secondo:

int result = myCollection.Sum(mc => mc.IsValid ? mc.Value : 0);

Lo renderò un po 'più breve, ma:

How much do you want to be the disambiguation percentage?
0
Starting benchmarking.
Ties: 0
Where: 60
Sum: 41
How much do you want to be the disambiguation percentage?
2
Starting benchmarking.
Ties: 8
Where: 55
Sum: 38
How much do you want to be the disambiguation percentage?
5
Starting benchmarking.
Ties: 21
Where: 49
Sum: 31
How much do you want to be the disambiguation percentage?
10
Starting benchmarking.
Ties: 39
Where: 41
Sum: 21
How much do you want to be the disambiguation percentage?
25
Starting benchmarking.
Ties: 85
Where: 16
Sum: 0

Il vantaggio di venti punti è ancora lì, il che significa che non ha a che fare con la combinazione Wheree Selectsottolineata da @Marcin nei commenti.

Grazie per aver letto attraverso il mio muro di testo! Inoltre, se sei interessato, ecco la versione modificata che registra il CSV utilizzato da Excel.


1
Direi che dipende da quanto costano la somma e l'accesso mc.Value.
Medinoc,

14
@ It'sNotALie. Where+ Selectnon causa due iterazioni separate sulla raccolta di input. LINQ to Objects lo ottimizza in un'unica iterazione. Maggiori informazioni sul mio post
MarcinJuraszek,

4
Interessante. Vorrei solo sottolineare che un ciclo for su un array sarebbe 10 volte più veloce della migliore soluzione LINQ. Quindi se vai a caccia di perf, non usare LINQ in primo luogo.
usr

2
A volte le persone si pongono dopo una vera ricerca, questa è una domanda di esempio: non sono un utente C # proveniente dall'elenco di domande frequenti.
Grijesh Chauhan,

2
@WiSaGaN Questo è un buon punto. Tuttavia, se ciò è dovuto alla mossa tra ramo e condizione, ci aspetteremmo di vedere la differenza più drammatica al 50% / 50%. Qui vediamo le differenze più drammatiche alle estremità, dove la ramificazione è più prevedibile. Se il Where è un ramo e il ternario è una mossa condizionale, allora ci aspetteremmo che i tempi Where tornino indietro quando tutti gli elementi sono validi, ma non torna mai giù.
John Tseng,

Risposte:


131

Selectscorre una volta sull'intero set e, per ciascun elemento, esegue un ramo condizionale (verificando la validità) e +un'operazione.

Where+Selectcrea un iteratore che salta gli elementi non validi (no yield), eseguendo un +solo sugli elementi validi.

Quindi, il costo per a Selectè:

t(s) = n * ( cost(check valid) + cost(+) )

E per Where+Select:

t(ws) = n * ( cost(check valid) + p(valid) * (cost(yield) + cost(+)) )

Dove:

  • p(valid) è la probabilità che un elemento nell'elenco sia valido.
  • cost(check valid) è il costo della filiale che verifica la validità
  • cost(yield)è il costo di costruzione del nuovo stato wheredell'iteratore, che è più complesso del semplice iteratore utilizzato dalla Selectversione.

Come puoi vedere, per un dato momento n, la Selectversione è una costante, mentre la Where+Selectversione è un'equazione lineare con p(valid)una variabile. I valori effettivi dei costi determinano il punto di intersezione delle due linee e, poiché cost(yield)possono essere diversi da cost(+), non si intersecano necessariamente a p(valid)= 0,5.


34
+1 per essere l'unica risposta (finora) che in realtà risponde alla domanda, non indovina la risposta e non genera semplicemente "anche io!" statistiche.
Binary Worrier

4
Tecnicamente i metodi LINQ creano alberi di espressioni che vengono eseguiti su tutta la raccolta una volta anziché "set".
Spoike,

Cosa cost(append)? Davvero una buona risposta, però, la guarda da una prospettiva diversa piuttosto che solo dalle statistiche.
It'sNotALie.

5
Wherenon crea nulla, restituisce solo un elemento alla volta dalla sourcesequenza se solo riempie il predicato.
MarcinJuraszek,

13
@Spoike - Gli alberi delle espressioni non sono rilevanti qui, perché questo è linq-to-object , non linq-to-qualcosa-altro (Entity, ad esempio). Questa è la differenza tra IEnumerable.Select(IEnumerable, Func)e IQueryable.Select(IQueryable, Expression<Func>). Hai ragione sul fatto che LINQ non fa "nulla" fino a quando non esegui l'iterazione della raccolta, che è probabilmente ciò che intendevi.
Kobi,

33

Ecco una spiegazione approfondita di ciò che sta causando le differenze temporali.


La Sum()funzione per IEnumerable<int>assomiglia a questo:

public static int Sum(this IEnumerable<int> source)
{
    int sum = 0;
    foreach(int item in source)
    {
        sum += item;
    }
    return sum;
}

In C #, foreachè solo zucchero sintattico per la versione .Net di un iteratore, (da non confondere ) . Quindi il codice sopra è effettivamente tradotto in questo:IEnumerator<T> IEnumerable<T>

public static int Sum(this IEnumerable<int> source)
{
    int sum = 0;

    IEnumerator<int> iterator = source.GetEnumerator();
    while(iterator.MoveNext())
    {
        int item = iterator.Current;
        sum += item;
    }
    return sum;
}

Ricorda, le due righe di codice che stai confrontando sono le seguenti

int result1 = myCollection.Where(mc => mc.IsValid).Sum(mc => mc.Value);
int result2 = myCollection.Sum(mc => mc.IsValid ? mc.Value : 0);

Ora ecco il kicker:

LINQ utilizza l'esecuzione differita . Pertanto, sebbene possa sembrare che result1iteri sulla raccolta due volte, in realtà itererà su di essa solo una volta. La Where()condizione viene effettivamente applicata durante Sum(), all'interno della chiamata a MoveNext() (Ciò è possibile grazie alla magia di yield return) .

Ciò significa che, per result1, il codice all'interno del whileciclo,

{
    int item = iterator.Current;
    sum += item;
}

viene eseguito una sola volta per ogni elemento con mc.IsValid == true. In confronto, result2eseguirà quel codice per ogni articolo della collezione. Ecco perché result1è generalmente più veloce.

(Tuttavia, nota che chiamare la Where()condizione all'interno MoveNext()ha ancora un piccolo overhead, quindi se la maggior parte / tutti gli articoli hanno mc.IsValid == true, result2sarà effettivamente più veloce!)


Spero che ora sia chiaro perché di result2solito è più lento. Ora vorrei spiegare perché nei commenti ho affermato che questi confronti di prestazioni LINQ non contano .

La creazione di un'espressione LINQ è economica. Chiamare le funzioni delegate è economico. L'allocazione e il looping su un iteratore sono economici. Ma è anche più economico non fare queste cose. Pertanto, se scopri che un'istruzione LINQ è il collo di bottiglia nel tuo programma, nella mia esperienza riscriverla senza LINQ lo renderà sempre più veloce di uno dei vari metodi LINQ.

Quindi, il tuo flusso di lavoro LINQ dovrebbe assomigliare a questo:

  1. Usa LINQ ovunque.
  2. Profilo.
  3. Se il profiler dice che LINQ è la causa di un collo di bottiglia, riscrivi quel pezzo di codice senza LINQ.

Fortunatamente, i colli di bottiglia di LINQ sono rari. Diamine, i colli di bottiglia sono rari. Ho scritto centinaia di dichiarazioni LINQ negli ultimi anni e ho finito per sostituire <1%. E la maggior parte di questi erano dovuti alla scarsa ottimizzazione SQL di LINQ2EF , piuttosto che essere colpa di LINQ.

Quindi, come sempre, scrivi prima un codice chiaro e sensibile e attendi fino a quando non hai profilato per preoccuparti delle micro-ottimizzazioni.


3
Piccolo addendum: la risposta principale è stata corretta.
It'sNotALie.

16

Cosa divertente. Sai come viene Sum(this IEnumerable<TSource> source, Func<TSource, int> selector)definito? Usa il Selectmetodo!

public static int Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
{
    return source.Select(selector).Sum();
}

Quindi, in realtà, dovrebbe funzionare quasi allo stesso modo. Ho fatto ricerche veloci per conto mio, e qui ci sono i risultati:

Where -- mod: 1 result: 0, time: 371 ms
WhereSelect -- mod: 1  result: 0, time: 356 ms
Select -- mod: 1  result 0, time: 366 ms
Sum -- mod: 1  result: 0, time: 363 ms
-------------
Where -- mod: 2 result: 4999999, time: 469 ms
WhereSelect -- mod: 2  result: 4999999, time: 429 ms
Select -- mod: 2  result 4999999, time: 362 ms
Sum -- mod: 2  result: 4999999, time: 358 ms
-------------
Where -- mod: 3 result: 9999999, time: 441 ms
WhereSelect -- mod: 3  result: 9999999, time: 452 ms
Select -- mod: 3  result 9999999, time: 371 ms
Sum -- mod: 3  result: 9999999, time: 380 ms
-------------
Where -- mod: 4 result: 7500000, time: 571 ms
WhereSelect -- mod: 4  result: 7500000, time: 501 ms
Select -- mod: 4  result 7500000, time: 406 ms
Sum -- mod: 4  result: 7500000, time: 397 ms
-------------
Where -- mod: 5 result: 7999999, time: 490 ms
WhereSelect -- mod: 5  result: 7999999, time: 477 ms
Select -- mod: 5  result 7999999, time: 397 ms
Sum -- mod: 5  result: 7999999, time: 394 ms
-------------
Where -- mod: 6 result: 9999999, time: 488 ms
WhereSelect -- mod: 6  result: 9999999, time: 480 ms
Select -- mod: 6  result 9999999, time: 391 ms
Sum -- mod: 6  result: 9999999, time: 387 ms
-------------
Where -- mod: 7 result: 8571428, time: 489 ms
WhereSelect -- mod: 7  result: 8571428, time: 486 ms
Select -- mod: 7  result 8571428, time: 384 ms
Sum -- mod: 7  result: 8571428, time: 381 ms
-------------
Where -- mod: 8 result: 8749999, time: 494 ms
WhereSelect -- mod: 8  result: 8749999, time: 488 ms
Select -- mod: 8  result 8749999, time: 386 ms
Sum -- mod: 8  result: 8749999, time: 373 ms
-------------
Where -- mod: 9 result: 9999999, time: 497 ms
WhereSelect -- mod: 9  result: 9999999, time: 494 ms
Select -- mod: 9  result 9999999, time: 386 ms
Sum -- mod: 9  result: 9999999, time: 371 ms

Per le seguenti implementazioni:

result = source.Where(x => x.IsValid).Sum(x => x.Value);
result = source.Select(x => x.IsValid ? x.Value : 0).Sum();
result = source.Sum(x => x.IsValid ? x.Value : 0);
result = source.Where(x => x.IsValid).Select(x => x.Value).Sum();

modsignifica: ogni 1 da modarticoli non è valido: per mod == 1ogni articolo non è valido, per mod == 2articoli dispari non sono validi, ecc. La raccolta contiene 10000000articoli.

inserisci qui la descrizione dell'immagine

E risultati per la raccolta con 100000000articoli:

inserisci qui la descrizione dell'immagine

Come si può vedere, Selecte Sumi risultati sono abbastanza coerenti in tutti i modvalori. Tuttavia wheree where+ selectnon lo sono.


1
È molto interessante che nei tuoi risultati, tutti i metodi inizino nello stesso posto e divergano, mentre i risultati di It'sNotALie si incrociano nel mezzo.
John Tseng,

6

La mia ipotesi è che la versione con Where filtra gli 0 e non sono oggetto di Sum (cioè non stai eseguendo l'aggiunta). Questa è ovviamente un'ipotesi poiché non riesco a spiegare come l'esecuzione di ulteriori espressioni lambda e la chiamata di più metodi superi una semplice aggiunta di 0.

Un mio amico ha suggerito che il fatto che lo 0 nella somma possa causare gravi penalità di prestazione a causa di controlli di overflow. Sarebbe interessante vedere come ciò si comporterebbe in un contesto non controllato.


Alcuni test con uncheckedlo rendono un po ', un pochino meglio per Select.
It'sNotALie.

Qualcuno può dire se deselezionato influisce sui metodi chiamati in pila o solo sulle operazioni di livello superiore?
Stilgar,

1
@Stilgar Si applica solo ai massimi livelli.
Branko Dimitrijevic,

Quindi forse dobbiamo implementare la somma non selezionata e provarla in questo modo.
Stilgar,

5

Eseguendo il seguente esempio, mi viene in mente che l'unica volta in cui + Seleziona può sovraperformare Seleziona è in realtà quando sta scartando una buona quantità (circa la metà nei miei test informali) dei potenziali elementi nell'elenco. Nel piccolo esempio che segue, ottengo all'incirca gli stessi numeri da entrambi i campioni quando il Where salta circa 4mil articoli da 10mil. Ho corso in versione e ho riordinato l'esecuzione di dove + select vs select con gli stessi risultati.

static void Main(string[] args)
        {
            int total = 10000000;
            Random r = new Random();
            var list = Enumerable.Range(0, total).Select(i => r.Next(0, 5)).ToList();
            for (int i = 0; i < 4000000; i++)
                list[i] = 10;

            var sw = new Stopwatch();
            sw.Start();

            int sum = 0;

            sum = list.Where(i => i < 10).Select(i => i).Sum();            

            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);

            sw.Reset();
            sw.Start();
            sum = list.Select(i => i).Sum();            

            sw.Stop();

            Console.WriteLine(sw.ElapsedMilliseconds);
        }

Non è perché non scarti le decine nel Select?
It'sNotALie.

3
L'esecuzione nel debug è inutile.
MarcinJuraszek,

1
@MarcinJuraszek Ovviamente. Volevo davvero dire che ho corso in versione :)
DavidN,

@ It'sNotALie Questo è il punto. Mi sembra che l'unico modo in cui + Seleziona può superare Select è quando Where sta filtrando una grande quantità degli elementi da sommare.
DavidN,

2
Questo è fondamentalmente ciò che afferma la mia domanda. Si legano a circa il 60%, come fa questo esempio. La domanda è: perché non si risponde qui.
It'sNotALie.

4

Se hai bisogno di velocità, fare semplicemente un giro semplice è probabilmente la soluzione migliore. E fare fortende ad essere migliore di foreach(supponendo che la tua collezione sia ovviamente un accesso casuale).

Ecco i tempi che ho ottenuto con il 10% di elementi non validi:

Where + Select + Sum:   257
Select + Sum:           253
foreach:                111
for:                    61

E con elementi non validi al 90%:

Where + Select + Sum:   177
Select + Sum:           247
foreach:                105
for:                    58

Ed ecco il mio codice di riferimento ...

public class MyClass {
    public int Value { get; set; }
    public bool IsValid { get; set; }
}

class Program {

    static void Main(string[] args) {

        const int count = 10000000;
        const int percentageInvalid = 90;

        var rnd = new Random();
        var myCollection = new List<MyClass>(count);
        for (int i = 0; i < count; ++i) {
            myCollection.Add(
                new MyClass {
                    Value = rnd.Next(0, 50),
                    IsValid = rnd.Next(0, 100) > percentageInvalid
                }
            );
        }

        var sw = new Stopwatch();
        sw.Restart();
        int result1 = myCollection.Where(mc => mc.IsValid).Select(mc => mc.Value).Sum();
        sw.Stop();
        Console.WriteLine("Where + Select + Sum:\t{0}", sw.ElapsedMilliseconds);

        sw.Restart();
        int result2 = myCollection.Select(mc => mc.IsValid ? mc.Value : 0).Sum();
        sw.Stop();
        Console.WriteLine("Select + Sum:\t\t{0}", sw.ElapsedMilliseconds);
        Debug.Assert(result1 == result2);

        sw.Restart();
        int result3 = 0;
        foreach (var mc in myCollection) {
            if (mc.IsValid)
                result3 += mc.Value;
        }
        sw.Stop();
        Console.WriteLine("foreach:\t\t{0}", sw.ElapsedMilliseconds);
        Debug.Assert(result1 == result3);

        sw.Restart();
        int result4 = 0;
        for (int i = 0; i < myCollection.Count; ++i) {
            var mc = myCollection[i];
            if (mc.IsValid)
                result4 += mc.Value;
        }
        sw.Stop();
        Console.WriteLine("for:\t\t\t{0}", sw.ElapsedMilliseconds);
        Debug.Assert(result1 == result4);

    }

}

A proposito, concordo con l' ipotesi di Stilgar : le velocità relative dei tuoi due casi variano a seconda della percentuale di articoli non validi, semplicemente perché la quantità di lavoro che Sumdeve fare varia nel caso "Dove".


1

Invece di provare a spiegare tramite la descrizione, ho intenzione di adottare un approccio più matematico.

Dato il codice seguente che dovrebbe approssimare ciò che LINQ sta facendo internamente, i relativi costi sono i seguenti:
Seleziona solo: Nd + Na
Dove + Seleziona:Nd + Md + Ma

Per capire il punto in cui attraverseranno, dobbiamo fare una piccola algebra:
Nd + Md + Ma = Nd + Na => M(d + a) = Na => (M/N) = a/(d+a)

Ciò significa che, affinché il punto di flesso sia al 50%, il costo di una chiamata del delegato deve essere approssimativamente uguale al costo di un'aggiunta. Poiché sappiamo che il punto di flesso effettivo era di circa il 60%, possiamo lavorare all'indietro e determinare che il costo di una chiamata del delegato per @ It'sNotALie era in realtà circa i 2/3 del costo di un'aggiunta che è sorprendente, ma questo è ciò che dicono i suoi numeri.

static void Main(string[] args)
{
    var set = Enumerable.Range(1, 10000000)
                        .Select(i => new MyClass {Value = i, IsValid = i%2 == 0})
                        .ToList();

    Func<MyClass, int> select = i => i.IsValid ? i.Value : 0;
    Console.WriteLine(
        Sum(                        // Cost: N additions
            Select(set, select)));  // Cost: N delegate
    // Total cost: N * (delegate + addition) = Nd + Na

    Func<MyClass, bool> where = i => i.IsValid;
    Func<MyClass, int> wSelect = i => i.Value;
    Console.WriteLine(
        Sum(                        // Cost: M additions
            Select(                 // Cost: M delegate
                Where(set, where),  // Cost: N delegate
                wSelect)));
    // Total cost: N * delegate + M * (delegate + addition) = Nd + Md + Ma
}

// Cost: N delegate calls
static IEnumerable<T> Where<T>(IEnumerable<T> set, Func<T, bool> predicate)
{
    foreach (var mc in set)
    {
        if (predicate(mc))
        {
            yield return mc;
        }
    }
}

// Cost: N delegate calls
static IEnumerable<int> Select<T>(IEnumerable<T> set, Func<T, int> selector)
{
    foreach (var mc in set)
    {
        yield return selector(mc);
    }
}

// Cost: N additions
static int Sum(IEnumerable<int> set)
{
    unchecked
    {
        var sum = 0;
        foreach (var i in set)
        {
            sum += i;
        }

        return sum;
    }
}

0

Penso sia interessante che il risultato di MarcinJuraszek sia diverso da quello di It'sNotALie. In particolare, i risultati di MarcinJuraszek iniziano con tutte e quattro le implementazioni nello stesso posto, mentre i risultati di It'sNotALie si incrociano nel mezzo. Spiegherò come funziona dalla fonte.

Supponiamo che ci siano nelementi totali ed melementi validi.

La Sumfunzione è piuttosto semplice. Passa semplicemente attraverso l'enumeratore: http://typedescriptor.net/browse/members/367300-System.Linq.Enumerable.Sum(IEnumerable%601)

Per semplicità, supponiamo che la raccolta sia un elenco. Sia Select che WhereSelect creeranno a WhereSelectListIterator. Ciò significa che gli attuali iteratori generati sono gli stessi. In entrambi i casi, esiste un oggetto Sumche gira su un iteratore, il WhereSelectListIterator. La parte più interessante dell'iteratore è il metodo MoveNext .

Poiché gli iteratori sono gli stessi, i loop sono gli stessi. L'unica differenza è nel corpo dei cappi.

Il corpo di questi lambda ha un costo molto simile. La clausola where restituisce un valore di campo e anche il predicato ternario restituisce un valore di campo. La clausola select restituisce un valore di campo e i due rami dell'operatore ternario restituiscono un valore di campo o una costante. La clausola select combinata ha il ramo come operatore ternario, ma WhereSelect usa il ramo in MoveNext.

Tuttavia, tutte queste operazioni sono abbastanza economiche. L'operazione più costosa finora è la filiale, dove ci costerà una previsione errata.

Un'altra operazione costosa qui è la Invoke. Richiamare una funzione richiede un po 'più di tempo rispetto all'aggiunta di un valore, come ha dimostrato Branko Dimitrijevic.

Anche l'accumulo verificato viene pesato Sum. Se il processore non ha un flag di overflow aritmetico, anche questo potrebbe essere costoso da controllare.

Quindi, i costi interessanti sono: è:

  1. ( n+ m) * Richiama + m*checked+=
  2. n* Richiama + n*checked+=

Pertanto, se il costo di Invoke è molto più elevato del costo dell'accumulazione controllata, il caso 2 è sempre migliore. Se sono circa pari, vedremo un equilibrio quando circa la metà degli elementi sono validi.

Sembra che sul sistema di MarcinJuraszek, controllato + = abbia un costo trascurabile, ma sui sistemi It'sNotALie e Branko Dimitrijevic, controllato + = abbia costi significativi. Sembra che sia il più costoso sul sistema di It'sNotALie poiché il punto di pareggio è molto più alto. Non sembra che nessuno abbia pubblicato risultati da un sistema in cui l'accumulo costa molto di più di Invoke.


@ It'sNotALie. Non penso che qualcuno abbia un risultato sbagliato. Non riuscivo proprio a spiegare alcune cose. Avevo supposto che il costo di Invoke fosse molto più elevato di quello di + =, ma è ipotizzabile che potrebbero essere molto più vicini a seconda delle ottimizzazioni hardware.
John Tseng,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.