C++ 什么是别名以及它如何影响性能?
在GoingNative活动中,在第二天9分钟的比赛中,Chandler Carruth说: 指针会产生别名问题。它们会减慢二进制文件的速度,而不是加快它们的速度C++ 什么是别名以及它如何影响性能?,c++,C++,在GoingNative活动中,在第二天9分钟的比赛中,Chandler Carruth说: 指针会产生别名问题。它们会减慢二进制文件的速度,而不是加快它们的速度 这是什么意思?这可以用一个(简单的)例子来说明吗?指针是一个表示内存地址的值有时两个指针可以表示相同的内存地址,这就是别名 int * p; *p = 5; int * alias; alias = p; 变量alias是p的别名,*alias等于5如果您更改*别名,则*p随之更改钱德勒所说的问题类型可以用简化的strcpy很容易
这是什么意思?这可以用一个(简单的)例子来说明吗?指针是一个表示内存地址的值有时两个指针可以表示相同的内存地址,这就是别名
int * p;
*p = 5;
int * alias;
alias = p;
变量
alias
是p的别名,*alias
等于5如果您更改*别名
,则*p
随之更改钱德勒所说的问题类型可以用简化的strcpy
很容易说明:
char *stpcpy (char * dest, const char * src);
在编写这一实现时,您可能会假设dest
指向的内存与src
指向的内存完全分离。编译器)可能希望通过从src
指向的字符串中读取一个字符块,并将所有字符一次写入dest
来对其进行优化。但是,如果dest
指向src
前面的一个字节,其行为将不同于简单的逐字符复制
这里的别名问题是src
可以别名dest
,并且生成的代码必须比src
不允许别名dest
时效率更低
真正的strcpy
使用了一个额外的关键字(即,它告诉编译器假定src
和dest
不重叠,这允许编译器生成效率更高的代码
下面是一个更简单的示例,我们可以看到装配中的巨大差异:
void my_function_1(int* a, int* b, int* c) {
if (*a) *b = *a;
if (*a) *c = *a;
}
void my_function_2(int* __restrict a, int* __restrict b, int* __restrict c) {
if (*a) *b = *a;
if (*a) *c = *a;
}
假设这是一个函数的简化,使用两个if语句而不仅仅是if(*a){*b=*a;*c=*a;}
,实际上是有意义的,但目的是相同的
我们在写这篇文章时可能会假设a!=b
,因为这样使用my_函数
是没有意义的。但是编译器不能这样假设,在执行第二行之前从内存中存储b
并重新加载a
,以覆盖b==a的情况代码>:
0000000000400550 <my_function_1>:
400550: 8b 07 mov (%rdi),%eax
400552: 85 c0 test %eax,%eax <= if (*a)
400554: 74 0a je 400560 <my_function_1+0x10>
400556: 89 06 mov %eax,(%rsi)
400558: 8b 07 mov (%rdi),%eax
40055a: 85 c0 test %eax,%eax <= if (*a)
40055c: 74 02 je 400560 <my_function_1+0x10>
40055e: 89 02 mov %eax,(%rdx)
400560: f3 c3 repz retq
考虑以下功能:
void f(float* lhs, float* rhs, float* out, int size) {
for(int i = 0; i < size; i++) {
out[i] = *lhs + *rhs;
}
}
如您所见,问题在于*lhs+*rhs
无法从循环中提升,因为out[i]
修改了它们的值。事实上,编译器无法从循环中提升任何逻辑。因此编译器无法执行“显而易见”的操作优化,因为如果参数别名逻辑现在是不正确的。但是,如果浮点数是按值进行的,那么编译器知道它们不能别名,可以执行提升
当然,此函数非常愚蠢,但它说明了这一点。别名会阻止编译器进行某些优化,从而影响性能。例如:
void foo(int *array,int *size,int *value) {
for(int i=0;i<*size;++i) {
array[i] = 2 * *value;
}
}
使用LLVM获取生成的代码,以下是不同的结果:
1)带有别名
foo: # @foo
.cfi_startproc
# BB#0:
cmpl $0, (%rsi)
jle .LBB0_3
# BB#1:
xorl %eax, %eax
.align 16, 0x90
.LBB0_2: # %.lr.ph
# =>This Inner Loop Header: Depth=1
movl (%rdx), %ecx
addl %ecx, %ecx
movl %ecx, (%rdi,%rax,4)
incq %rax
cmpl (%rsi), %eax
jl .LBB0_2
.LBB0_3: # %._crit_edge
ret
.size foo, .Ltmp1-foo
.cfi_endproc
.Leh_func_end0:
foo: # @foo
.cfi_startproc
# BB#0:
testl %esi, %esi
jle .LBB0_3
# BB#1: # %.lr.ph
addl %edx, %edx
.align 16, 0x90
.LBB0_2: # =>This Inner Loop Header: Depth=1
movl %edx, (%rdi)
addq $4, %rdi
decl %esi
jne .LBB0_2
.LBB0_3: # %._crit_edge
ret
.size foo, .Ltmp1-foo
.cfi_endproc
.Leh_func_end0:
2)无锯齿
foo: # @foo
.cfi_startproc
# BB#0:
cmpl $0, (%rsi)
jle .LBB0_3
# BB#1:
xorl %eax, %eax
.align 16, 0x90
.LBB0_2: # %.lr.ph
# =>This Inner Loop Header: Depth=1
movl (%rdx), %ecx
addl %ecx, %ecx
movl %ecx, (%rdi,%rax,4)
incq %rax
cmpl (%rsi), %eax
jl .LBB0_2
.LBB0_3: # %._crit_edge
ret
.size foo, .Ltmp1-foo
.cfi_endproc
.Leh_func_end0:
foo: # @foo
.cfi_startproc
# BB#0:
testl %esi, %esi
jle .LBB0_3
# BB#1: # %.lr.ph
addl %edx, %edx
.align 16, 0x90
.LBB0_2: # =>This Inner Loop Header: Depth=1
movl %edx, (%rdi)
addq $4, %rdi
decl %esi
jne .LBB0_2
.LBB0_3: # %._crit_edge
ret
.size foo, .Ltmp1-foo
.cfi_endproc
.Leh_func_end0:
您可以看到,带有别名的版本必须在循环体中做更多的工作(在标签LBB0_2
和LBB0_3
之间)。为什么不问问Chandler Carruth?可能是重复的尝试观看。实际上相当不错。正如钱德勒·卡鲁斯所解释的那样,别名是指当你用一个对象的指针而不是对象本身时。另请看钱德勒·卡鲁斯在这段视频(39:40-43:54)中解释了这一点:这如何减慢二进制文件的速度?@未探索的间接寻址减慢二进制文件的速度,因为在你获取数据之前,你不知道实际数据是什么。要获取数据,您需要获取数据的地址,然后您可以最终获取内存请求的数据(除非缓存)。@JesusRamos如果我错了,请纠正我,但如果没有间接寻址,它不是一样的吗?我的意思是,在这种情况下,当取消对p
的引用时,数据也只有在获取后才能知道。我认为这与编译器优化器有关。@未探索的指针不允许在这种情况下进行正常的优化。如果您有两个指向同一事物的指针,编译器将不会尝试跟随它们,因为修改的结果是未定义的。const关键字有帮助吗?例如,foo(…,const int*value)
,这意味着*value
的内容不会改变。@bumfo不,我不希望在这种情况下有任何改进,因为const
实际上并不意味着值不会改变。它所说的只是函数不会通过指针改变它。(虽然从技术上讲,const
并不意味着什么。)
foo: # @foo
.cfi_startproc
# BB#0:
testl %esi, %esi
jle .LBB0_3
# BB#1: # %.lr.ph
addl %edx, %edx
.align 16, 0x90
.LBB0_2: # =>This Inner Loop Header: Depth=1
movl %edx, (%rdi)
addq $4, %rdi
decl %esi
jne .LBB0_2
.LBB0_3: # %._crit_edge
ret
.size foo, .Ltmp1-foo
.cfi_endproc
.Leh_func_end0: