【问题标题】:Delegating value arguments to functions that accept ref arguments将值参数委托给接受 ref 参数的函数
【发布时间】:2014-10-01 16:31:12
【问题描述】:

为了减少我正在开发的库的维护,我试图将类似的功能委托给单个函数。例如,假设一个有两个分量向量,其中 Add 函数接受 by-ref args 和其他接受 by-value args。这个想法是在按值函数中简单地调用 by-ref 函数,这样就只需要维护 by-ref 函数。

struct Vector2
{
    public float X;
    public float Y;

    public Vector2(float x, float y)
    {
        this.X = x;
        this.Y = y;
    }

    public static void Add(ref Vector2 a, ref Vector2 b, out Vector2 result)
    {
        result.X = a.X + b.X;
        result.Y = a.Y + b.Y;
    }
    public static Vector2 Add1(Vector2 a, Vector2 b)
    {
        Add(ref a, ref b, out a);
        return a;
    }
    public static Vector2 Add2(Vector2 a, Vector2 b)
    {
        a.X += b.X;
        a.Y += b.Y;
        return a;
    }
}

问题是 by-ref 重载函数没有内联,导致我认为代码变慢(不包括 nops)。

启用 JIT 优化的发布输出:

添加1:

          Add(ref a, ref b, out a);
0000002b  lea         eax,[ebp+10h] 
0000002e  push        eax 
0000002f  lea         ecx,[ebp+10h] 
00000032  lea         edx,[ebp+8] 
00000035  call        FFEDA508 
0000003a  nop 
          return a;
0000003b  lea         edi,[ebp-44h] 
0000003e  lea         esi,[ebp+10h] 
00000041  movq        xmm0,mmword ptr [esi] 
00000045  movq        mmword ptr [edi],xmm0 
00000049  nop 
0000004a  jmp         0000004C

添加2:

          a.X += b.X;
0000002b  fld         dword ptr [ebp+8] 
0000002e  fadd        dword ptr [ebp+10h] 
00000031  fstp        dword ptr [ebp+10h] 
          a.Y += b.Y;
00000034  lea         eax,[ebp+8] 
00000037  fld         dword ptr [eax+4] 
0000003a  lea         eax,[ebp+10h] 
0000003d  fadd        dword ptr [eax+4] 
00000040  fstp        dword ptr [eax+4] 
          return a;
00000043  lea         edi,[ebp-44h] 
00000046  lea         esi,[ebp+10h] 
00000049  movq        xmm0,mmword ptr [esi] 
0000004d  movq        mmword ptr [edi],xmm0 
00000051  nop 
00000052  jmp         00000054

有没有办法让对 Add 的调用被内联?

请注意,该库需要 .NET 4.0,这意味着无法使用激进的内联。

【问题讨论】:

  • 对于 SO 的新手(可能还有很多普通用户),这是一篇经过深入研究的帖子!很高兴有你在这里!
  • -Patrick 谢谢,但我实际上不是第一次使用(我早就忘记了我原来的帐户详细信息)。 -DMAN 积极内联在 4.0 中不可用,这是当前的最低要求。
  • 请在您的问题中提及此要求,以防止误解。删除了我的标志。

标签: c# struct ref


【解决方案1】:

尝试启用aggressive inlining

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Add(ref Vector2 a, ref Vector2 b, out Vector2 result)
{
    result.X = a.X + b.X;
    result.Y = a.Y + b.Y;
}

它提示编译器(或 JIT'er)内联该函数。

注意:AggressiveInlining 是 .NET 4.5 中的新功能。

【讨论】:

  • 注意:AggressiveInlining 是 .Net4.5 中的新内容。
【解决方案2】:

我会大胆猜测一下,您可能是一名 C++ 程序员。您在此处使用的refout 关键字完全超流畅。您可以删除它们,您的代码将运行良好。

事实上,我没有看到任何简单版本无法提供的功能:

public Vector2 Add(Vector2 other)
{
    return new Vector2() { X = this.X + other.X, Y = this.Y + other.Y };
}

即使您想保持 API 静态,删除所有 refout 关键字,它们对于您的代码也不是必需的。

编辑:

我刚刚注意到您使用的是结构(值类型),所以我之前写的内容不正确,因此我删除了我的帖子。然后我又想了想,我想知道:要么你想让它成为一个值类型,因为它太小以至于复制速度足够快,或者你不想要。您将其设为值类型,现在您正试图通过滥用refout 关键字来解决您自己的决定。

public static void Add(ref Vector2 a, ref Vector2 b, out Vector2 result)

这很容易

public static void Add(Vector2 a, Vector2 b, ref Vector2 result)

如果您想要按引用传递,为什么不首先将其设为引用类型呢?如果你这样做了,那么我上面写的一切仍然有效:)

【讨论】:

  • 该代码在物理处理中被大量使用,在可能的情况下通过引用语义时会大大加快速度(请注意,还有 Vector3/Vector4/larger 结构不适合单个机器字)。按值重载存在于没有副本就无法通过引用传递的地方,例如在传递属性时。
  • @user3446285 那你为什么一开始就把它变成一个值类型呢?在您的应用程序的其他地方使用结构体是否更快?
  • 该库的主要目标之一是减少频繁的堆分配,从而减少垃圾和随后的收集周期。结构对此有很大帮助。
  • @user3613916 确实:)
  • 大型事物不应该是值类型的想法源于事物通常按值传递的概念。与可变类类型数组相比,较大的暴露字段值类型数组提供了更好的缓存局部性,当人们可以避免语义上不必要的复制时
【解决方案3】:

我只是硬着头皮手动内联代码,其中内联不会自动完成并且调用指令会很重要,例如在像 Add1 这样的函数中。在这种情况下,自动化单元测试可以处理问题检测,因此维护噩梦不是一个大问题。

感谢SO的帮助。

【讨论】:

    猜你喜欢
    • 2010-12-25
    • 2016-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多