技术问询:为何传值(pass by value)有时比传引用(pass by reference)更好
为什么简单对象传值反而比传引用更高效?
这问题问得太到位了!很多开发者刚接触性能优化时,都会默认“传引用肯定比传值省资源”,但Casey在Handmade Hero里提到的这个点,其实是底层性能优化里非常核心的缓存局部性问题,今天就掰开揉碎讲清楚。
先搞懂两个底层核心逻辑
- 缓存局部性的威力:CPU的L1/L2缓存访问速度比主存快100倍左右,而且它会优先加载最近访问过的内存区域(比如栈空间)。如果数据不在缓存里,就会触发缓存未命中,CPU要等待主存把数据读进来,这中间的延迟足以让CPU空转几十个周期。
- 传引用 vs 传值的本质差异:
- 传引用本质上是传递一个指针(通常4/8字节),函数内部必须通过这个指针去主存寻址实际对象。如果这个对象刚好不在缓存里,那缓存未命中的延迟会直接拖慢整个函数的执行。
- 传值是直接把对象的内容拷贝到函数的栈帧里(如果对象足够小,甚至能直接放到CPU寄存器里)。栈是CPU缓存命中率极高的区域,访问几乎没有延迟。
什么时候传值绝对更优?
当对象的大小小于等于2倍指针长度(比如int、float,或者包含2-3个基础类型的小struct)时,拷贝的成本(只需要几个CPU周期)远低于一次缓存未命中的成本(几十到上百个周期)。举个实际的代码例子:
// 8字节的小结构体(两个int) typedef struct { int x; int y; } Point; // 传值:直接把Point的两个int拷贝到栈,函数内直接访问 void update_point_by_value(Point p) { p.x += 10; p.y += 10; } // 传引用:传递8字节指针,函数内需要解引用寻址 void update_point_by_ref(Point *p) { p->x += 10; p->y += 10; }
调用update_point_by_value时,Point的内容直接在当前栈帧里,CPU不用去主存捞数据;而调用update_point_by_ref,哪怕只是修改两个字段,都得先通过指针去主存找对应的内存块——要是这个Point刚好不在缓存里,那等待主存加载的时间,够CPU完成几十次这样的拷贝操作了。
额外buff:编译器的优化空间
编译器对传值代码的优化空间要大得多。比如如果函数里只用到对象的某几个字段,编译器可以直接把这些字段放到寄存器里,连栈拷贝的步骤都能省掉;但传引用的话,编译器没法确定指针指向的内存会不会被其他线程或者外部函数修改,很多激进的优化就没法做,最终执行效率自然会打折扣。
总结一下
- 当对象小且结构简单时,传值的拷贝成本完全可以忽略,反而因为缓存局部性的优势,让数据访问速度快得多;
- 只有当对象足够大(比如几百字节以上),拷贝的成本超过缓存未命中的成本时,传引用才是更划算的选择。
内容的提问来源于stack exchange,提问作者Joseph Kirtman
相关产品推荐
相关产品推荐

