提问人:Ties 提问时间:11/16/2023 最后编辑:Ties 更新时间:11/21/2023 访问量:167
GCC/Clang 是否应该通过一组限制限定的指针来优化此冗余负载?
Should GCC/Clang optimize this redundant load via an array of restrict-qualified pointers?
问:
我正在研究通过将 C99 限制类型限定符添加到数组的类型参数来允许编译器的优化。
例如,考虑下面的函数 f:
int f(int* restrict a[2]) {
*(a[0]) = 10;
*(a[1]) = 11;
return *(a[0]);
}
它采用一个限制指向整数指针的双元素数组 a 作为参数。该函数存储到对象指向和对象指向中。最后,它返回对象指向的值。10
a[0]
11
a[1]
a[0]
因为指针和是限制限定的,所以我假设编译器有足够的信息来执行典型的优化,即不再次加载,而是直接返回(因为如果程序定义了行为,写入不可能改变对象指向)。a[0]
a[1]
a[0]
10
a[1]
a[0]
因此,如果优化发生在“C 代码级别”上,则函数将变为:
int f(int* restrict a[2]) {
*(a[0]) = 10;
*(a[1]) = 11;
return 10;
}
但是,GCC 和 Clang(我尝试了几个版本,其中最新的 Godbolt)都没有执行此优化(使用 -O3 编译时)。
GCC13.2 发出的 X86 程序集是
f:
mov rax, QWORD PTR [rdi]
mov rdx, QWORD PTR [rdi+8]
mov DWORD PTR [rax], 10
mov DWORD PTR [rdx], 11
mov eax, DWORD PTR [rax]
ret
正如人们所看到的,从寄存器执行重新加载,而不仅仅是移动到 .[rax]
10
eax
一个类似的程序,其中指针作为单独的参数而不是指针数组传递,确实展示了两个编译器的预期优化:
int g(int* restrict p, int* restrict q) {
*p = 10;
*q = 11;
return *p;
}
编译为:
g:
mov DWORD PTR [rdi], 10
mov eax, 10
mov DWORD PTR [rsi], 11
ret
我是否误解了限制限定指针的语义作为数组的类型参数,或者预期的优化对于上述编译器来说太难了?
答:
0赞
gulpr
11/16/2023
#1
此优化无法完成,并且您的第二个示例无效(因为忘记了一个间接级别)。
- 即使您在将指针作为数组访问时限制了这两个指针,如果通过第二个元素访问不会修改第一个指针,您也不会违反与编译器的约定。所以它必须再次加载。
int f(int* restrict * restrict a) {
*(a[0]) = 10;
*(a[1]) = 11;
return *(a[0]);
}
//this function is to illustrate the issue
void foo(void)
{
int a;
int *b[2] = {&a,&a};
f(b);
}
- 指针示例需要使用双指针,而不是单个指针。
int g(int* restrict * restrict p, int* restrict * restrict q) {
**p = 10;
**q = 11;
return **p;
}
这将生成优化的代码,因为您无法在不违反对编译器的承诺的情况下访问所引用的对象。*p
*q
mov rax, QWORD PTR [rdi]
mov DWORD PTR [rax], 10
mov rax, QWORD PTR [rsi]
mov DWORD PTR [rax], 11
mov eax, 10
ret
评论
0赞
Andrew Henle
11/16/2023
指针示例需要使用双指针,而不是单个指针。我不确定这如何适用。你能解释一下吗?void *memcpy(void * 限制 s1, const void * 限制 s2, size_t n);
似乎更接近问题提供的内容,作为编译器应该在何处优化的示例,如果指针别名,合约将被破坏,如您的示例所示。int *b[2] = {&a,&a};
0赞
gulpr
11/16/2023
@AndrewHenle 1.第一个示例比第二个示例具有更多的间接性。无法比较。2. 事实并非如此。您可以按照承诺通过传递的指针进行访问。
2赞
Lundin
11/16/2023
没有明显的理由说明为什么必须需要指针到指针。是的,在 OP 的情况下,指针本身可以在访问之间修改,当然。这也不能解决问题 godbolt.org/z/3bKEdr7GK 也没有明显的原因。
0赞
gulpr
11/16/2023
@Ties中方对此有何评论?
1赞
Eric Postpischil
11/16/2023
第1段的第一句话是没有信息的老生常谈。也许是错别字?它说,如果通过第二个元素 () 访问不会修改第一个元素(表示 ,因此 指向的对象),则不会破坏契约 (about )。这是微不足道的,因为这是关于断言某些事物不会访问同一个对象,因此访问不同的对象永远不会违反限制。但是这句话被用作下一句话的条件:“所以它必须再次加载。而这并没有随之而来......a[1]
a[0]
*a[0]
restrict
restrict
restrict
评论
10
*(a[0])
int * restrict p, int * restrict q
int * restrict a[2]
*a[i]
a[i]
*a[1] = 11;
a[0]
a[0]
extern int x