10

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

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>

4

4 回答 4

9

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


}
于 2012-09-06T07:30:55.937 回答
4

那么,什么是更快的 DIY 拳击IntObj或内置拳击?

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

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

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

于 2012-09-06T07:08:14.840 回答
2

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

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

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

于 2012-09-06T07:28:50.733 回答
0

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

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

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

于 2012-09-06T07:15:43.480 回答