4

我正在探索 C99 中简单循环的不同实现如何根据函数签名自动矢量化。

这是我的代码:

/* #define PRAGMA_SIMD _Pragma("simd") */
#define PRAGMA_SIMD

#ifdef __INTEL_COMPILER
#define ASSUME_ALIGNED(a) __assume_aligned(a,64)
#else
#define ASSUME_ALIGNED(a)
#endif

#ifndef ARRAY_RESTRICT
#define ARRAY_RESTRICT
#endif

void foo1(double * restrict a, const double * restrict b, const double * restrict c) 
{ 
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < 2048; ++i) {
        if (c[i] > 0) {
            a[i] = b[i];
        } else {
            a[i] = 0.0;
        } 
    }
}

void foo2(double * restrict a, const double * restrict b, const double * restrict c) 
{ 
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < 2048; ++i) {
        a[i] = ((c[i] > 0) ? b[i] : 0.0);
    }
}

/* Undetermined size version */

void foo3(int n, double * restrict a, const double * restrict b, const double * restrict c) 
{ 
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < n; ++i) {
        if (c[i] > 0) {
            a[i] = b[i];
        } else {
            a[i] = 0.0;
        } 
    }
}

void foo4(int n, double * restrict a, const double * restrict b, const double * restrict c) 
{ 
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < n; ++i) {
        a[i] = ((c[i] > 0) ? b[i] : 0.0);
    }
}

/* Static array versions */

void foo5(double ARRAY_RESTRICT a[2048], const double ARRAY_RESTRICT b[2048], const double ARRAY_RESTRICT c[2048]) 
{ 
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < 2048; ++i) {
        if (c[i] > 0) {
            a[i] = b[i];
        } else {
            a[i] = 0.0;
        } 
    }
}

void foo6(double ARRAY_RESTRICT a[2048], const double ARRAY_RESTRICT b[2048], const double ARRAY_RESTRICT c[2048]) 
{ 
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < 2048; ++i) {
        a[i] = ((c[i] > 0) ? b[i] : 0.0);
    }
}

/* VLA versions */

void foo7(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n], const double ARRAY_RESTRICT c[n]) 
{ 
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < n; ++i) {
        if (c[i] > 0) {
            a[i] = b[i];
        } else {
            a[i] = 0.0;
        } 
    }
}

void foo8(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n], const double ARRAY_RESTRICT c[n]) 
{ 
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < n; ++i) {
        a[i] = ((c[i] > 0) ? b[i] : 0.0);
    }
}

当我编译时

$ icc -O3 -std=c99 -opt-report5 -mavx -S foo.c 
icc: remark #10397: optimization reports are generated in *.optrpt files in the output location

我看到 VLA 案例不是自动矢量化的,但是当我添加标志来断言 no aliasing-fno-alias时,它们是。因此,我得出结论,我应该在源代码中规定这个,所以我尝试通过编译来做到这一点

$ icc -O3 -std=c99 -opt-report5 -mavx -DARRAY_RESTRICT=restrict -S foo.c 
icc: remark #10397: optimization reports are generated in *.optrpt files in the output location

编译器错误输出包括

foo.c(98): error: "restrict" is not allowed
void foo7(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n], 
const double ARRAY_RESTRICT c[n]) 

             ^

但如您所见,我的 VLA 参数不允许使用限制。

所以我的问题是:有没有办法在 ISO C 中断言 VLA 没有别名?

请注意,我可以使用编译指示在源代码中断言没有别名 - 例如simd,omp simdivdep- 并获得我想要的自动矢量化,但这些不是 ISO C。

在这种情况下,ISO C 是指 C 的最新版本,在撰写本文时当然是 C11。

4

2 回答 2

5

您的原始代码对我来说很失败,其中包含以下消息:

 void foo7(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n], const double ARRAY_RESTRICT c[n])
 ^
restrict.c:126:1: error: invalid use of ‘restrict’
restrict.c:126:1: error: invalid use of ‘restrict’
restrict.c:145:1: error: invalid use of ‘restrict’

转移评论的选定部分

§6.7.6.3函数声明符(包括原型)具有示例 5,其中说明以下函数原型声明符是等效的:

void f(double (* restrict a)[5]);
void f(double a[restrict][5]);
void f(double a[restrict 3][5]);
void f(double a[restrict static 3][5]);

这是标准中唯一与数组类型直接关联的地方。§6.7.6 一般用于声明符,§6.7.6.2 用于数组声明符,在我看来,限制必须出现在数组维度的第一个组件内。在您的上下文中,它应该是:

void foo7(int n, double a[ARRAY_RESTRICT n],
           const double b[ARRAY_RESTRICT n],
           const double c[ARRAY_RESTRICT n])

如果没有看到标准中的示例并且您提出问题,我不会相信这种表示法!请注意,这适用于数组以及 VLA。

这个修改后的代码,基于注释,在相同的编译选项下干净地编译:

gcc -g -O3 -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
    -Wold-style-definition -Wold-style-declaration -Werror -c new.restrict.c

