最后我想我想通了......这是我找到的答案。如果我犯了错误,请告诉我。
运行时是否真的为每个装箱/拆箱操作复制堆上的数据,还是使用引用计数等技巧?
public void Test6()
{
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
object[] myarr = new object[1024 * 1024];
long mem1 = GC.GetTotalMemory(true);
int a = 1;
for (int i = 0; i < myarr.Length; ++i)
{
myarr[i] = a;
}
long mem2 = GC.GetTotalMemory(true);
Console.WriteLine("Total memory usage is {0} bytes", mem2 - mem1);
// Make sure we use it so that the JIT doesn't optimize our code
int sum = 0;
for (int i = 0; i < myarr.Length; ++i)
{
sum += (int)myarr[i];
}
Console.WriteLine("Sum = {0}", sum);
}
其结果是 x86 的 12582912 - 这是成熟对象的行为:4x1M int、4x1M 类型引用和存储在数组中的 4x1M 指针。答:它只是复制到堆中。
这使得运行时不太可能使用不同的 IMO 规则。
- 内联代码时,运行时 IL 是否优化了装箱/拆箱操作,或者那是不可能的?如果可能的话,你能“帮助”一下 JIT 编译吗?
看来不是。尝试:
private object IntBox1()
{
return 1;
}
private int IntNotBox1()
{
return 1;
}
public int Total1()
{
int sum = 0;
for (int i = 0; i < 100000000; ++i)
{
sum += (int)IntBox1();
}
return sum;
}
public int Total2()
{
int sum = 0;
for (int i = 0; i < 100000000; ++i)
{
sum += IntNotBox1();
}
return sum;
}
时间上有显着差异,所以没有。我还没有找到一种方法来帮助运行时优化装箱/拆箱。如果有人找到任何方法使运行时优化开箱/拆箱操作,请分享。
- 由于值类型和类类型都派生自对象,并且装箱值应该是类类型,我想知道装箱值类型的 vtable 查找是否不同于值类型的 vtable 查找?
情况似乎是这样:对值类型的 vtable 查找要快得多。
public void Test4()
{
int a = 1;
object oa = a;
Stopwatch sw = new Stopwatch();
sw.Start();
int sum = 0;
for (int i = 0; i < 100000000; ++i)
{
sum += a.GetHashCode();
}
Console.WriteLine("Calc {0} took {1:0.000}s", sum, new TimeSpan(sw.ElapsedTicks).TotalSeconds);
sw = new Stopwatch();
sw.Start();
sum = 0;
for (int i = 0; i < 100000000; ++i)
{
sum += oa.GetHashCode();
}
Console.WriteLine("Calc {0} took {1:0.000}s", sum, new TimeSpan(sw.ElapsedTicks).TotalSeconds);
}
我现在认为这与两个原因有关。
- 性能和内存大小。int 的开销?是 4 个字节,而装箱值的开销在 x86 上是 4 个字节,在 x64 上是 8 个字节。这意味着int?作为对象复制更快或同样快。通过方法复制时的开销是相同的。同样使用值类型的 vtable 查找要快得多。
- 兼容性。装箱对象的类型 = 未装箱对象的类型。对于整数?你想要一个不同的类型而不破坏与旧版本代码的兼容性。改变int?反对需要语言支持并打破依赖这些类型的旧版本相同。
结论:装箱确实总是将值类型复制到堆中,它只是一个普通对象。您可能注意到的唯一奇怪的事情是对象中的类型引用是对未装箱值的原始(值)类型的类型引用。我找不到任何证据表明装箱值不是存在于堆上的“普通类类型”对象。