【问题标题】:What are the semantics of C99's "restrict" with regards to pointers to pointers?关于指向指针的指针,C99 的“限制”的语义是什么?
【发布时间】:2010-11-29 19:21:45
【问题描述】:

我正在做大量的矩阵运算,并希望利用 C99 的 restrict 指针限定符。

我想将我的矩阵设置为指向指针的指针,以便于下标,如下所示:

int **A = malloc (ncols * sizeof(int *));
A[0] = malloc (nrows * ncols * sizof(int));
for (int i=1; i < ncols; i++) {
    A[i] = A[0] + i*nrows;
}

现在,对于矩阵乘法函数

void mmultiply ( int nrows, int ncols, int **Out, int **A, int **B);

我必须将参数的两个指针都限定为受限吗?这是有效的语法,但我很难确定 int *restrict *restrict 的行为是否与 int **restrict 不同。

那么,在适当限制指针的情况下,是否通过A[0][col*nrows + row] 访问元素未定义? (即,编译器是否会假设我通过A[col][row] 访问矩阵以获得row 的值,例如row &lt; nrow)?还是我必须保持一致?

【问题讨论】:

  • 非常感谢所有回答者!很遗憾我只能选择一个“正确”的答案——你们都帮助我思考了所涉及的问题。
  • 十一年后,我想回到过去,对我以前的自己大喊大叫,因为我没有使用BLAS。好吧,好吧,当时还没有 OpenBLAS,但 the older netlib BLAS 确实存在。在任何情况下,优化矩阵乘法都不是您想要发现自己心血来潮做的事情!

标签: c pointers multidimensional-array c99 restrict-qualifier


【解决方案1】:

对于第一个问题,“是”,如果您同时使用restrict 限定符,这将意味着不同,特别是指针也不会被别名。至于是否有什么不同:理论上是的,在实践中,这取决于优化器。

对于第二个问题,“是”,它将假定通过行指针访问的任何内容都只能通过行指针访问。

你也可以把const扔进去。

最后,如果这是 -O2、-O3 或 -Os 处的 gcc,则编译器已经在根据类型进行别名分析。我相信其他编译器也会这样做。这意味着已经理解了限制指针与整数,只留下可能相互存储的数组。

总之,优化器将假定指针没有被存储为整数,并且它知道在循环期间它没有进行任何指针写入。

所以你可能会得到相同的代码,只有一个限制。

【讨论】:

  • 需要注意的是,大多数编译器在别名方面比 GCCx86 宽松得多。例如,MSVC05 甚至会将 int *float * 考虑为可能的别名,除非您使用 restrict 来告诉它。 (是的,我知道这不符合规范,但编译器还是会这样做,正如我从 /FAcs 中了解到的那样。)
  • @Crashworks:规范中没有要求,但正确使用 restrict 可以让代码获得与严格别名基本相同的性能优势,但不会破坏任何现有代码,而且什么也没有在标准中禁止编译器将所有操作视为直接作用于内存。
  • 如果一个函数接受一个restrict指针,并且在调用点已经有另一个指针,那么你现在有两个指向同一个对象的指针,这违反了restrict,不是吗?
【解决方案2】:

外部(第二个)限制告诉编译器没有指针数组(A、B 和 out)别名。内部(第一个)限制告诉编译器,整数数组(由指针数组的元素指向)没有别名。

如果您同时访问 A[0][col*nrows + row] 和 A[col][row],那么您违反了内部限制,因此事情可能会中断。

【讨论】:

    【解决方案3】:

    int **restrict 仅断言 Out、A 和 B 寻址的内存不重叠(除非 A 和 B 可以重叠,假设您的函数不修改其中任何一个)。这意味着指针数组。它没有断言 Out、A 和 B 指向的内存内容的任何内容。n1124 中的脚注 117 说:

    如果标识符 p 具有类型 (int **restrict),那么指针表达式 p 和 p+1 是基于 指定的受限指针对象 由 p,但指针表达式 *p 而 p[1] 不是。

    通过与const 类比,我怀疑使用restrict 排位赛两次将断言您想要什么,即数组中的值都没有指向重叠的内存。但是阅读标准,我无法向自己证明它确实如此。我认为“让 D 是一个普通标识符的声明,它提供了一种将对象 P 指定为指向 T 类型的限制限定指针的方法”确实意味着对于int *restrict *restrict A,然后是 A[0] 和 A[1 ] 是指定为指向 int 的限制限定指针的对象。但这是相当沉重的法律术语。

    我不知道你的编译器是否真的会利用这些知识做任何事情,请注意。明明可以,就是有没有实现的问题。

    所以我真的不知道您在传统的 C 二维数组中获得了什么,您只需分配 rows * cols * sizeof(int),并使用 A[cols*row + col] 进行索引。那么您显然只需要使用一次限制,并且任何使用restrict 执行任何操作的编译器都能够在写入 Out 时重新排序从 A 和 B 读取。如果没有restrict,当然不能,所以通过做你正在做的事情,你把自己置于编译器的怜悯之下。如果它不能处理双重限制,只有单一限制的情况,那么你的双重间接已经花费了你的优化。

    乍一看,乘法可能比额外的指针间接更快。您显然关心性能,否则您根本不会使用限制,因此在进行此更改之前,我会相当仔细地测试性能(在您关心的所有编译器上),以便语法稍微好一点,而不必记住有多少每次访问时数组中都有列。

    是否通过 A[0][col*nrows + row] undefined 访问元素?

    是的,如果元素被其中一种访问修改,因为这使得 A[0] 成为内存的别名,也通过 A[col] 访问。如果只有 A 和 B 是限制限定的指针,那很好,但如果 A[0] 和 A[col] 是,则不是。

    我假设你没有在这个函数中修改 A,所以实际上这个别名很好。但是,如果您对 Out 执行相同的操作,则行为将是未定义的。

    【讨论】:

    • 感谢您的周到回复。关于乘法比指针间接更快,您是对的;我已经测试过这通常是正确的。但是,我计划将维度与数据捆绑到矩阵结构中以进行传递。毫不奇怪,从结构指针中取消引用nrows 并乘以与取消引用数据指针相同的成本。在保证它的函数中,我可以提前取消引用任何指针并使用一维数组索引。如果我仍然这样做,我不妨在其他地方提供更好的 [col][row] 语法。
    猜你喜欢
    • 2013-06-27
    • 2021-06-13
    • 1970-01-01
    • 2018-02-09
    • 2017-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-16
    相关资源
    最近更新 更多