拳击会导致性能问题吗?我怎样才能防止拳击?

Does boxing cause performance issues? How can I prevent boxing?

装箱会导致我的代码出现性能问题吗?如何防止拳击?

void Main()
{
    AreEqual<int>(12, 13);
}

public static bool AreEqual<T>(T a, T b)
{
    return a.Equals(b);
}

IL:

IL_0000:  nop         
IL_0001:  ldc.i4.s    0C 
IL_0003:  ldc.i4.s    0D 
IL_0005:  call        UserQuery.AreEqual
IL_000A:  pop         
IL_000B:  ret         

AreEqual:
IL_0000:  nop         
IL_0001:  ldarga.s    00 
IL_0003:  ldarg.1     
IL_0004:  box         01 00 00 1B 
IL_0009:  constrained. 01 00 00 1B 
IL_000F:  callvirt    System.Object.Equals
IL_0014:  stloc.0     // CS[=11=]00
IL_0015:  br.s        IL_0017
IL_0017:  ldloc.0     // CS[=11=]00
IL_0018:  ret       

有时会,通常不会。

您无法通过这种方式真正预测性能问题。您是否有一个太慢的特定程序并且您怀疑问题出在拳击上?

请参阅此 hidden feature,它可以用作解决方法。但是 - 一如既往 - 确保这不是过早的优化。

示例代码:

   static void foo<T>(ref T value) {
        //This is the ONLY way to treat value as bool, without boxing/unboxing objects
        if(value is bool) {
            TypedReference reference = __makeref(value); //get reference
            bool boolVal = __refvalue(reference,bool);   //get primitive value
            __refvalue(reference, bool) = !boolVal;      //set primitive value
        } else {
            value = default(T);
        }
    }

我从 this thread

中提取了部分代码

Does Boxing cause performance issues in my code?

只有你能回答。这是你的代码,这是你的代码的性能。对我来说,这不是问题,对你来说,可能是。

How can I prevent boxing?

通过强制编译器选择正确的重载。您的初始代码调用 object.Equals(object)(在 Int32.Equals(object) 中被覆盖),因为这是唯一适用于每个无约束 T 的方法,它需要对您传递的 int 参数进行装箱。

Int32.Equals(Int32) implements IEquatable<T>.Equals(T),所以把它作为约束:

private static bool AreEqualEquatable<T>(T a, T b)
    where T : IEquatable<T>
{
    return a.Equals(b);
}

编译成这个 IL:

IL_0000: nop
IL_0001: ldarga.s a
IL_0003: ldarg.1
IL_0004: constrained. !!T
IL_000a: callvirt instance bool class [mscorlib]System.IEquatable`1<!!T>::Equals(!0)
IL_000f: stloc.0
IL_0010: br.s IL_0012

IL_0012: ldloc.0
IL_0013: ret

因为编译器会尝试找到最专业的重载,在本例中 IEquatable<T>.Equals(T)

计算机为执行任务所做的任何额外操作都会降低性能,性能下降是一回事,性能问题是另一回事。如果你试图实现一个包含数百万数据的高速数据结构,它可能会导致性能问题,但如果你只是编写一个在数据库和网络上执行许多查询的信息系统,那么我认为装箱不会导致性能问题给你。

您应该始终对您的应用程序进行概要分析,看看哪些部分会影响性能。

因此,如果您尝试比较两个整数,则操作是 (a == b)。如果您为此编写函数,则需要额外的函数调用。如果您添加额外的泛型方法、匿名类型、装箱、拆箱...那么所有这些额外的操作都会降低性能。

如前所述您可以防止整数装箱。

这是一个基准

private static void Main(string[] args)
{
    var sw1 = new Stopwatch();
    bool b1 = true;
    sw1.Start();
    for (int i = 0; i < 10 * 1000 * 1000; i++)
    {
        b1 = b1 ^ AreEqual(i, i + 1);
    }
    sw1.Stop();
    Console.WriteLine(b1);
    Console.WriteLine(sw1.ElapsedTicks);


    var sw2 = new Stopwatch();
    bool b2 = true;
    sw2.Start();
    for (int i = 0; i < 10 * 1000 * 1000; i++)
    {
        b2 = b2 ^ AreEqualEx(i, i + 1);
    }
    sw2.Stop();
    Console.WriteLine(b2);
    Console.WriteLine(sw2.ElapsedTicks);
}

public static bool AreEqual<T>(T a, T b)
{
    return a.Equals(b);
}

public static bool AreEqualEx<T>(T a, T b) where T:IEquatable<T>
{
    return a.Equals(b);
}

结果是

True
254379
True
35514