编译选项要求事先声明非静态函数,因此需要在文件顶部声明。我还强制#define ARRAY_RESTRICT restrict输入源代码,而不是将其作为编译选项。

编译器是在 Ubuntu 14.04 衍生版本上运行的 GCC 4.9.2。

文件new.restrict.c

/* #define PRAGMA_SIMD _Pragma("simd") */
#define PRAGMA_SIMD

#ifdef __INTEL_COMPILER
#define ASSUME_ALIGNED(a) __assume_aligned(a, 64)
#else
#define ASSUME_ALIGNED(a)
#endif

#define ARRAY_RESTRICT restrict

#ifndef ARRAY_RESTRICT
#define ARRAY_RESTRICT
#endif

void foo1(double *restrict a, const double *restrict b, const double *restrict c);
void foo2(double *restrict a, const double *restrict b, const double *restrict c);
void foo3(int n, double *restrict a, const double *restrict b, const double *restrict c);
void foo4(int n, double *restrict a, const double *restrict b, const double *restrict c);
void foo5(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048]);
void foo6(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048]);
void foo7(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n]);
void foo8(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n]);

void foo1(double *restrict a, const double *restrict b, const double *restrict c)
{
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < 2048; ++i)
    {
        if (c[i] > 0)
        {
            a[i] = b[i];
        }
        else
        {
            a[i] = 0.0;
        }
    }
}

void foo2(double *restrict a, const double *restrict b, const double *restrict c)
{
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < 2048; ++i)
    {
        a[i] = ((c[i] > 0) ? b[i] : 0.0);
    }
}

/* Undetermined size version */

void foo3(int n, double *restrict a, const double *restrict b, const double *restrict c)
{
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < n; ++i)
    {
        if (c[i] > 0)
        {
            a[i] = b[i];
        }
        else
        {
            a[i] = 0.0;
        }
    }
}

void foo4(int n, double *restrict a, const double *restrict b, const double *restrict c)
{
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < n; ++i)
    {
        a[i] = ((c[i] > 0) ? b[i] : 0.0);
    }
}

/* Static array versions */

void foo5(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048])
{
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < 2048; ++i)
    {
        if (c[i] > 0)
        {
            a[i] = b[i];
        }
        else
        {
            a[i] = 0.0;
        }
    }
}

void foo6(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048])
{
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < 2048; ++i)
    {
        a[i] = ((c[i] > 0) ? b[i] : 0.0);
    }
}

/* VLA versions */

void foo7(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n])
{
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < n; ++i)
    {
        if (c[i] > 0)
        {
            a[i] = b[i];
        }
        else
        {
            a[i] = 0.0;
        }
    }
}

void foo8(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n])
{
    ASSUME_ALIGNED(a);
    ASSUME_ALIGNED(b);
    ASSUME_ALIGNED(c);
    PRAGMA_SIMD
    for (int i = 0; i < n; ++i)
    {
        a[i] = ((c[i] > 0) ? b[i] : 0.0);
    }
}
于 2015-03-17T01:06:07.810 回答
4

此代码中的所有参数都没有可变修改类型。foo6并且foo7是完全相同的函数签名,除了int n. 请参阅为什么 C 和 C++ 编译器在从未强制执行时允许函数签名中的数组长度?.

这些都是完全相同的:

void foo8(int n, T *a);
void foo8(int n, T a[16]);
void foo8(int n, T a[n]);

版本void foo8(int n, T a[]);几乎相同,但它有一个极端情况,如果T是不完整的类型,则不允许这样做。

foo8可以使用 VLA 或非 VLA 调用。

尽管数组声明器具有可变修改的类型,但是在计算参数类型之前进行了数组 T到T指针的调整。SoT a[n]被调整T *a为不可变修改的;但是void foo9(int n, T a[][n]);确实会T (*)[n]产生a.


restrict与数组声明器结合的最简单方法是实际使用指针形式,这里:

void foo8(int n, T *restrict a ) {

该尝试void foo8(int n, T restrict a[]);不起作用,因为它等效于void foo8(int n, T restrict *a);. Therestrict是一个限定符,它的行为在语法上与其他限定符相同,例如const.

正如 Jonathan Leffler 所指出的,还有一种替代语法:

void foo8(int n, T a[restrict]) {   // n is optional , as before

在这种情况下,允许以两种不同的方式指定相同的事物似乎是多余的,但是也存在static只能与数组声明符(而不是指针声明符)一起使用的情况。如果你想使用这种形式的staticand ,restrict那么别无选择,只能restrict在方括号内:

void foo8(int n, T a[restrict static n]) { 

需要明确的是,最后一种情况仍然不是可变修改类型;thestatic是一个承诺a它是一个指针,指向一个至少包含元素的数组的第一个n元素。

此外,在static调用函数时不需要在编译时检查(当然如果编译器确实强制执行会很好)。

最后一点:restrict在原型中可能没有效果,它只在函数定义中有意义。

于 2015-03-17T01:45:57.833 回答