【问题标题】:What is the benefit of using out/ref versus returning?使用 out/ref 与返回相比有什么好处?
【发布时间】:2012-05-10 20:20:38
【问题描述】:

我正在使用 XNA 框架制作游戏,所以我使用了很多对向量进行操作的函数。 (尤其是Vector2(64 位结构))。困扰我的是大多数方法都是用 ref 和 out 参数定义的。这是一个例子:

void Min(ref Vector2 value1, ref Vector2 value2, out Vector2 result)

我也觉得有点奇怪。还有一个更明显的Min

public static Vector2 Min(Vector2 value1, Vector2 value2);

基本上,几乎所有函数都有refs 和outs 的重载。类似,其他APIs

这种设计有什么好处? XNA 针对性能进行了优化,这会是结果吗?比如说,四元数需要 128b,其中通过 ref 较少。

编辑:

这是一个测试代码:

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

结果:

  • refOut1 2200
  • refOut2 1400

Win 7 64 位,.Net 4. XNA 4.0

也是IL代码

.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

.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

似乎开销是由临时向量引起的。我还尝试了 1GHz WP 7.5 设备:

  • 1979
  • 1677

迭代次数少一个数量级的刻度数。

【问题讨论】:

  • 我想真正的问题是,“.NET 不应该做 RVO 吗?”

标签: c# .net api xna


【解决方案1】:

Vector2 是一个结构,这意味着当它作为值返回时,会返回一个副本,而不是返回对现有结构的引用。通过使用 ref/out 参数,您可以避免此副本,以便在 Min 方法中创建的 Vector 与您的 result 变量中的向量完全相同。

这是通常会被劝阻的那些微优化之一,但在游戏世界中它已经足够频繁地完成,并且在性能足够重要的环境中,它值得稍微不那么可读的选项。

【讨论】:

  • 将值类型分配给 out/ref 参数也是将值从局部变量“复制”到参数。
  • @PeterRitchie 整点是 out/ref 参数将在整个方法中用作局部变量,这样您就没有从局部变量到 out/ref 参数的副本。否则,是的,您将执行额外的复制,这会破坏目的。
  • 您仍在将一个变量的值分配给另一个变量(参数)。赋值是“复制”操作;只要您将值类型分配给变量(无论类型如何),您就是在“复制”该值。
  • @PeterRitchie Vector2 是一个可变结构。您可以新建一个并将其分配给result(结构的简单操作),然后根据方法中的逻辑对其进行修改。
  • 谢谢!看来,好处是有道理的。 10% 到近 50% 的加速。
【解决方案2】:

Servy 提到的性能效率之外的另一个区别是能够有多个“返回”值:不是以通常的方式返回它们,而是将它们列为 ref/var 参数。

【讨论】:

  • 这是我的第一个想法,但它们只返回 1 个值。
  • @lukas - 在这种情况下,是的,我写的是一般情况:)
猜你喜欢
  • 2012-05-05
  • 2020-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-22
  • 2014-12-06
  • 1970-01-01
相关资源
最近更新 更多