做(DSP)编译器的,看到楼主的疑问特地来回复一下,可能说的是DSP架构的情况,但是应该区别不会太大。
通常来说,寄存器传参只要一条mov指令,而压栈在父函数里面要push,在子函数要pop,光是从指令数量上来说,寄存器传参就要有点优势(可能不大);
其次,从硬件角度考虑,内存传输通道的延时比寄存器大,而大多数运算指令都是以寄存器为基础的,压栈的数据最终都要读到寄存器上进行操作。从汇编上看到的情况可能是mov和pop的区别,但是实际执行过程中,可能mov需要1个机器周期,pop需要5个机器周期,做pop的时间足够做5条其他指令的运算了。
至于factorial(n) --> factorial(n-1)的例子,在编译器启动优化时,会做 reg = reg - 1 这样的操作,然后把new reg作为传参寄存器的。如果不停的出现计算后压栈的操作,可能是因为没启动编译器的优化功能(GCC的话,-O2可能才有效果,默认O0下对所有变量都会压栈)。
Crofevil 发表于 2015-02-11 14:38 static/image/common/back.gif
做(DSP)编译器的,看到楼主的疑问特地来回复一下,可能说的是DSP架构的情况,但是应该区别不会太大。
...
PC上的问题比DSP严重多了,以x64为例,
假设普通64位加法指令addq消耗一个时钟周期
那么,带进位64位加法adcq最少2时钟
64位乘法mulq要4时钟
MOVQ 寄存器 -> 内存,L1命中大约20时钟
MOVQ 寄存器 -> 内存,L1未命中100时钟起步
对寄存器很多的处理器,寄存器传参很有优势。
对X86这样寄存器少得可怜的,还是用栈吧!
X86_64则增加了不少寄存器,所以,也。。。 本帖最后由 myzhzygh 于 2015-07-11 12:28 编辑
实在看不下去了,寄存器就是CPU和主存之前的一个数据缓存,栈是在函数调用过程中帮助保存中间数据的一段内存。
在上面提到的这种递归调用中,因为函数中没有太多访问参数的代码,只是简单的进行-1操作作为下一次调用的参数,
这个过程中寄存器传递参数只是在调用新的函数之前,不需要把callee的参数压到栈上,你看到的压栈,是当前栈帧函
数自己保存自己的参数,因为保存当前调用参数的寄存器要被用于保存callee的参数,所以自己不能再占着寄存器,
所以作为中间数据保存在当前栈帧上。对于你提到的例子,其实就是少了一次压栈操作。也就少占用一个内存单元。
编译器并不是想象的那么智能,要遵循规则,所以其实这些递归调用的参数都是同一个寄存器在装载。 myzhzygh 发表于 2015-07-11 12:24 static/image/common/back.gif
实在看不下去了,寄存器就是CPU和主存之前的一个数据缓存,栈是在函数调用过程中帮助保存中间数据的一段内存 ...[/quote]
最苦恼的是系统函数:
mkcontext(),源码是32位的,处理的栈帧。64位不知道怎么办了,不知道弄哪个寄存器。 楼主的问题是既然大部分函数还调用了其它函数,那么穿参的寄存器仍然需要入栈来保存其内容,寄存器传参相比栈穿参没什么优势。
实际上在编译器完全不开优化的情况下确实会把传参寄存器入栈,再从栈上取值来完成对参数的计算/处理,这么看貌似跟直接用栈传参没什么区别;但是开了优化时会避免把只使用1次的参数入栈/出栈(因为丢了没影响,gdb看此类“一次性”参数只能看到<optimized out>),这样能提升性能。 farseeraliens 发表于 2017-02-02 14:38
楼主的问题是既然大部分函数还调用了其它函数,那么穿参的寄存器仍然需要入栈来保存其内容,寄存器传参相比 ...
除了有的参数只用到一次因而不需要保存也不需要恢复的情况,另外比如c++程序函数成员互相调用,由于this指针是用rdi传递的,只有最外面一帧把对象地址赋给rdi,其它地方不需要修改rdi,这就节约了若干次入栈、出栈。
页:
1
[2]