Pergunta sobre c#, xna, .net, api – Qual é o benefício de usar out / ref versus return?

9

Eu estou fazendo um jogo usando o framework XNA, então eu uso muitas funções que operam em vetores. (especialmenteVector2 (Estrutura de 64 bits)). O que me incomoda é que a maioria dos métodos é definida com parâmetros ref e out. Aqui está um exemplo:

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

que parece um pouco estranho também eu. Há também outroMin o que é mais óbvio

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

Basicamente, quase todas as funções têm sobrecargasrefareiaouts. Similar, outroAPIs.

Qual é o benefício desse design? XNA é otimizado para desempenho, poderia ser um resultado? Diga, Quaternion requer 128b onde passando por ref menos.

EDITAR:

Aqui está um código de teste:

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

Os resultados:

refOut1 2200refOut2 1400

Ganhe 7 64bit, .Net 4. XNA 4.0

Também 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>

e

<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 a sobrecarga é causada pelo vetor temp. Também tentei dispositivo de 1GHz WP 7.5:

19791677

Número de ticks para uma ordem de grandeza menor número de iterações.

Eu acho que oreal pergunta é, "não deveria .NET fazer RVO?" Mehrdad

Sua resposta

2   a resposta
7

o que significa que quando é retornado como um valor, uma cópia é retornada, em vez de retornar uma referência a uma estrutura existente. Ao usar os parâmetros ref / out, você pode evitar essa cópia para que o Vetor criado no método Min seja o vetor exato em seuresult variável.

É uma daquelas micro-otimizações que normalmente seriam desencorajadas, mas no mundo do jogo isso é feito com bastante frequência, e em ambientes onde o desempenho é suficiente, vale a pena a opção um pouco menos legível.

Você ainda está atribuindo um valor de uma variável para outra (o parâmetro). A atribuição é uma operação de "cópia"; Contanto que você esteja atribuindo um tipo de valor a uma variável (independentemente do tipo), você está "copiando" o valor. Peter Ritchie
@PeterRitchie Vector2 é uma estrutura mutável. Você pode criar um novo e atribuí-lo aresult (Uma operação trivial para uma estrutura) e, em seguida, modifique-a com base em qualquer que seja a lógica do método. Servy
Atribuir um tipo de valor a um parâmetro out / ref também é "copiar" o valor da variável local para o parâmetro. Peter Ritchie
Obrigado! Parece que o benefício é justificável. 10% a quase 50% de aceleração. Lukasz Madon
@PeterRitchie O ponto inteiro seria que o parâmetro out / ref seria usado como a variável local em todo o método, para que você não tivesse a cópia da variável local para o parâmetro out / ref. Caso contrário, sim, você estaria realizando uma cópia adicional e isso frustraria o propósito. Servy
3

ervy é a capacidade de ter vários valores de "retorno": em vez de retorná-los da maneira usual, você os lista como parâmetros ref / var.

Que o meu primeiro pensamento, mas eles só retornam 1 valor. Lukasz Madon
@lukas - neste caso, sim, eu estava escrevendo para o caso geral :) Attila

Perguntas relacionadas