Ant*_*eru 22 c c++ optimization restrict-qualifier
只是想知道:当我向指针添加restrict时,我告诉编译器指针不是另一个指针的别名.我们假设我有一个类似的函数:
// Constructed example
void foo (float* result, const float* a, const float* b, const size_t size)
{
for (size_t i = 0; i < size; ++i)
{
result [i] = a [0] * b [i];
}
}
Run Code Online (Sandbox Code Playgroud)
如果编译器必须假设result可能重叠a,则必须每次重新获取.但是,正如a标记的那样const,编译器也可以假设a是固定的,因此一次取回它就可以了.
问题是,在这种情况下,使用restrict的推荐方法是什么?我当然不希望编译器a每次都重新获取,但我找不到关于如何restrict在这里工作的好信息.
fal*_*tro 15
你的指针是const,告诉任何调用你的函数的人你不会触及通过该变量指向的数据.不幸的是,编译器仍然不知道结果是否是const指针的别名.您始终可以使用非常量指针作为常量指针.例如,许多函数将const char(即字符串)指针作为参数,但是如果您愿意,可以将它传递给非const指针,该函数仅仅是让您承诺它不会使用该特定的指针改变任何东西.
基本上,为了更接近你的问题,你需要为a和b添加限制,以便"保证"编译器使用此函数的人不会将结果作为别名传递给a或b.当然,假设你能够做出这样的承诺.
小智 9
这里的每个人似乎都很困惑.到目前为止,在任何答案中都没有一个const指针的例子.
该声明const float* a是不是一个常量指针,它是常量存储.指针仍然是可变的.float *const a是一个指向可变浮点的const指针.
所以问题应该是,是否有任何意义float *const restrict a(或者const float *const restrict a如果您愿意).
是的,你需要限制. Pointer-to-const并不意味着没有任何东西可以改变数据,只是你无法通过指针改变它.
const主要是一种机制,要求编译器帮助您跟踪您希望功能允许修改的内容. const并不是对编译器的承诺,函数实际上不会修改数据.
与restrict使用指向const可变数据的指针基本上是对其他人的承诺,而不是对编译器的承诺.const除非您尝试修改编译器放入只读内存中的内容(请参阅下面的static const变量),否则抛弃所有地方不会导致优化器(AFAIK)出现错误行为.如果编译器在优化时无法看到函数的定义,则必须假设它const通过该指针抛弃并修改数据(即函数不尊重const其指针args 的ness).
但编译器确实知道static const int foo = 15;无法更改,即使将其地址传递给未知函数,也会可靠地内联该值.(这就是为什么static const int foo = 15;并不比#define foo 15优化编译器慢.好的编译器会constexpr尽可能优化它.)
请记住,这restrict是对编译器的承诺,即您通过该指针访问的内容不会与其他任何内容重叠.如果这不是真的,那么你的功能不一定会达到你的预期.例如,不要打电话foo_restrict(buf, buf, buf)到原地操作.
根据我的经验(使用gcc和clang),restrict主要用于存储的指针.连接restrict源指针也没什么坏处,但是如果你的函数所做的所有存储都是通过restrict指针,通常你只需要将目标指针放在目标指针上即可获得所有的asm改进.
如果你的循环中有任何函数调用,restrict在源指针上确实让clang(但不是gcc)避免重新加载.在Godbolt编译器资源管理器上查看这些测试用例,特别是这个:
void value_only(int); // a function the compiler can't inline
int arg_pointer_valonly(const int *__restrict__ src)
{
// the compiler needs to load `*src` to pass it as a function arg
value_only(*src);
// and then needs it again here to calculate the return value
return 5 + *src; // clang: no reload because of __restrict__
}
Run Code Online (Sandbox Code Playgroud)
gcc6.3(针对x86-64 SysV ABI)决定src在函数调用中将(指针)保留在调用保留寄存器中,并*src在调用后重新加载.无论是gcc的算法都没有发现这种优化的可能性,或者认为它不值得,或者gcc devs故意没有实现它,因为他们认为它不安全.IDK哪个.但是既然clang做了,我猜它根据C11标准可能是合法的.
clang4.0优化它只加载*src一次,并将值保存在函数调用的保持调用寄存器中.没有restrict,它不会这样做,因为被调用的函数可能(作为副作用)*src通过另一个指针修改.
例如,此函数的调用者可能已传递全局变量的地址.但是*src除了通过src指针之外的任何修改都会违反restrict对编译器的承诺.由于我们没有传递src给valonly(),编译器可以假设它不会修改该值.
C语言的GNU方言允许使用__attribute__((pure))或__attribute__((const))声明一个函数没有副作用,允许这种优化restrict,但是在ISO C11(AFAIK)中没有可移植的等价物.当然,允许函数内联(通过将其放在头文件中或使用LTO)也允许这种优化,并且对于小函数(尤其是在内部循环中调用)更好.
编译器通常非常积极地进行标准允许的优化,即使它们对某些程序员感到惊讶并且破坏了一些恰好工作的现有不安全代码.(C是如此可移植,以至于很多东西都是基本标准中未定义的行为;大多数好的实现确实定义了标准留下的许多东西的行为作为UB.)C不是一种在编译器中抛出代码是安全的语言.它做你想要的,而不检查你是否正确地做(没有符号整数溢出等)
如果你看一下x86-64 asm输出来编译你的函数(从问题中),你可以很容易地看到差异.我把它放在Godbolt编译器资源管理器上.
在这种情况下,把restrict上a足以让铛葫芦的负载a[0],而不是的gcc.
有了float *restrict result,clang和gcc都会提升负载.
例如
# gcc6.3, for foo with no restrict, or with just const float *restrict a
.L5:
vmovss xmm0, DWORD PTR [rsi]
vmulss xmm0, xmm0, DWORD PTR [rdx+rax*4]
vmovss DWORD PTR [rdi+rax*4], xmm0
add rax, 1
cmp rcx, rax
jne .L5
Run Code Online (Sandbox Code Playgroud)
与
# gcc 6.3 with float *__restrict__ result
# clang is similar with const float *__restrict__ a but not on result.
vmovss xmm1, DWORD PTR [rsi] # outside the loop
.L11:
vmulss xmm0, xmm1, DWORD PTR [rdx+rax*4]
vmovss DWORD PTR [rdi+rax*4], xmm0
add rax, 1
cmp rcx, rax
jne .L11
Run Code Online (Sandbox Code Playgroud)
总而言之,把__restrict__所有保证不与其他东西重叠的指针放在一起.
BTW,restrict只是C中的一个关键字.一些C++编译器支持__restrict__或__restrict作为扩展,所以你应该#ifdef把它放在未知的编译器上.
以来
小智 7
在C-99标准(ISO/IEC 9899:1999(E))中const * restrict,有例如第7.8.2.3节中的例子:
#include <inttypes.h>
intmax_t strtoimax(const char * restrict nptr,
char ** restrict endptr, int base);
--- snip ---
Run Code Online (Sandbox Code Playgroud)
因此,如果假设标准不提供这样的例子,如果它们是const *多余的* restrict,那么它们实际上并不是多余的.