C++ 通过引用将整型变量传递给函数会比通过值传递更有效吗?

C++ 通过引用将整型变量传递给函数会比通过值传递更有效吗?,c++,performance,function,assembly,parameter-passing,C++,Performance,Function,Assembly,Parameter Passing,我知道这是在把整型变量如int,double,long double等传递给函数时说的;它应该通过值来完成,但我很好奇,从组装点(性能方面或空间方面)来看,在我的平台上传递一个整数类型的变量时,它的大小不会比long double之类的指针大,long double的大小为8字节,比4字节的指针大;通过引用将更有效?通常,如果机器的字大小(因此通常是指针大小)小于整数的大小,则通过引用传递将更快 例如,在32位机器上,通过引用传递uint64\t类型将比通过值传递稍快,因为通过值传递涉及复制整数

我知道这是在把整型变量如int,double,long double等传递给函数时说的;它应该通过值来完成,但我很好奇,从组装点(性能方面或空间方面)来看,在我的平台上传递一个整数类型的变量时,它的大小不会比long double之类的指针大,long double的大小为8字节,比4字节的指针大;通过引用将更有效?

通常,如果机器的字大小(因此通常是指针大小)小于整数的大小,则通过引用传递将更快

例如,在32位机器上,通过引用传递
uint64\t
类型将比通过值传递稍快,因为通过值传递涉及复制整数,这需要两个寄存器加载。通过引用传递只涉及一个寄存器加载


无论如何,在大多数情况下,它不太可能产生任何明显的性能差异,除非您在一个紧密循环中调用该函数数百万次,在这种情况下,如果可能,函数可能应该内联。

将指针/引用传递给大于本机指针大小的整数值可能是局部最优的,但很难说它是否是全局最优的。这在很大程度上取决于被调用方对值的使用。如果它确实是一个整数,并且被调用者将其视为一个整数,那么在某个时刻,该值很可能会被加载到一个或多个寄存器中(例如,为了让程序对这些值执行算术运算),从而在调用者中产生额外的开销来取消对指针的引用。如果被调用方是由优化编译器内联的,则编译器可能只需在两个寄存器之间传递拆分的整数值。但是,如果被调用方不能内联(例如,如果它是第三方API代码),那么编译器就不能执行这种内联,实际上传递指针可能更有效,尽管您不太可能找到接受整数的函数通过引用传递的库,除非这样被调用方可以修改调用方的值:这会带来一系列完全不同的问题

大多数情况下,现代优化编译器会在考虑所有这些因素的情况下做出接近最优的决策,程序员通常最好不要试图通过过早优化抢占编译器。事实上,这可能会导致代码效率降低


