人类可以从限制限定符中得到什么?
-
19-09-2019 - |
题
如果我有C99 restrict
关键字正确,用它限定指针是一种承诺,即它引用的数据不会在编译器背后通过别名进行修改。
相比之下,我的理解方式 const
限定符是编译器强制执行的文档,规定给定的对象不会在人类编写代码的背后被修改。编译器可能会得到一个提示作为副作用,但作为程序员我并不关心。
以类似的方式,考虑 restrict
函数原型中的限定符作为用户确保调用期间的独占访问(“避免别名”,或者可能更强的东西)的要求?它应该用作“文档”吗?
另外,有什么需要理解的吗? restrict
限定一个指针而不是它指向的数据(如 const
做) ?
编辑:我原本相信 restrict
可能会对线程代码产生影响,但这似乎是错误的,因此我从问题中删除了对线程的引用,以避免使读者感到困惑。
解决方案
在最佳“直觉”具有关于限制关键字是,它的一个保证(由程序员给编译器),对于该指针的寿命,存储器经由指针访问将只能通过该指针和未被访问经由另一个指针或引用或全局地址。所以它的重要的其上的指针作为其指针和存储器两者的特性,捆扎两者相加,直到指针超出范围。
其他提示
克里斯·多德 (Chris Dodd) 对关键字的描述正确。在某些平台中,出于性能原因,它可能非常重要,因为它让编译器知道,一旦通过该指针将数据加载到寄存器中,就不需要再次这样做。如果没有这种保证,每次写入任何其他可能别名的指针时,编译器都必须通过指针重新加载数据,这可能会导致严重的管道停顿,称为 加载-命中-存储.
const
和 restrict
是不同的概念,事实并非如此 const
暗示 restrict
. 。全部 const
说的是你不会通过该指针进行写入 在该功能的范围内. 。A 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()
.
我有 写的是关于 restrict
关键字及其性能影响的详细介绍, , 和 迈克·阿克顿也是如此. 。虽然我们讨论的是特定的有序 PowerPC,但 x86 上也存在加载命中存储问题,但 x86 的无序执行使得该停顿更难在配置文件中隔离。
只是想强调一下:这是 不是 如果您关心性能,那么这是一个神秘的或过早的优化。 restrict
如果使用得当,可以带来真正显着的加速。
大多数人都知道什么是错的!
常量做的不可以保证东西不会编译器的背后改变。它所做的就是停止的从编写到现场。别的东西可能仍然能够写入该位置的,所以编译器不能假设它是不变的。
正如其他人所说的限制限定符是大约混叠。事实上,第一轮Ç标准化的过程中,有一个“noalias”关键字的建议。不幸的是,该提案被写入相当糟糕 - 它促使丹尼斯里奇在该过程被卷入了一个也是唯一的一次,当他写了一封信,说了一句话,大意是“noalias一定要去这不是开放的谈判。 “
不用说,“noalias”并没有成为C的一部分。当是时候再试一次,该提案被写得够多了更好的制约被列入的标准 - 即使noalias可能会是一个更有意义的叫法,这个名字是如此沾染,我怀疑有人甚至考虑尝试使用它。
在任何情况下,限制的主要目的是告诉编译器不会有此文件的别名。其中一个原因是为了让事情暂时存储在寄存器中。例如,考虑这样的:
void f(int *a, int *b, int *c) {
for (int i=0; i<*a; i++)
*b += c[i];
}
编译器真的希望把我在寄存器中,并加载*一个到一个寄存器中,所以,当它的时间来决定是否要执行循环的另一次迭代,只是它的值在这些比较寄存器到彼此。可惜的是,它不能做到这一点 - 如果有人谁使用这个功能完全是疯了,并用== B,每次写入* B内环路时把它称为,新价值也是* A的价值 - 因此具有在循环,的的每一次迭代读取*从存储器以防万一强>谁把它称为完全疯狂。使用限制告诉编译器可以生成代码假设a和b将始终是不同的,所以写入*一个永远不变* B(或反之亦然)。
你的理解是正确的大部分。所述restrict
限定符简单地指出,通过如此限定指针访问的数据是仅通过精确指针访问。它适用于作为孔读为写操作。
在编译器不关心并发线程,它不会产生任何代码不同,而且,只要你喜欢,你可以揍你自己的数据。但它确实需要知道的指针操作可能会改变什么全局内存。
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
。
有人比较熟悉的标准或许可以给出更好的答案,但我给它一个镜头。
听起来更像的“挥发性”相反对我“数据将不被编译器的背后修饰的”。
“常量”是指数据不会在编程前进行修改;也就是说,她无法通过标记为“常量”(我写的“能指”,因为int const *pi
,名字pi
不是const的,但*pi
是)能指修改数据。该数据可能是通过另一种能指修改(非const数据可以被传递给一个函数为const数据,毕竟)。
这“限制”资格指针是关键。指针是在C别名数据的唯一途径,所以他们就可以通过两个不同的名字访问某些数据块的唯一途径。 “限制”是所有关于限制到一个接入路径的数据访问。
这可能是从的非常的窄域的例子,但是Altera的Nios II平台是一个软核微控制器,你可以在FPGA中定制。然后,对于微C源代码内,可以使用一个C到硬件工具使用定制的硬件,而不是在软件加快内部循环。
在那里,使用__restrict__
关键字(其是相同的C99的restrict
)的允许C2H工具来正确地优化指针操作的的硬件加速平行的而不是按顺序。在这种情况下,至少所述restrict
是简单地不指用于人类消费。另请参见太阳对restrict
,其中第一行说页
在C程序适当地使用
restrict
限定符可以允许编译器生成显著更快的可执行文件。
如果任何人的兴趣阅读更多关于C2H,这个PDF 讨论优化C2H结果。上__restrict__
的段是20页上。