代码之家  ›  专栏  ›  技术社区  ›  Jeff Hammond

对C99可变长度数组(VLA)使用限制限定符

  •  4
  • Jeff Hammond  · 技术社区  · 11 年前

    我正在探索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情况不是自动矢量化的,但当我添加标志以断言无锯齿时 -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没有混叠?

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

    在本文中,ISO C指的是C的最新版本,当然,在撰写本文时是C11。

    2 回复  |  直到 11 年前
        1
  •  5
  •   Jonathan Leffler    11 年前

    你的原始代码对我来说很失败,比如:

     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);
        }
    }
    
        2
  •  4
  •   Community Mohan Dere    9 年前

    此代码中的所有参数都没有可变修改的类型。 foo6 和 foo7 除了 int n 看见 Why do C and C++ compilers allow array lengths in function signatures when they're never enforced? .

    这些都完全相同:

    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 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); 这个 限制 是一个限定符,其语法行为与其他限定符(如 const .

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

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

    然而,在这种情况下,允许以两种不同的方式指定同一事物似乎是多余的 there also exists static 它只能与数组声明符(而不是指针声明符)一起使用。如果要使用此形式的 静止的 而且 限制 那就别无选择了 限制 方括号内:

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

    要清楚的是,最后一种情况仍然不是可变修改的类型;这个 静止的 是一个承诺 一 , 这是一个指针 ,指向至少 n 元素。

    此外 静止的 在调用函数时不需要在编译时进行检查(当然,如果编译器确实执行了这一点也会很好)。

    最后一点: 限制 在原型中 probably has no effect ,它只在函数定义中有意义。

    推荐文章