2011-11-13 4 views
8

Hola, esto es algo que realmente me está molestando y espero que alguien tenga una respuesta para mí. He estado leyendo sobre ref (y out) y estoy tratando de averiguar si estoy ralentizando mi código usando ref s. Comúnmente SUBSTITUIRÉ algo como:¿El costo de rendimiento de usar ref en lugar de devolver los mismos tipos?

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

con

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

debido a mis ojos este

AddToInt(ref _value, _add); 

es más fácil de leer y el código de este

_value = AddToInt(_value, _add); 

I sé exactamente lo que estoy haciendo en el co de usar ref, en lugar de devolver un valor. Sin embargo, el rendimiento es algo que tomo en serio, y aparentemente la eliminación de referencias y la limpieza es mucho más lenta cuando se utilizan refs.

Lo que me gustaría saber es por qué todos los envíos que leí dice que hay muy pocos lugares que normalmente pasar una ref (sé que los ejemplos son artificiales, pero espero que se entiende la idea), cuando se me parece que el ejemplo ref es más pequeño, más limpio y más exacto.

También me gustaría saber por qué ref realmente es más lento que devolver un tipo de valor; a mí me parece que si voy a editar mucho el valor de la función antes de devolverlo, sería más rápido para hacer referencia a la variable real para editarla en lugar de una instancia de esa variable poco antes de que se limpie de la memoria.

+3

me gustaría por lo general prefieren devolver un valor por razones estilísticas, ya que me gusta de efectos secundarios funciones gratuitas. – CodesInChaos

+0

¿Está tratando de calcular el costo de rendimiento? ¡Pregunta a un generador de perfiles, no nos preguntes! – sehe

+0

El primer caso es más fácil de leer porque ha nombrado su método en consecuencia. Algo como '_value = Add (_value, _add);' o '_value = SumOf (_value, _add);' es más legible para mí. – nawfal

Respuesta

10

El tiempo principal que "ref" se utiliza en la misma frase o comentario a rendimiento se Cuando se habla de algunos casos muy atípicos, por ejemplo en escenarios de XNA donde los "objetos" del juego son comúnmente representados por estructuras en lugar de clases para evitar problemas con GC (que tiene un impacto desproporcionado en XNA).Esto se convierte en útil para:

  • Evitar la copia de un gran tamaño struct varias veces en la pila
  • evitar la pérdida de datos debido a la mutación de una copia struct (estructuras de XNA son comúnmente mutable, en contra de la práctica habitual)
  • permiten pasar una struct en un una serie directamente, en lugar de tener que copiar hacia fuera y hacia atrás en

en todos los demás casos, "ref" es más comúnmente asociado con un efecto secundario adicional, no se expresa fácilmente en el retorno v alue (por ejemplo, vea Monitor.TryEnter).

Si no tiene un escenario como XNA/struct one, y no hay ningún efecto secundario incómodo, entonces simplemente use el valor de retorno. Además de ser más típico (que en sí mismo tiene valor), bien podría implicar pasar menos datos (y int es más pequeño que una referencia en x64, por ejemplo), y podría requerir menos desreferenciación.

Finalmente, el enfoque de retorno es más versátil; usted no siempre quiere actualizar la fuente. Contraste:

// 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); 

creo que la última es la menos clara (con la otra "ref" uno cerca detrás de él) y el uso árbitro aún menos claro en los idiomas en que no es explícita (VB por ejemplo) .

+0

¡Gracias, eso explica con precisión dónde y no quiero usar ref y por qué! Lo mejor de todo es que me has dado un montón de cosas para ver que de otra manera no tendría. – user1044057

+1

Sin embargo, una ventaja importante de la referencia es su ejemplo: si se pasa algo por referencia a un método como Agregar, es posible que el método realice la actualización sin bloqueos, sin bloqueos, atómico, seguro de subprocesos Moda. Entonces, si dos hilos llaman simultáneamente a un método "Agregar" como se muestra en el segundo ejemplo, x tendría diez agregados a él. Por el contrario, si uno o ambos hilos hicieron la actualización como se muestra en el primer ejemplo, un hilo podría deshacer la actualización realizada por el otro. – supercat

