Pregunta sobre c#, xna, .net, api – ¿Cuál es el beneficio de usar / ref en lugar de regresar?

9

Estoy haciendo un juego usando el framework XNA, así que uso muchas funciones que operan en vectores. (especialmenteVector2 (64bit struct)). Lo que me molesta es que la mayoría de los métodos están definidos con parámetros de referencia y de salida. Aquí hay un ejemplo:

<code>void Min(ref Vector2 value1, ref Vector2 value2, out Vector2 result)
</code>

lo que también me parece un poco extraño. También hay otroMin que es mas obvio

<code>public static Vector2 Min(Vector2 value1, Vector2 value2);
</code>

Básicamente, casi todas las funciones tienen sobrecargas conrefs youts. Similares, otrosAPIs.

¿Cuál es el beneficio de este diseño? XNA está optimizado para el rendimiento, ¿podría ser un resultado? Por ejemplo, Quaternion requiere 128b donde pasa menos ref.

EDITAR:

Aquí hay un código de prueba:

<code>public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    private Vector2 vec1 = new Vector2(1, 2);
    private Vector2 vec2 = new Vector2(2, 3);
    private Vector2 min;
    private string timeRefOut1;
    private string timeRefOut2;
    private SpriteFont font;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";

        refOut1();
        refOut2();
    }

    private Vector2 refOut1()
    {
        Vector2 min = Vector2.Min(vec1, vec2);
        return min;
    }

    private Vector2 refOut2()
    {
        Vector2.Min(ref vec1, ref vec2, out min);
        return min;
    }

    protected override void Initialize()
    {
        const int len = 100000000;
        Stopwatch stopWatch = new Stopwatch();
        stopWatch.Start();
        for (int i = 0; i < len; i++)
        {
            refOut1();
        }
        stopWatch.Stop();

        timeRefOut1 = stopWatch.ElapsedMilliseconds.ToString();

        stopWatch.Reset();
        stopWatch.Start();
        for (int i = 0; i < len; i++)
        {
            refOut2();
        }
        stopWatch.Stop();

        timeRefOut2 = stopWatch.ElapsedMilliseconds.ToString();

        base.Initialize();
    }

    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);
        font = Content.Load<SpriteFont>("SpriteFont1");
    }

    protected override void Update(GameTime gameTime)
    {
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            this.Exit();

        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);

        spriteBatch.Begin();
        spriteBatch.DrawString(font, timeRefOut1, new Vector2(200, 200), Color.White);
        spriteBatch.DrawString(font, timeRefOut2, new Vector2(200, 300), Color.White);
        spriteBatch.End();

        // TODO: Add your drawing code here

        base.Draw(gameTime);
    }
}
</code>

Los resultados:

refOut1 2200refOut2 1400

Win 7 64bit, .Net 4. XNA 4.0

También el código IL

<code>.method public hidebysig static void  Min(valuetype Microsoft.Xna.Framework.Vector2& value1,
                                          valuetype Microsoft.Xna.Framework.Vector2& value2,
                                          [out] valuetype Microsoft.Xna.Framework.Vector2& result) cil managed
{
  // Code size       69 (0x45)
  .maxstack  3
  IL_0000:  ldarg.2
  IL_0001:  ldarg.0
  IL_0002:  ldfld      float32 Microsoft.Xna.Framework.Vector2::X
  IL_0007:  ldarg.1
  IL_0008:  ldfld      float32 Microsoft.Xna.Framework.Vector2::X
  IL_000d:  blt.s      IL_0017
  IL_000f:  ldarg.1
  IL_0010:  ldfld      float32 Microsoft.Xna.Framework.Vector2::X
  IL_0015:  br.s       IL_001d
  IL_0017:  ldarg.0
  IL_0018:  ldfld      float32 Microsoft.Xna.Framework.Vector2::X
  IL_001d:  stfld      float32 Microsoft.Xna.Framework.Vector2::X
  IL_0022:  ldarg.2
  IL_0023:  ldarg.0
  IL_0024:  ldfld      float32 Microsoft.Xna.Framework.Vector2::Y
  IL_0029:  ldarg.1
  IL_002a:  ldfld      float32 Microsoft.Xna.Framework.Vector2::Y
  IL_002f:  blt.s      IL_0039
  IL_0031:  ldarg.1
  IL_0032:  ldfld      float32 Microsoft.Xna.Framework.Vector2::Y
  IL_0037:  br.s       IL_003f
  IL_0039:  ldarg.0
  IL_003a:  ldfld      float32 Microsoft.Xna.Framework.Vector2::Y
  IL_003f:  stfld      float32 Microsoft.Xna.Framework.Vector2::Y
  IL_0044:  ret
} // end of method Vector2::Min
</code>

