人类可以通过限制限定符做出什么?

如果我正确地获得了C99 restrict关键字,那么用它来限定指针是一个承诺,它引用的数据不会在编译器的后面通过别名修改。

相比之下,我理解const限定符的方式是编译器强制执行的文档,即在人类编写代码的背后不会修改给定对象。 编译器可能会得到一个提示作为副作用,但作为程序员我并不在乎。

以类似的方式,将函数原型中的restrict限定符视为要求用户在调用期间确保独占访问(“避免别名”或可能更强的东西)是否合适? 它应该用作“文件”吗?

另外,有一些事情要理解, restrict限定指针而不是它指向的数据(如const那样)?

编辑:我原本认为restrict可能会影响线程代码,但这似乎是错误的,所以我从问题中删除线程的引用,以避免混淆读者。

关于restrict关键字的最好“直觉”是它(由程序员对编译器)的保证,在指针的生命周期内,通过该指针访问的内存只能通过该指针访问而不能通过另一个指针访问或参考或全球地址。 因此,重要的是它将指针作为指针和内存的属性,将两者绑在一起直到指针超出范围。

Chris Dodd对关键字有正确的描述。 在某些平台中,由于性能原因,它可能非常重要,因为它让编译器知道一旦它通过该指针将数据加载到寄存器上,就不需要再次这样做了。 如果没有这种保证,编译器必须在每次写入任何其他可能别名指针时通过指针重新加载数据,这可能导致严重的管道停顿,称为加载命中存储 。

constrestrict是不同的概念,并不是const意味着restrict的情况。 所有const表示你不会在该函数范围内写入该指针。 const指针可能仍然是别名。 例如考虑:

 int foo( const int *a, int * b ) { *b *= 2; return *a + *b; // induces LHS: *a must be read back immediately // after write has cleared the store queue } 

虽然你不能直接写入这个函数,但你可以完全合法地调用foo:

 int x = 3; foo( &x, &x ); // returns 12 

restrict是一个不同的保证:在foo()所有调用中承诺a != b

我已经详细介绍了restrict关键字及其性能影响, Mike Acton也是如此 。 虽然我们讨论了特定的有序PowerPC,但x86上也存在load-hit-store问题,但是x86的无序执行使得在配置文件中更难隔离。

并且只是为了强调:如果你完全关心性能,这不是一个神秘或不成熟的优化。 如果使用正确, restrict可以导致真正显着的加速。

你知道的大部分都是错的!

const不保证在编译器后面不会改变某些东西。 它只是阻止写到那个地方。 其他东西可能仍然可以写入该位置,因此编译器不能认为它是常量。

正如其他人所说,限制限定符是关于别名。 实际上,在第一轮C标准化期间,有一个关于“noalias”关键字的提议。 不幸的是,这个提议写得相当糟糕 – 这促使丹尼斯·里奇在这个过程中唯一一次参与,当时他写了一封信,上面写着“诺娜必须去的东西。这不谈判。 “

毋庸置疑,’noalias’没有成为C的一部分。当再次尝试时,提案写得更好,限制被包含在标准中 – 尽管noalias可能是一个更有意义的名字因为它,这个名字是如此被污染,我怀疑任何人甚至考虑尝试使用它。

在任何情况下,限制的主要目的是告诉编译器不会有此项的别名。 其中一个原因是允许暂时将事物存储在寄存器中。 例如,考虑以下内容:

 void f(int *a, int *b, int *c) { for (int i=0; i<*a; i++) *b += c[i]; } 

编译器确实希望将i放入寄存器,并将* a加载到寄存器中,因此当决定是否执行循环的另一次迭代时,它只是将这些寄存器中的值相互比较。 不幸的是,它无法做到这一点 - 如果使用此函数的人完全疯了,并且使用== b调用它,每次它在循环内写入* b时,新值也是* a的值- 所以它必须在循环的每次迭代中从内存中读取* a ,以防无论谁调用它都是完全疯了。 使用restrict告诉编译器它可以生成代码,假设a和b将始终是不同的,因此写入* a将永远不会改变* b(反之亦然)。

你的理解基本上是正确的。 restrict限定符只是声明由so-qualified指针访问的数据只能由该精确指针访问。 它适用于读取和写入。

编译器不关心并发线程,它不会以任何不同的方式生成代码,您可能会根据自己的喜好破坏自己的数据。 但它确实需要知道什么指针操作可能会改变全局内存。

Restrict还带有一个API警告,告知人们假定具有非混淆参数的特定函数。

就编译器而言,用户不需要锁定。 它只是想通过编译器应该生成的代码来确保它正确地读取应该被破坏的数据,以防没有restrict限定符。 添加restrict可以使其摆脱这种担忧。

最后,请注意,编译器可能已经在更高的优化级别基于数据类型分析可能的别名,因此restrict对于具有指向同一类型数据的多个指针的函数来说非常重要。 您可以从这个主题中吸取教训,并确保您所做的任何故意别名都是通过union完成的。

我们可以看到restrict行动:

 void move(int *a, int *b) { void move(int *__restrict a, int *__restrict b) { a[0] = b[0]; a[0] = b[0]; a[1] = b[0]; a[1] = b[0]; } } movl (%edx), %eax movl (%edx), %edx movl %eax, (%ecx) movl %edx, (%eax) movl (%edx), %eax movl %edx, 4(%eax) movl %eax, 4(%ecx) 

在右列中,使用restrict ,编译器不需要从内存重新读取b[0] 。 它能够读取b[0]并将其保存在寄存器%edx ,然后将寄存器两次存储到存储器中。 在左栏中,它不知道a的商店是否可能已经改变了b

更熟悉标准的人可能会给出更好的答案,但我会试一试。

“数据不会在编译器的后面修改”听起来更像是对我的“易变”。

“const”表示不会在程序员面前修改数据; 也就是说,她不能通过标记为“const”的能指来修改数据(我写了“能指”,因为在int const *pi ,名称pi不是const,而是*pi是)。 数据可以通过另一个指示符进行修改(毕竟,非const数据可以作为const数据传递给函数)。

“限制”限定指针是关键。 指针是在C中对数据进行别名的唯一方法,因此它们是您通过两个不同名称访问某些数据的唯一方法。 “限制”是关于限制对一个访问路径的数据访问。

这可能是一个非常狭窄的领域的例子,但Altera的Nios II平台是一个软核微控制器,您可以在FPGA中进行自定义。 然后,在该微控制器的C源代码中,您可以使用C到硬件工具来使用自定义硬件而不是软件来加速内部循环。

在那里,使用__restrict__关键字(与C99的restrict相同)允许C2H工具并行地而不是顺序地正确地优化指针操作的硬件加速。 至少在这种情况下, restrict根本适合人类消费。 另请参阅Sun关于restrict的页面 ,第一行说明

在C程序中适当地使用restrict限定符可以允许编译器生成明显更快的可执行文件。

如果有人有兴趣阅读有关C2H的更多信息, 本PDF将讨论如何优化C2H结果。 有关__restrict__的部分,请__restrict__第20页。