+0

@supercat para hacer eso, todavía necesitarían usar Enclavamiento, etc. ... –

3

Primero, no moleste si usa ref es más lento o más rápido. Es una optimización prematura. En 99,9999% de los casos, no se encontrará con una situación que podría causar un paro de rendimiento.

En segundo lugar, se prefiere devolver el resultado del cálculo como un valor de retorno en comparación con el uso de ref debido a la naturaleza 'funcional' habitual de los lenguajes tipo C. Conduce a un mejor encadenamiento de declaraciones/llamadas.

+16

Siempre interpreto estas respuestas de "Es una optimización prematura" como "No sé". – Rauhotz

+2

Bueno, realmente * sé * y sé cómo medirlo. Sin embargo, no tiene sentido perder tiempo porque estamos hablando de unos pocos ciclos de CPU de diferencia por llamada. –

+4

No me gusta trabajar con desarrolladores que siempre usan el rendimiento como el caso principal al elegir entre algunos estilos de codificación/diseños/arquitecturas en sectores NON críticos para el rendimiento. Sucede mucho – Guillaume86

2

Estoy de acuerdo con Ondrej aquí. Desde una perspectiva estilística, si comienzas a pasar todo con ref acabarás trabajando con desarrolladores que querrán estrangularte por diseñar una API como esta.

Simplemente devuelva cosas del método, no tiene 100% de sus métodos devolviendo void. Lo que estás haciendo conducirá a un código muy sucio y podría confundir a otros desarrolladores que terminan trabajando en tu código. Favorecer la claridad sobre el rendimiento aquí, ya que de todos modos no ganarás mucho en optomización.

cheque este SO mensaje: C# 'ref' keyword, performance

y este artículo de Jon Skeet: http://www.yoda.arachsys.com/csharp/parameters.html

2

El objetivo principal del uso de la palabra clave ref es indicar que el valor de la variable se puede cambiar por la función en la que se pasa. Cuando pasa una variable por valor, las actualizaciones desde dentro de la función no afectan a la copia original.

Es extremadamente útil (y más rápido) para situaciones en las que desea valores de devolución múltiples y construir una estructura o clase especial para los valores devueltos sería excesivo. Por ejemplo,

public void Quaternion.GetRollPitchYaw(ref double roll, ref double pitch, ref double yaw){ 
    roll = something; 
    pitch = something; 
    yaw = something; 
} 

Este es un patrón bastante fundamental en los idiomas que tienen un uso ilimitado de punteros. En c/C++, con frecuencia se ven elementos primitivos que se pasan por valor con clases y matrices como punteros. C# hace exactamente lo contrario, por lo que 'ref' es útil en situaciones como las anteriores.

Cuando pasa una variable que desea actualizar en una función por ref, solo se necesita 1 operación de escritura para darle su resultado. Sin embargo, al devolver valores, normalmente escribe en alguna variable dentro de la función, la devuelve y luego la escribe de nuevo en la variable de destino. Dependiendo de los datos, esto podría agregar una sobrecarga innecesaria. De todos modos, estas son las cosas principales que normalmente considero antes de usar la palabra clave ref.

A veces, la referencia es un poco más rápida si se usa de esta manera en C#, pero no lo suficiente como para justificar el rendimiento.

Esto es lo que obtuve en una máquina de 7 años usando el siguiente código para pasar y actualizar una cadena de 100k por ref y por valor.

Salida:

iteraciones: 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

En ese tamaño para un objeto, crear una nueva copia va a ser más caro, por supuesto. Para la mayoría de los casos normales, no creo que pueda haber una gran diferencia. – nawfal

+0

todos arrojan los resultados son correctos, las acciones no son las mismas en las dos funciones. En realidad, la función es devolver algo, la otra no. La palabra clave ref solo se compara con la palabra clave no ref no es más rápida, pero casi la misma, si no más lenta. Para hacer una medida correcta, realice dos funciones con las mismas acciones y solo debe cambiar el 'ref' ... – Aristos

Cuestiones relacionadas