【问题标题】:.NET boxing / unboxing vs casting performance.NET 装箱/拆箱与铸造性能
【发布时间】:2012-09-06 07:05:36
【问题描述】:

我试图从性能角度了解两种解决方案中的哪一种更受欢迎。 例如,我有两段代码:

1) 装箱/拆箱

int val = 5;
Session["key"] = val; 
int val2 = (int)Session["key"];

2) 强制转换(IntObj 有 int Value 属性来存储 int)

IntObj val = new IntObj(5);  
Session["key"] = val;
int val2 = ((IntObj )Session["key"]).Value;

这些示例之间的内存管理区别是什么? 是否有更快的方法来执行此类操作?

注意:Session 只是举例,它可以是任何Dictionary<string, object>

【问题讨论】:

  • 我没有测量它,但我会说原语会更快。一个对象有开销。但在这种情况下,我认为可读性是一个更重要的衡量标准。 CPU 周期比大脑周期便宜很多

标签: c# .net performance


【解决方案1】:

看起来你在这里真正做的是比较手动拳击和内置拳击。内置拳击已经过高度优化 - 所以我不希望在这里看到巨大的差异,但我们可以检查一下。重要的是,请注意两者具有相同的内存影响:一个堆对象包含一个 int 字段,每个 int 装箱/包装。

以下显示了两个接近的时间几乎相同;因此,我会说,只需直接/内置方式将其装箱即可。

注意:在发布模式下运行它,无需调试器(最好在命令行中)。请注意,第一次调用是为了预先 JIT 一切。

using System;
using System.Diagnostics;
public sealed class IntObj
{
    public readonly int Value;
    public IntObj(int value)
    {
        Value = value;
    }
}
static class Program
{
    static void Main()
    {
        Run(1, 0, false);
        Run(100000, 500, true);
        Console.ReadKey();
    }
    static void Run(int length, int repeat, bool report)
    {
        var data = new object[length];

        int chk = 0;
        var watch = Stopwatch.StartNew();
        for (int j = 0; j < repeat; j++)
        {
            for (int i = 0; i < data.Length; i++)
            {
                data[i] = i;
                chk += i;
            }
        }
        watch.Stop();
        if(report) Console.WriteLine("Box: {0}ms (chk: {1})", watch.ElapsedMilliseconds, chk);
        chk = 0;
        watch = Stopwatch.StartNew();
        for (int j = 0; j < repeat; j++)
        {
            for (int i = 0; i < data.Length; i++)
            {
                chk += (int) data[i];
            }
        }
        watch.Stop();
        if (report) Console.WriteLine("Unbox: {0}ms (chk: {1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int j = 0; j < repeat; j++)
        {
            for (int i = 0; i < data.Length; i++)
            {
                data[i] = new IntObj(i);
                chk += i;
            }
        }
        watch.Stop();
        if (report) Console.WriteLine("Wrap: {0}ms (chk: {1})", watch.ElapsedMilliseconds, chk);
        chk = 0;
        watch = Stopwatch.StartNew();
        for (int j = 0; j < repeat; j++)
        {
            for (int i = 0; i < data.Length; i++)
            {
                chk += ((IntObj)data[i]).Value;
            }
        }
        watch.Stop();
        if (report) Console.WriteLine("Unwrap: {0}ms (chk: {1})", watch.ElapsedMilliseconds, chk);
    }


}

【讨论】:

  • 谢谢!现在很容易看到:)
【解决方案2】:

那么,使用IntObj 的 DIY 拳击或内置拳击哪个更快?

我的猜测是内置的品种。很有可能两个编译器都经过优化来处理它。

是否存在更“快速”的方式来执行此类操作?

对于大型数据集,首选方法始终是避免使用它。对于小型套装来说,这根本不重要。

【讨论】:

  • 我只是举了一个Session,它可能是一个简单的Dictionary/
  • 我认为问题是关于 IntObj。容器(Session)不是很相关。
【解决方案3】:

做某事最快的方法就是不做。尝试重构数据以避免大量装箱,从而同时获得更高的类型安全性、可读性和潜在性能。

我发现您不太可能需要在无类型字典中存储大量不相关的整数(或其他值类型)元素。通常值被组织成一些一起有意义的对象,在这种情况下,您将顶级对象存储在无类型字典中,并且只需要一次强制转换。对于更深层次的元素,您可以使用强类型类(如Dictionary&lt;string,int&gt;),因为不需要装箱,所以这个问题已经得到解决。

如果您觉得在您的情况下确实需要在 string=>opbject 映射中存储大量 int(或其他值类型元素),那么使用 your 自己执行测量应该非常容易数据集和你的目标,看看这两个版本是否有显着的好处。如果两者都满足您的目标(很可能) - 选择一个生成最易读代码的代码(即对我来说它将是第一个变体)。

【讨论】:

    【解决方案4】:

    我对 C# cast 运算符生成的不同类型的 IL 指令进行了分类:

    装箱(装箱 IL 指令)和拆箱(拆箱 IL 指令) 通过继承层次进行强制转换(如 C++ 中的 dynamic_cast,使用 castclass IL 指令进行验证) 原始类型之间的转换(如 C++ 中的 static_cast,有大量 IL 指令用于原始类型之间的不同类型的转换) 调用用户定义的转换运算符(在 IL 级别,它们只是对相应 op_XXX 方法的方法调用)。

    不同之处在于,转换会在创建新引用类型时分配额外的内存。

    【讨论】:

    • 那如何回答这个问题? (装箱还会在堆上创建一个新实例)
    猜你喜欢
    • 2012-07-15
    • 1970-01-01
    • 1970-01-01
    • 2011-02-28
    • 1970-01-01
    • 1970-01-01
    • 2012-03-22
    • 1970-01-01
    相关资源
    最近更新 更多