Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/138.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/mysql/68.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 什么是别名以及它如何影响性能?_C++ - Fatal编程技术网

C++ 什么是别名以及它如何影响性能?

C++ 什么是别名以及它如何影响性能?,c++,C++,在GoingNative活动中,在第二天9分钟的比赛中,Chandler Carruth说: 指针会产生别名问题。它们会减慢二进制文件的速度,而不是加快它们的速度 这是什么意思?这可以用一个(简单的)例子来说明吗?指针是一个表示内存地址的值有时两个指针可以表示相同的内存地址,这就是别名 int * p; *p = 5; int * alias; alias = p; 变量alias是p的别名,*alias等于5如果您更改*别名,则*p随之更改钱德勒所说的问题类型可以用简化的strcpy很容易

在GoingNative活动中,在第二天9分钟的比赛中,Chandler Carruth说:

指针会产生别名问题。它们会减慢二进制文件的速度,而不是加快它们的速度


这是什么意思?这可以用一个(简单的)例子来说明吗?

指针是一个表示内存地址的值有时两个指针可以表示相同的内存地址,这就是别名

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: