c - 让编译器以合理的方式自动向量化代码

标签 c optimization fortran vectorization avx

我正在尝试弄清楚如何构建数值模拟的主循环代码,使编译器能够以紧凑的方式生成很好的矢量化指令。

这个问题最容易用 C 伪代码来解释,但我也有一个受同类问题影响的 Fortran 版本。考虑以下循环,其中 lots_of_code_* 是一些复杂的表达式,它会产生相当数量的机器指令。

void process(const double *in_arr, double *out_arr, int len)
{
    for (int i = 0; i < len; i++)
    {
        const double a = lots_of_code_a(i, in_arr);
        const double b = lots_of_code_b(i, in_arr);
        ...
        const double z = lots_of_code_z(i, in_arr);

        out_arr[i] = final_expr(a, b, ..., z);
    }
}

当使用 AVX 目标进行编译时,英特尔编译器生成如下代码

process:
    AVX_loop
    AVX_code_a
    AVX_code_b
    ...
    AVX_code_z
    AVX_final_expr
    ...
    SSE_loop
    SSE_instructions
    ...
    scalar_loop
    scalar_instructions
    ...

生成的二进制文件已经相当大了。不过,我的实际计算循环看起来更像以下内容:

void process(const double *in_arr1, ... , const double *in_arr30, 
             double *out_arr1, ... double *out_arr30,
             int len) 
{
    for (int i = 0; i < len; i++)
    {
        const double a1 = lots_of_code_a(i, in_arr1);
        ...
        const double a30 = lots_of_code_a(i, in_arr30);

        const double b1 = lots_of_code_b(i, in_arr1);
        ...
        const double b30 = lots_of_code_b(i, in_arr30);

        ...
        ...

        const double z1 = lots_of_code_z(i, in_arr1);
        ...
        const double z30 = lots_of_code_z(i, in_arr30);

        out_arr1[i] = final_expr1(a1, ..., z1);
        ...
        out_arr30[i] = final_expr30(a30, ..., z30);
    }
}

这确实会产生非常大的二进制文件(Fortran 版本为 400KB,C99 版本为 800KB)。如果我现在将 lots_of_code_* 定义为函数,那么每个函数都会变成非矢量化代码。每当编译器决定内联一个函数时,它都会对其进行矢量化,但似乎每次都会复制代码。

在我看来,理想的代码应该是这样的:

AVX_lots_of_code_a:
    AVX_code_a
AVX_lots_of_code_b:
    AVX_code_b
...
AVX_lots_of_code_z:
    AVX_code_z
SSE_lots_of_code_a:
    SSE_code_a
...
scalar_lots_of_code_a:
    scalar_code_a
...
...
process:
    AVX_loop
    call AVX_lots_of_code_a
    call AVX_lots_of_code_a
    ...
    SSE_loop
    call SSE_lots_of_code_a
    call SSE_lots_of_code_a
    ...
    scalar_loop
    call scalar_lots_of_code_a
    call scalar_lots_of_code_a
    ...

这显然会产生更小的代码,但仍然与完全内联版本一样优化。幸运的话,它甚至可能适合 L1。

显然,我可以使用内在函数或其他任何方式自己编写此代码,但是是否有可能让编译器通过“正常”源代码以上述方式自动进行矢量化?

我知道编译器可能永远不会为函数的每个向量化版本生成单独的符号,但我认为它仍然可以在 process 中内联每个函数一次并使用内部跳转重复相同的操作代码块,而不是为每个输入数组复制代码。

最佳答案

像你这样的问题的正式回答:

考虑使用支持 OpenMP4.0 SIMD(我没有说内联)的函数或等效的专有机制。在英特尔编译器或全新的 GCC4.9 中可用。

在此处查看更多详细信息:https://software.intel.com/en-us/node/522650

例子:

//Invoke this function from vectorized loop
#pragma omp declare simd
    int vfun(int x, int y)
    {
        return x*x+y*y;
    }

它将使您能够在不进行内联的情况下通过函数调用对循环进行矢量化处理,从而无需生成大量代码。 (我并没有真正详细地研究您的代码片段;相反,我以文本形式回答了您提出的问题)

关于c - 让编译器以合理的方式自动向量化代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26920602/

相关文章:

c - 如何在C语言中打印一个double类型的数字,最多保留一位小数?

r - 如何使用 R 最小化线性规划模型的设置成本

c++ - 从 fortran 写出二进制文件并用 C 读取

fortran - Fortran 未格式化文件中出现意外的 "padding"

MySQL ORDER BY 非常慢。如何优化?

fortran - 在 gdb 中对 gfortran 程序的数组越界设置断点

c - 仅使用 X11 lib 在 C 中截取窗口的屏幕截图

c - 如何将常量分配给指针?

c - 结构中数组的值仍在变化

c++ - g++ 重载运算符优化