我正在通过C#阅读'CLR'并且在回顾'运行时的事物如何关联'以及'原始,参考和值类型'时,我感到有些困惑。 如果我从Main获得以下代码 -
void DoSomething(int x)
{
int m = x/2;
int n = SomeMethod1(m);
n = (n * 2) + x;
int k = SomeMethod2(n);
m += (k*3);
}
代码没有任何用处,但我只是想了解本地整数变量和内存分配的行为。
我理解m,n和k驻留在堆栈上? 现在,对于最后一行(我忽略了变量n),必须修改'm'的值。那么必须弹出堆栈中的其他所有内容并更新“m”的值?与堆上装箱和拆箱的开销相比,这可以忽略不计吗?如果堆叠高度更高,它会变得重要吗?
P.S:从讨论中排除GC(用于装箱和拆箱),这绝对是一种开销,而且这只是试图理解行为,可能/可能不是实际场景。
答案 0 :(得分:1)
不,没有人说m,n或k应该在堆栈上。最有可能的是,他们会在登记册中,因为你甚至不使用相互依赖,他们甚至不必同时在那里。 JIT编译器非常擅长运行时优化,除了方法参数(或它们的引用)之外,堆栈几乎不用于任何东西。 GC在寄存器或堆栈中都没有电源 - 堆栈的释放方式与本机代码相同。只有堆是GC的域。
这是一个常见的误解,因为在IL(中间语言C#编译成)中,实际上使用堆栈所有内容。但是,这是不在您的机器上执行的内容 - IL代码由JIT编译器再次编译。当然,JIT编译器也可以自由地在堆栈上执行所有操作,但这很愚蠢。简单的示例时间:
x + y
将编译为此IL(伪代码):
push y
push x
op_Add
因此,对于一个简单的整数加法,它需要进行一个方法调用并且需要两次推送才能进行堆栈。不是非常昂贵,但如果您正在尝试进行任何严肃的计算,那么您就会遇到麻烦。
但是,JIT编译器会产生更像这样的东西:
add ax, bx
它非常聪明,所以它实际上经常使用寄存器甚至是方法参数 - 如果它是安全的。
但请注意,所有这些只是一个实现细节 - 在这种情况下,是性能优化。一个整数可能很容易存在于堆上(事实上,如果它是盒装的,或者是堆上另一个对象的一部分),它就会存在。
所以,显然,最快的是CPU内部支持的东西 - 如上例所示,将两个寄存器一起添加。很快。
使用堆栈通常仍然非常快。并不是对堆的分配是昂贵的 - 实际上,堆和堆栈分配的成本大致相同。它会导致重新分配 - 堆需要进行垃圾收集和压缩,而堆栈只需要更改一个指针。
哦,你误解了你可以访问堆栈的方式。是的,有基本的push
和pop
指令,但这些指令不是唯一的方式。您可以像处理任何其他内存一样直接寻址堆栈的内存。事实上,这就是为什么你可以看到堆栈指针(如ESP)的全部观点。
答案 1 :(得分:0)
该代码中的每个变量都是可变的并且是堆栈!
如果您想了解哪些不变性,请阅读Eric Lippert关于不变性的帖子:
之间会有细微差别
m = m + (k*3);
m += (k*3);
区别在于第二行更好,因为没有创建L1 / L2缓存中的临时注册表元素(m +(k * 3)),并且值被直接推回到变量值槽中。使其快3-12个循环(即小于1毫秒的1%)。尽量专注于重要的事情。