代码之家  ›  专栏  ›  技术社区  ›  Jérémie Koenig

人类能从限制限定符中得到什么?

  •  16
  • Jérémie Koenig  · 技术社区  · 15 年前

    如果我有C99 restrict 关键字right,用它限定指针是一种承诺,即它引用的数据不会通过别名在编译器后面被修改。

    相反,我理解 const 限定符是编译器强制的文档,给定的对象不会在编写代码的人后面被修改。编译器可能会得到一个提示作为副作用,但作为一个程序员,我并不真正关心。

    以类似的方式,考虑 限制 函数原型中的限定符,要求用户在调用期间确保独占访问(“避免别名”或更强大的内容)?是否应将其用作“文档”?

    还有,有什么事情需要理解吗? 限制 限定一个指针而不是它指向的数据(如 康斯特 是吗?

    编辑:我最初认为 限制 可能与线程代码有关,但这似乎是错误的,因此我从问题中删除了对线程的引用,以避免混淆读者。

    6 回复  |  直到 12 年前
        1
  •  14
  •   thegreendroid    12 年前

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

        2
  •  21
  •   Crashworks    15 年前

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

    const restrict 是不同的概念,但事实并非如此 康斯特 暗示 限制 . 所有 康斯特 说你不会通过那个指针写 在该职能范围内 . 一 康斯特 指针可能仍有别名。例如,考虑:

    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-like:

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

    限制 是另一种保证:承诺 a != b 在所有呼叫中 foo() .

    我已经 written about the restrict keyword and its performance implications at length so has Mike Acton .尽管我们讨论的是一个特定的按顺序PowerPC,但在x86上也存在加载命中存储问题,但x86的无序执行使得在概要文件中隔离该暂停变得更加困难。

    只是强调一下:这是 如果你关心性能的话,这是一个晦涩的或过早的优化。 限制 如果正确使用,会导致非常显著的加速。

        3
  •  8
  •   Jerry Coffin    15 年前

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

    康斯特 保证编译器的背后不会发生变化。只要停下来 从写作到那个地方。但是,其他一些东西可能仍然能够写入该位置,因此编译器不能假定它是常量。

    正如其他人所说,限制限定符是关于别名的。事实上,在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加载到寄存器中,所以当需要决定是否执行循环的另一次迭代时,它只是将要注册的值相互比较。不幸的是,它不能这样做——如果使用这个函数的人完全疯了,用a==b来调用它,每次它在循环中写到*b时,这个新值也是*a的值——所以它必须在循环的每次迭代中从内存中读取*a, 以防万一 不管是谁说的都是疯子。使用restrict告诉编译器,它可以生成代码,假设a和b总是不同的,所以对*a的写入永远不会更改*b(反之亦然)。

        4
  •  5
  •   DigitalRoss    15 年前

    你的理解基本上是正确的。这个 restrict 限定符简单地声明由这样限定的指针访问的数据是 只有 通过精确的指针访问。它既适用于读,也适用于写。

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

    Restrict 此外,它还附带一个API警告,警告人们给定的函数是以不负责任的参数的假设来实现的。

    就编译器而言,用户不需要锁定。它只想确保它正确地读取 想象上的 被代码击倒 编译器应该生成 如果没有 限制 限定符。添加 限制 把它从那个问题中解放出来。

    最后,请注意,编译器可能已经在更高的优化级别上分析了基于数据类型的可能别名,因此 限制 对于具有指向同一类型数据的多个指针的函数来说,这一点非常重要。您可以从本主题中吸取教训,并确保通过 union .

    我们可以看到 限制 行动中:

    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)
    

    在右栏中, 限制 ,编译器不需要重新读取 b[0] 从记忆中。它能阅读 B〔0〕 把它放在登记簿里 %edx ,然后将寄存器存储两次到内存中。在左列,它不知道商店是否 a 可能已经改变了 b .

        5
  •  1
  •   outis    15 年前

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

    “数据不会在编译器后面被修改”听起来更像是“易失性”的反面。

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

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

        6
  •  1
  •   Mark Rushakoff    15 年前

    这可能是来自 极其 范围很窄,但Altera的NiosII平台是一个软核心微控制器,您可以在FPGA中定制。然后,在该micro的C源代码中,您可以使用C到硬件工具,使用自定义硬件而不是软件来加速内部循环。

    在那里,使用 __restrict__ 关键字(与c99相同 restrict )允许C2H工具正确优化指针操作的硬件加速 并行地 而不是按顺序。至少在这种情况下, 限制 仅仅是 用于人类消费。也见 Sun's page on restrict 第一行写的是

    使用 限制 C程序中适当的限定符允许编译器生成更快的可执行文件。

    如果有人有兴趣阅读更多关于C2H的内容, this PDF 讨论优化C2H结果。章节 限制条件 在第20页。