使用以下访问数组元素的方法之一是否有任何性能下降?
int someRandomArrayWithLongName[] = {0, 1, 2, 3, 4};
// case (1): access with alias reference
int &elem = someRandomArrayWithLongName[1];
while (!exit)
{
++elem;
}
// case (2): pointer access
int *elem = &someRandomArrayWithLongName[1];
while (!exit)
{
++(*elem);
}
// default case (3): conventional element access
while (!exit)
{
++someRandomArrayWithLongName[1];
}
特别是,编译器是否会识别别名并发挥其魔力来防止不必要的内存分配(案例1和2)?或者仅仅使用案例3是否更好?
编辑:我根据评论中的建议使用了http://gcc.godbolt.org/。结果证明这三种情况产生完全相同的汇编代码(source):
test1():
movzbl exit(%rip), %eax
testb %al, %al
jne .L1
movl someRandomArrayWithLongName+4(%rip), %eax
addl $1, %eax
.L3:
movzbl exit(%rip), %edx
movl %eax, %ecx
addl $1, %eax
testb %dl, %dl
je .L3
movl %ecx, someRandomArrayWithLongName+4(%rip)
.L1:
rep ret
test2():
movzbl exit(%rip), %eax
testb %al, %al
jne .L8
movl someRandomArrayWithLongName+4(%rip), %eax
addl $1, %eax
.L10:
movzbl exit(%rip), %edx
movl %eax, %ecx
addl $1, %eax
testb %dl, %dl
je .L10
movl %ecx, someRandomArrayWithLongName+4(%rip)
.L8:
rep ret
test3():
movzbl exit(%rip), %eax
testb %al, %al
jne .L14
movl someRandomArrayWithLongName+4(%rip), %eax
addl $1, %eax
.L16:
movzbl exit(%rip), %edx
movl %eax, %ecx
addl $1, %eax
testb %dl, %dl
je .L16
movl %ecx, someRandomArrayWithLongName+4(%rip)
.L14:
rep ret
exit:
.zero 1
答案 0 :(得分:4)
你担心的“不必要的内存分配”在最坏的情况下只涉及一小部分堆栈空间,并且几乎没有额外的时间,所以没什么好担心的。
也就是说,任何有价值的现代编译器很可能会为优化版本中的所有三个生成相同的代码 在实践中,使用您认为最易读的(并且最不容易出错)并且如果结果太慢则重写。
为了提高性能,你应该努力避免通过尽可能保持本地修改来访问内存,这样它们就可以在寄存器中执行,如下所示:
int increment = 0;
while (!exit)
{
++increment;
}
someRandomArrayWithLongName[1] += increment;
答案 1 :(得分:1)
一般来说,第三个选项的执行效果与编译器可以执行的数组操作相同,前两个选项的性能取决于编译器跟踪指针原点返回数组的能力。
特别是,当写入任意指针时,可能会抑制许多优化,如(1)和(2)中所示 - 因为编译器很难保证循环中的各种对象不会{{3 }}。由于(3)中的访问直接针对整数数组,因此通常可以应用更积极的优化(尤其是循环变量的注册)。
在您给出的简单示例中,别名不会成为问题,因为循环不会重复访问同一个元素。我假设您的问题更为一般 - 您对这些方法的性能感兴趣,这些循环可能非常包含可能出现别名问题的操作。
通过盯着源代码确定是否实际出现别名或任何其他(de)优化是相对困难的。例如,当elem在同一函数中声明时,现代编译器可能会处理上述所有情况,但是如果elem是从外部传入的话则不会(因为编译器在函数调用之间优化的能力有限)。
因此,虽然理解理论并没有错,但最好的方法是测试它是否真的重要。按照实际代码的实际方式计算各种方法,如果你准备好了,请看一下反汇编。