2011-11-13 14 views
8

Ciao, questo è qualcosa che mi infastidisce e spero che qualcuno abbia una risposta per me. Ho letto su ref (e out) e sto cercando di capire se sto rallentando il mio codice usando ref s. Comunemente Sostituirò qualcosa di simile:Il costo delle prestazioni per utilizzare ref invece di restituire gli stessi tipi?

int AddToInt(int original, int add){ return original+add; } 

con

void AddToInt(ref int original, int add){ original+=add; } // 1st parameter gets the result 

perché ai miei occhi questo

AddToInt(ref _value, _add); 

è più facile da leggere e il codice di questo

_value = AddToInt(_value, _add); 

I so esattamente cosa sto facendo sul co de usando ref, anziché restituire un valore. Tuttavia, le prestazioni sono qualcosa che prendo sul serio, e apparentemente il dereferenziamento e la pulizia sono molto più lenti quando si usano i ref.

Quello che mi piacerebbe sapere è il motivo per cui ogni post che ho letto dice che c'è molto pochi posti si sarebbe tipicamente passare un ref (lo so gli esempi sono artificiosa, ma spero che si ottiene l'idea), quando mi sembra che l'esempio ref sia più piccolo, più pulito e più preciso.

Mi piacerebbe anche sapere perché ref è davvero più lento di restituire un tipo di valore - a me sembrerebbe, se dovessi modificare molto il valore della funzione prima di restituirlo, che sarebbe più veloce per fare riferimento alla variabile attuale per modificarla in contrasto con un'istanza di quella variabile poco prima che venga pulita dalla memoria.

+3

mi piacerebbe di solito preferisco restituire un valore per ragioni stilistiche, dal momento che mi piace funzioni libere effetti collaterali. – CodesInChaos

+0

Stai cercando di capire il costo delle prestazioni? Chiedi un profiler, non chiedere a noi! – sehe

+0

Il primo caso è più facile da leggere perché hai chiamato il tuo metodo di conseguenza. Qualcosa come '_value = Aggiungi (_valore, _add);' o '_valore = SumOf (_valore, _add);' è più leggibile per me. – nawfal

risposta

10

Il tempo principale che "ref" è usato nella stessa sentance le prestazioni è quando si parla di alcuni casi molto atipici, per esempio in scenari XNA in cui gli "oggetti" del gioco sono comunemente rappresentati da strutture piuttosto che da classi per evitare problemi con GC (che ha un impatto sproporzionato su XNA).Questo diventa utile per:

  • impedire la copia un sovradimensionato struct più volte sullo stack
  • prevenire la perdita di dati a causa di mutare una copia struct (le strutture di XNA sono comunemente mutabili, contro la prassi normale)
  • permettono il superamento di un struct in un array direttamente, piuttosto che mai copiandolo e rientrare

in tutti gli altri casi, "ref" è più comunemente associato con un ulteriore effetto collaterale, non facilmente espressa nel ritorno v alue (ad esempio vedi Monitor.TryEnter).

Se non si dispone di uno scenario come XNA/struct e non vi sono effetti collaterali scomodi, è sufficiente utilizzare il valore restituito. Oltre ad essere più tipico (che di per sé ha valore), potrebbe comportare il passaggio di meno dati (e int è inferiore a un ref su x64 per esempio) e potrebbe richiedere meno dereferenziazione.

Infine, l'approccio di ritorno è più versatile; non vuoi ancora aggiornare la fonte. Contrasto:

// want to accumulate, no ref 
x = Add(x, 5); 

// want to accumulate, ref 
Add(ref x, 5); 

// no accumulate, no ref 
y = Add(x, 5); 

// no accumulate, ref 
y = x; 
Add(ref y, x); 

Penso che l'ultimo è il meno evidente (con l'altra "ref" uno vicino dietro di esso) e l'uso di ref è ancora meno chiaro in lingue in cui non è esplicita (VB per esempio) .

+0

Grazie, questo spiega esattamente dove faccio e non voglio usare ref e perché! Meglio di tutto mi hai dato un sacco di cose da controllare non avrei altrimenti. – user1044057

+1

Un importante vantaggio di ref, tuttavia, facendo riferimento al tuo esempio: se qualcosa viene passato facendo riferimento a un metodo come Add, potrebbe essere possibile che il metodo esegua l'aggiornamento in lock-free, non-blocking, atomic, thread-safe moda. Quindi se due thread chiamano simultaneamente un metodo "Aggiungi" come mostrato nel secondo esempio, x ne avrebbe aggiunti dieci. Al contrario, se uno o entrambi i thread facevano l'aggiornamento come mostrato nel primo esempio, un thread potrebbe annullare l'aggiornamento eseguito dall'altro. – supercat

+0

@supercat per fare ciò che avrebbero ancora bisogno di usare Interlocked ecc ... –

3

In primo luogo, non fastidio se utilizzare ref è più lento o più veloce. È l'ottimizzazione prematura. Nel 99,999% dei casi non si verificherebbe una situazione tale da causare un collo di bottiglia delle prestazioni.

In secondo luogo, restituire il risultato del calcolo come valore di ritorno anziché utilizzare ref è preceduto dalla solita natura "funcional" dei linguaggi di tipo C. Porta a una migliore concatenazione di dichiarazioni/chiamate.

+16

Interpreto sempre queste risposte di "Ottimizzazione prematura" come "Non so". – Rauhotz

+2

Beh, in realtà io * so * e so come misurarlo. Tuttavia, non ha senso sprecare tempo perché stiamo parlando di una differenza di cicli di CPU per chiamata. –

+4

Non mi piace (lavorare con) gli sviluppatori che usano sempre le prestazioni come caso principale quando si sceglie tra alcuni stili/design/architetture di codifica in settori NON critici per le prestazioni. Succede molto – Guillaume86

2

Sono d'accordo con Ondrej qui. Da una vista stilistica, se inizi a passare tutto con ref finirai per lavorare con gli sviluppatori che vorranno strangolarti per progettare un'API come questa!

Restituire semplicemente le informazioni dal metodo, non avere il 100% dei metodi che restituiscono void. Quello che stai facendo porterà a un codice molto sporco e potrebbe confondere altri sviluppatori che finiscono per lavorare sul tuo codice. Favorisci la chiarezza sulle prestazioni qui, dato che comunque non otterrai molto in optomizzazione.

controllo questo SO messaggio: C# 'ref' keyword, performance

e questo articolo di Jon Skeet: http://www.yoda.arachsys.com/csharp/parameters.html

2

Lo scopo principale dell'utilizzo della parola chiave ref è di indicare che il valore della variabile può essere modificato dalla funzione in cui viene passato. Quando si passa una variabile in base al valore, gli aggiornamenti dalla funzione non influiscono sulla copia originale.

È estremamente utile (e più veloce) per le situazioni in cui si desidera più valori di ritorno e la creazione di una struttura o una classe speciale per i valori di ritorno sarebbe eccessiva. Ad esempio,

Questo è uno schema piuttosto fondamentale in lingue che hanno un uso illimitato di puntatori. In c/C++ spesso si vedono le primitive passate per valore con le classi e gli array come puntatori. C# fa esattamente l'opposto, quindi 'ref' è utile in situazioni come sopra.

Quando si passa una variabile che si desidera aggiornare in una funzione mediante ref, è necessaria solo un'operazione di scrittura per fornire il risultato. Tuttavia, quando si restituiscono i valori, normalmente si scrive su una variabile all'interno della funzione, la si restituisce, quindi la si scrive nuovamente nella variabile di destinazione. A seconda dei dati, questo potrebbe aggiungere un sovraccarico non necessario. Comunque, queste sono le cose principali che prendo in considerazione prima di usare la parola chiave ref.

A volte ref è un po 'più veloce se usato in questo modo in C# ma non abbastanza da usarlo come giustificazione goto per le prestazioni.

Ecco cosa ho ottenuto su una macchina di 7 anni utilizzando il codice seguente passando e aggiornando una stringa di 100k per ref e per valore.

uscita:

iterazioni: 10000000 byref: 165ms ByVal: 417ms

private void m_btnTest_Click(object sender, EventArgs e) { 

    Stopwatch sw = new Stopwatch(); 

    string s = ""; 
    string value = new string ('x', 100000); // 100k string 
    int iterations = 10000000; 

    //----------------------------------------------------- 
    // Update by ref 
    //----------------------------------------------------- 
    sw.Start(); 
    for (var n = 0; n < iterations; n++) { 
     SetStringValue(ref s, ref value); 
    } 
    sw.Stop(); 
    long proc1 = sw.ElapsedMilliseconds; 

    sw.Reset(); 

    //----------------------------------------------------- 
    // Update by value 
    //----------------------------------------------------- 
    sw.Start(); 
    for (var n = 0; n < iterations; n++) { 
     s = SetStringValue(s, value); 
    } 
    sw.Stop(); 
    long proc2 = sw.ElapsedMilliseconds; 

    //----------------------------------------------------- 
    Console.WriteLine("iterations: {0} \nbyref: {1}ms \nbyval: {2}ms", iterations, proc1, proc2); 
} 

public string SetStringValue(string input, string value) { 
    input = value; 
    return input; 
} 

public void SetStringValue(ref string input, ref string value) { 
    input = value; 
} 
+0

A quella dimensione per un oggetto, la creazione di una nuova copia sarà ovviamente più costosa. Per la maggior parte dei casi normali non penso che ci possa essere una grande differenza. – nawfal

+0

tutti i risultati sono corretti, le azioni non sono le stesse sulle due funzioni. In realtà l'unica funzione è restituire qualcosa, l'altra no. La parola chiave ref da solo confrontare con la parola chiave non ref non è più veloce ma quasi la stessa, se non più lenta. Per fare una misura corretta fai due funzioni con le stesse azioni e solo il 'ref' deve cambiare ... – Aristos

Problemi correlati