为什么GCC不会自动向量化这个循环?

5

我有以下C程序(这是我的实际用例的简化版本,展示了相同的行为)

#include <stdlib.h>
#include <math.h>
int main(int argc, char ** argv) {
    const float * __restrict__ const input = malloc(20000*sizeof(float));
    float * __restrict__ const output = malloc(20000*sizeof(float));

    unsigned int pos=0;
    while(1) {
            unsigned int rest=100;
            for(unsigned int i=pos;i<pos+rest; i++) {
                    output[i] = input[i] * 0.1;
            }

            pos+=rest;            
            if(pos>10000) {
                    break;
            }
    }
}

当我使用编译器

 -O3 -g -Wall -ftree-vectorizer-verbose=5 -msse -msse2 -msse3 -march=native -mtune=native --std=c99 -fPIC -ffast-math

我得到了输出。
main.c:10: note: not vectorized: unhandled data-ref 

内部for循环的行数为10。当我查找原因时,结果显示指针可能存在别名(alias),但在我的代码中它们没有别名,因为我使用了__restrict关键字。他们还建议包括-msse标志,但它们似乎也没有起作用。需要帮助吗?


什么版本的gcc?一个工作示例可能也很有用,当我尝试使用4.4.5时,矢量化的版本被修改了。 - ergosys
你能发一些编译通过的示例代码吗?当我填入一些虚拟值时,循环被向量化了... - Christoph
我已经更新了示例以使其可编译。但它仍无法进行向量化。我正在使用“gcc(Debian 4.4.5-10)4.4.5”。 - Jeremy Salwen
3个回答

3

这似乎是一个bug。在以下等效的函数中,当编译为x86-64目标时,foo()是矢量化的,但bar()不是:

void foo(const float * restrict input, float * restrict output)
{
    unsigned int pos;
    for (pos = 0; pos < 10100; pos++)
        output[pos] = input[pos] * 0.1;
}

void bar(const float * restrict input, float * restrict output)
{
    unsigned int pos;
    unsigned int i;
    for (pos = 0; pos <= 10000; pos += 100)
        for (i = 0; i < 100; i++)
            output[pos + i] = input[pos + i] * 0.1;
}

在编译时添加-m32标志,以将目标编译为x86架构,会导致这两个函数都被向量化。


1
谢谢!我在64位平台上!使用-m32可以完美地解决问题。我现在正在提交错误报告。其他答案很好,但实际上只是一些变通方法,因为这应该可以不做任何修改就能正常工作。 - Jeremy Salwen
请注意,32位可执行文件可能比未矢量化的64位文件慢得多,因此,除非您的目标纯粹是“使用SSE”,否则应对整个应用程序进行分析。 - Ben Jackson
谢谢Ben,实际上我并不是用这个来编译我的代码,而只是用来提交错误报告。通过稍微重新排列一下,我可以让它在64位上正确地进行向量化。 - Jeremy Salwen

2

它不喜欢外循环格式,这阻止了它理解内部循环。如果我将其折叠成单个循环,我可以让它向量化:

#include <stdlib.h>
#include <math.h>
int main(int argc, char ** argv) {
    const float * __restrict__ input = malloc(20000*sizeof(float));
    float * __restrict__ output = malloc(20000*sizeof(float));

    for(unsigned int i=0; i<=10100; i++) {
            output[i] = input[i] * 0.1f;
    }
}

请注意,我没有深入考虑如何将pos+rest限制正确地转换为单个for循环条件,这可能是错误的。

您可以通过将简化的内部循环放入一个函数中,并使用指针和计数进行调用来利用此功能。即使再次内联,它也可能正常工作。假设您删除了我刚刚简化掉但需要保留的while()循环的某些部分。


1

尝试:

const float * __restrict__ input = ...;
float * __restrict__ output = ...;

尝试通过改变一些东西来进行实验:

#include <stdlib.h>
#include <math.h>

int main(int argc, char ** argv) {

    const float * __restrict__ input = new float[20000];
    float * __restrict__  output = new float[20000];

    unsigned int pos=0;
    while(1) {
        unsigned int rest=100;
        output += pos;
        input += pos;
        for(unsigned int i=0;i<rest; ++i) {
            output[i] = input[i] * 0.1;
        }

        pos+=rest;
        if(pos>10000) {
            break;
        }
    }
}

g++ -O3 -g -Wall -ftree-vectorizer-verbose=7 -msse -msse2 -msse3 -c test.cpp

test.cpp:14: note: versioning for alias required: can't determine dependence between *D.4096_24 and *D.4095_21
test.cpp:14: note: mark for run-time aliasing test between *D.4096_24 and *D.4095_21
test.cpp:14: note: Alignment of access forced using versioning.
test.cpp:14: note: Vectorizing an unaligned access.
test.cpp:14: note: vect_model_load_cost: unaligned supported by hardware.
test.cpp:14: note: vect_model_load_cost: inside_cost = 2, outside_cost = 0 .
test.cpp:14: note: vect_model_simple_cost: inside_cost = 2, outside_cost = 0 .
test.cpp:14: note: vect_model_simple_cost: inside_cost = 2, outside_cost = 1 .
test.cpp:14: note: vect_model_simple_cost: inside_cost = 1, outside_cost = 0 .
test.cpp:14: note: vect_model_store_cost: inside_cost = 1, outside_cost = 0 .
test.cpp:14: note: cost model: Adding cost of checks for loop versioning to treat misalignment.

test.cpp:14: note: cost model: Adding cost of checks for loop versioning aliasing.

test.cpp:14: note: Cost model analysis:
  Vector inside of loop cost: 8
  Vector outside of loop cost: 6
  Scalar iteration cost: 5
  Scalar outside cost: 1
  prologue iterations: 0
  epilogue iterations: 0
  Calculated minimum iters for profitability: 2

test.cpp:14: note:   Profitability threshold = 3

test.cpp:14: note: Vectorization may not be profitable.
test.cpp:14: note: create runtime check for data references *D.4096_24 and *D.4095_21
test.cpp:14: note: created 1 versioning for alias checks.

test.cpp:14: note: LOOP VECTORIZED.
test.cpp:4: note: vectorized 1 loops in function.

Compilation finished at Wed Feb 16 19:17:59

这背后的原理是什么? - Oliver Charlesworth
@Oli 只是猜测,也许他的编译器不喜欢额外的const或__restrict形式。 - Anycorn
@Jeremy,请看我的更新,它似乎不喜欢运行时开始绑定。我猜它认为pos可能以某种方式被别名化了。 - Anycorn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接