在绝大多数情况下,最明智的做法是以最能传达您意图的方式编写代码(传递“值”类型的值,除非参数在语义上是“out”或“reference”参数,采用C#术语),并且只有在存在明显的性能瓶颈时才担心效率。

Test,测试,测试,拆卸,拆卸,拆卸

简单的、本机大小的整数

unsigned int fun_one ( unsigned int a ) { return((a&7)+1); } unsigned int fun_two ( unsigned int *a ) { return((*a&7)+1); } 无符号整数(无符号整数a) { 回报率((a&7)+1); } 无符号整数乐趣二(无符号整数*a) { 回报率((*a&7)+1); } 没有优化,当通过引用传递时,您有一条额外的指令,以便加载该地址的值来处理它

00000000 : 0: e52db004 push {fp} ; (str fp, [sp, #-4]!) 4: e28db000 add fp, sp, #0 8: e24dd00c sub sp, sp, #12 c: e50b0008 str r0, [fp, #-8] 10: e51b3008 ldr r3, [fp, #-8] 14: e2033007 and r3, r3, #7 18: e2833001 add r3, r3, #1 1c: e1a00003 mov r0, r3 20: e28bd000 add sp, fp, #0 24: e49db004 pop {fp} ; (ldr fp, [sp], #4) 28: e12fff1e bx lr 0000002c : 2c: e52db004 push {fp} ; (str fp, [sp, #-4]!) 30: e28db000 add fp, sp, #0 34: e24dd00c sub sp, sp, #12 38: e50b0008 str r0, [fp, #-8] 3c: e51b3008 ldr r3, [fp, #-8] 40: e5933000 ldr r3, [r3] 44: e2033007 and r3, r3, #7 48: e2833001 add r3, r3, #1 4c: e1a00003 mov r0, r3 50: e28bd000 add sp, fp, #0 54: e49db004 pop {fp} ; (ldr fp, [sp], #4) 58: e12fff1e bx lr 00000000 : 0:e52db004推送{fp};(str fp,[sp,#-4]!) 4:e28db000添加fp,sp,#0 8:e24dd00c子sp,sp,#12 c:e50b0008 str r0[fp,#-8] 10:e51b3008 ldr r3[fp,#-8] 14:e2033007和r3,r3,#7 18:e2833001添加r3,r3,#1 1c:e1a00003 mov r0,r3 20:e28bd000添加sp,fp,#0 24:e49db004 pop{fp};(ldr fp,[sp],#4) 28:E12FF1E bx lr 0000002c: 2c:e52db004推送{fp};(str fp,[sp,#-4]!) 30:e28db000添加fp,sp,#0 34:e24dd00c子sp,sp,#12 38:e50b0008 str r0[fp,#-8] 3c:e51b3008 ldr r3[fp,#-8] 40:e5933000 ldr r3[r3] 44:e2033007和r3,r3,#7 48:e2833001添加r3,r3,#1 4c:e1a00003 mov r0,r3 50:e28bd000添加sp,fp,#0 54:e49db004 pop{fp};(ldr fp,[sp],#4) 58:E12FF1E bx lr 优化-O1到-O3得到了相同的结果。但仍然会丢失加载该值的指令

00000000 : 0: e2000007 and r0, r0, #7 4: e2800001 add r0, r0, #1 8: e12fff1e bx lr 0000000c : c: e5900000 ldr r0, [r0] 10: e2000007 and r0, r0, #7 14: e2800001 add r0, r0, #1 18: e12fff1e bx lr 00000000 : 0:e2000007和r0,r0,#7 4:e2800001加上r0,r0,#1 8:E12FF1E bx lr 0000000c: c:E590000 ldr r0,[r0] 10:e2000007和r0,r0,#7 14:e2800001加上r0,r0,#1 18:E12FF1E bx lr 对于几乎任何你可以传递的单一尺寸的东西,它都会继续这样。如果是64位整数,您仍然会将从引用加载的额外指令和内存周期刻录到寄存器中,以便进行操作。任何数组中的某些东西你都不能真正做一个传递值,对吗?但是,一个可以访问的结构,以及访问一个结构,不管引用与否,都可能需要一些寻址

typedef struct { unsigned int a; unsigned int b; char c[4]; } ruct; unsigned int fun_one ( ruct a ) { return((a.c[3]&7)+1); } unsigned int fun_two ( ruct *a ) { return((a->c[3]&7)+1); } 类型定义结构 { 无符号整数a; 无符号整数b; charc[4]; }UCT; 无符号整型(结构a) { 回报率((a.c[3]&7)+1); } 无符号整数二次方(uct*a) { 返回((a->c[3]&7)+1); } 在没有优化的情况下,我们开始时每个都有12条指令。我将不得不更多地盯着它看,以决定其中一个是否比另一个燃烧更多的时钟周期

00000000 : 0: e52db004 push {fp} ; (str fp, [sp, #-4]!) 4: e28db000 add fp, sp, #0 8: e24dd014 sub sp, sp, #20 c: e24b3010 sub r3, fp, #16 10: e8830007 stm r3, {r0, r1, r2} 14: e55b3005 ldrb r3, [fp, #-5] 18: e2033007 and r3, r3, #7 1c: e2833001 add r3, r3, #1 20: e1a00003 mov r0, r3 24: e28bd000 add sp, fp, #0 28: e49db004 pop {fp} ; (ldr fp, [sp], #4) 2c: e12fff1e bx lr 00000030 : 30: e52db004 push {fp} ; (str fp, [sp, #-4]!) 34: e28db000 add fp, sp, #0 38: e24dd00c sub sp, sp, #12 3c: e50b0008 str r0, [fp, #-8] 40: e51b3008 ldr r3, [fp, #-8] 44: e5d3300b ldrb r3, [r3, #11] 48: e2033007 and r3, r3, #7 4c: e2833001 add r3, r3, #1 50: e1a00003 mov r0, r3 54: e28bd000 add sp, fp, #0 58: e49db004 pop {fp} ; (ldr fp, [sp], #4) 5c: e12fff1e bx lr 00000000 : 0:e52db004推送{fp};(str fp,[sp,#-4]!) 4:e28db000添加fp,sp,#0 8:e24dd014子sp,sp,#20 c:e24b3010子类r3,fp,#16 10:e8830007 stm r3,{r0,r1,r2} 14:e55b3005 ldrb r3[fp,#-5] 18:e2033007和r3,r3,#7 1c:e2833001添加r3,r3,#1 20:e1a00003 mov r0,r3 24:e28bd000添加sp,fp,#0 28:e49db004 pop{fp};(ldr fp,[sp],#4) 2c:E12FF1E bx lr 00000030 : 30:e52db 00000000 : 0: e24dd010 sub sp, sp, #16 4: e28d3004 add r3, sp, #4 8: e8830007 stm r3, {r0, r1, r2} c: e5dd100f ldrb r1, [sp, #15] 10: e2010007 and r0, r1, #7 14: e2800001 add r0, r0, #1 18: e28dd010 add sp, sp, #16 1c: e12fff1e bx lr 00000020 : 20: e5d0100b ldrb r1, [r0, #11] 24: e2010007 and r0, r1, #7 28: e2800001 add r0, r0, #1 2c: e12fff1e bx lr
long long x=0,y=1;

for (int i = 0; i < 10; i++) {
  x = f(&x);
  g(&x);

  y = f(&y);
  g(&y);
}
long long f(long long * x) {
    static long long * old;
    if (old) { *old++; *x += *old; }
    return ++*x;
}

long long g(long long * x) {
    static long long * old;
    if (old == x) { abort(); }
    printf("%lld\n", *x);
}
{
  long long tmp = x;
  x = f(&tmp);
}