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

时间:2009-10-01 22:12:52

标签: c c99 keyword restrict-qualifier

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

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

以类似的方式,将函数原型中的restrict限定符视为要求用户确保独占访问(“避免别名”,或者可能更强一些)的持续时间是否合适。呼叫?它应该用作“文件”吗?

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

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

6 个答案:

答案 0 :(得分:21)

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

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
}

虽然您无法直接在此函数中写入a,但您可以完全合法地调用foo:

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

restrict是另一种保证:在a != b的所有来电中foo()承诺。

written about the restrict keyword and its performance implications at lengthso has Mike Acton。虽然我们讨论了特定的有序PowerPC,但x86上也存在加载命中存储问题,但是x86的无序执行使得在配置文件中更难隔离该停顿。

并且只是强调:如果您完全关心表现,那么是一种神秘或过早的优化。如果使用正确,restrict会导致非常显着的加速。

答案 1 :(得分:14)

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

答案 2 :(得分:8)

你知道的大多数都是错的!

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

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

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

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

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(反之亦然)。

答案 3 :(得分:5)

您的理解基本上是正确的。 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

答案 4 :(得分:1)

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

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

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

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

答案 5 :(得分:1)

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

在那里,使用__restrict__关键字(与C99的restrict相同)允许C2H工具正确地优化指针操作的硬件加速而不是顺序。至少在这种情况下,restrict只是意味着人类消费。另请参阅Sun's page on restrict,第一行显示

  

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

如果有兴趣阅读有关C2H的更多信息,this PDF讨论优化C2H结果。 __restrict__上的部分位于第20页。