y

<code>.method public hidebysig static valuetype Microsoft.Xna.Framework.Vector2 
        Min(valuetype Microsoft.Xna.Framework.Vector2 value1,
            valuetype Microsoft.Xna.Framework.Vector2 value2) cil managed
{
  // Code size       80 (0x50)
  .maxstack  3
  .locals init (valuetype Microsoft.Xna.Framework.Vector2 V_0)
  IL_0000:  ldloca.s   V_0
  IL_0002:  ldarga.s   value1
  IL_0004:  ldfld      float32 Microsoft.Xna.Framework.Vector2::X
  IL_0009:  ldarga.s   value2
  IL_000b:  ldfld      float32 Microsoft.Xna.Framework.Vector2::X
  IL_0010:  blt.s      IL_001b
  IL_0012:  ldarga.s   value2
  IL_0014:  ldfld      float32 Microsoft.Xna.Framework.Vector2::X
  IL_0019:  br.s       IL_0022
  IL_001b:  ldarga.s   value1
  IL_001d:  ldfld      float32 Microsoft.Xna.Framework.Vector2::X
  IL_0022:  stfld      float32 Microsoft.Xna.Framework.Vector2::X
  IL_0027:  ldloca.s   V_0
  IL_0029:  ldarga.s   value1
  IL_002b:  ldfld      float32 Microsoft.Xna.Framework.Vector2::Y
  IL_0030:  ldarga.s   value2
  IL_0032:  ldfld      float32 Microsoft.Xna.Framework.Vector2::Y
  IL_0037:  blt.s      IL_0042
  IL_0039:  ldarga.s   value2
  IL_003b:  ldfld      float32 Microsoft.Xna.Framework.Vector2::Y
  IL_0040:  br.s       IL_0049
  IL_0042:  ldarga.s   value1
  IL_0044:  ldfld      float32 Microsoft.Xna.Framework.Vector2::Y
  IL_0049:  stfld      float32 Microsoft.Xna.Framework.Vector2::Y
  IL_004e:  ldloc.0
  IL_004f:  ret
} // end of method Vector2::Min
</code>

Parece que la sobrecarga es causada por el vector temp. También probé 1GHz WP 7.5 dispositivo:

19791677

Número de tics para un orden de magnitud menor número de iteraciones.

Supongo que elreal la pregunta es, "no debería .NET hacer RVO?" Mehrdad

Tu respuesta

2   la respuesta
7

lo que significa que cuando se devuelve como un valor se devuelve una copia, en lugar de devolver una referencia a una estructura existente. Al usar los parámetros ref / out, puede evitar esta copia, de modo que el Vector creado en el método Mín es el vector exacto en suresult variable.

Es una de esas micro optimizaciones que normalmente se desalientan, pero en el mundo del juego se realiza con suficiente frecuencia, y en entornos donde el rendimiento es lo suficientemente importante, vale la pena la opción un poco menos legible.

¡Gracias! Parece que el beneficio es justificable. 10% a casi el 50% de aceleración. Lukasz Madon
@PeterRitchie Vector2 es una estructura mutable. Puedes crear uno nuevo y asignarlo aresult (Una operación trivial para una estructura) y luego modifíquela en función de la lógica del método. Servy
Asignar un tipo de valor a un parámetro out / ref también es "copiar" el valor de la variable local al parámetro. Peter Ritchie
Todavía está asignando un valor de una variable a otra (el parámetro). La asignación es una operación de "copia"; Mientras esté asignando un tipo de valor a una variable (independientemente del tipo), está "copiando" el valor. Peter Ritchie
@PeterRitchie El punto completo sería que el parámetro out / ref se usaría como variable local en todo el método para que no tenga la copia de la variable local al parámetro out / ref. De lo contrario, sí, estaría realizando una copia adicional y anularía el propósito. Servy
3

s la capacidad de tener múltiples valores de "retorno": en lugar de devolverlos de la forma habitual, los enumera como parámetros ref / var.

@lukas - en este caso, sí, estaba escribiendo para el caso general :) Attila
Eso es lo que pensé primero, pero solo me devuelven 1 valor. Lukasz Madon

Preguntas relacionadas