如何使用g++向量化我的循环?

15

我在搜索时找到的介绍链接:

  1. 6.59.14 循环特定的 Pragmas
  2. 2.100 Pragma Loop_Optimize
  3. 如何告诉gcc循环次数
  4. 告诉gcc特定展开一个循环
  5. 如何在C++中强制使用向量化

正如您所看到的,它们大多是为C而设计的,但我认为它们在C++上也可能有效。 这是我的代码:

template<typename T>
//__attribute__((optimize("unroll-loops")))
//__attribute__ ((pure))
void foo(std::vector<T> &p1, size_t start,
            size_t end, const std::vector<T> &p2) {
  typename std::vector<T>::const_iterator it2 = p2.begin();
  //#pragma simd
  //#pragma omp parallel for
  //#pragma GCC ivdep Unroll Vector
  for (size_t i = start; i < end; ++i, ++it2) {
    p1[i] = p1[i] - *it2;
    p1[i] += 1;
  }
}

int main()
{
    size_t n;
    double x,y;
    n = 12800000;
    vector<double> v,u;
    for(size_t i=0; i<n; ++i) {
        x = i;
        y = i - 1;
        v.push_back(x);
        u.push_back(y);
    }
    using namespace std::chrono;

    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    foo(v,0,n,u);
    high_resolution_clock::time_point t2 = high_resolution_clock::now();

    duration<double> time_span = duration_cast<duration<double>>(t2 - t1);

    std::cout << "It took me " << time_span.count() << " seconds.";
    std::cout << std::endl;
    return 0;
}

我尝试了上面所有被评论的提示,但是没有获得任何加速效果,如下所示(第一次运行时未注释掉此#pragma GCC ivdep Unroll Vector):

samaras@samaras-A15:~/Downloads$ g++ test.cpp -O3 -std=c++0x -funroll-loops -ftree-vectorize -o test
samaras@samaras-A15:~/Downloads$ ./test
It took me 0.026575 seconds.
samaras@samaras-A15:~/Downloads$ g++ test.cpp -O3 -std=c++0x -o test
samaras@samaras-A15:~/Downloads$ ./test
It took me 0.0252697 seconds.

有希望吗?还是优化标志O3就可以解决问题了?欢迎提出任何加速此代码(foo函数)的建议!

我的g++版本:

samaras@samaras-A15:~/Downloads$ g++ --version
g++ (Ubuntu 4.8.1-2ubuntu1~12.04) 4.8.1

注意循环体是随机的。我不想以其他形式重新编写它。


编辑

回答说没有其他更多的事情可以做也是可以接受的!


你有没有查看汇编代码,看看是否在 -O3 下已经进行了向量化? - Mysticial
哎呀,我还没有。我将通过查看这个问题来达成目标:https://dev59.com/sHM_5IYBdhLWcg3wp00X Mysticial 建议不错! - gsamaras
@Mysticial 或许 David 给出的答案使得阅读汇编代码变得不必要了? - gsamaras
不使用别名的意思是它们肯定不同吗?我发布的一个链接中描述了“ivdep”提示,但我不确定那是否回答了你的问题@Mysticial。 - gsamaras
我尝试阅读汇编代码,但是得到的结果与我在评论中发布的链接非常不同。输出结果太大了。 - gsamaras
显示剩余2条评论
2个回答

15
O3标志自动开启-ftree-vectorize。详见https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html

-O3开启所有由-O2指定的优化,同时还开启了-finline-functions-funswitch-loops-fpredictive-commoning-fgcse-after-reload-ftree-loop-vectorize-ftree-loop-distribute-patterns-ftree-slp-vectorize-fvect-cost-model-ftree-partial-pre-fipa-cp-clone选项。

所以在这两种情况下编译器都尝试进行循环向量化。

使用g++ 4.8.2编译:

# In newer versions of GCC use -fopt-info-vec-missed instead of -ftree-vectorize
g++ test.cpp -O2 -std=c++0x -funroll-loops -ftree-vectorize -ftree-vectorizer-verbose=1 -o test

给出这个:
Analyzing loop at test.cpp:16                                                                                                                                                                                                                                               
                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                
Vectorizing loop at test.cpp:16                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                                                                
test.cpp:16: note: create runtime check for data references *it2$_M_current_106 and *_39                                                                                                                                                                                    
test.cpp:16: note: created 1 versioning for alias checks.                                                                                                                                                                                                                   
                                                                                                                                                                                                                                                                                
test.cpp:16: note: LOOP VECTORIZED.                                                                                                                                                                                                                                         
Analyzing loop at test_old.cpp:29                                                                                                                                                                                                                                               
                                                                                                                                                                                                                                                                                
test.cpp:22: note: vectorized 1 loops in function.                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                                
test.cpp:18: note: Unroll loop 7 times                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                                
test.cpp:16: note: Unroll loop 7 times                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                                
test.cpp:28: note: Unroll loop 1 times  

未使用-ftree-vectorize标志进行编译:

g++ test.cpp -O2 -std=c++0x -funroll-loops -ftree-vectorizer-verbose=1 -o test

仅返回此内容:

test_old.cpp:16: note: Unroll loop 7 times

test_old.cpp:28: note: Unroll loop 1 times

第16行是循环函数的开始,因此编译器肯定会对其进行向量化。检查汇编代码也证实了这一点。

我似乎在当前使用的笔记本上得到了一些激进的缓存,这使得准确测量函数运行时间非常困难。

但是这里还有一些其他的尝试方法:

  • 使用__restrict__限定符告诉编译器数组之间没有重叠。

  • 使用__builtin_assume_aligned告诉编译器数组对齐(不可移植)

以下是我的代码结果(我删除了模板,因为您可能需要为不同的数据类型使用不同的对齐方式)。

#include <iostream>
#include <chrono>
#include <vector>

void foo( double * __restrict__ p1,
          double * __restrict__ p2,
          size_t start,
          size_t end )
{
  double* pA1 = static_cast<double*>(__builtin_assume_aligned(p1, 16));
  double* pA2 = static_cast<double*>(__builtin_assume_aligned(p2, 16));

  for (size_t i = start; i < end; ++i)
  {
      pA1[i] = pA1[i] - pA2[i];
      pA1[i] += 1;
  }
}

int main()
{
    size_t n;
    double x, y;
    n = 12800000;
    std::vector<double> v,u;

    for(size_t i=0; i<n; ++i) {
        x = i;
        y = i - 1;
        v.push_back(x);
        u.push_back(y);
    }

    using namespace std::chrono;

    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    foo(&v[0], &u[0], 0, n );
    high_resolution_clock::time_point t2 = high_resolution_clock::now();

    duration<double> time_span = duration_cast<duration<double>>(t2 - t1);

    std::cout << "It took me " << time_span.count() << " seconds.";
    std::cout << std::endl;

    return 0;
}

就像我说的那样,我一直有困难获得一致的时间测量,所以无法确认这是否会给您带来性能提升(或甚至可能会降低!)


没有区别!也许-unroll-loops已经被O2启用了,但我无法确认。如果您有其他建议,请使用编辑按钮(推荐:D)。 - gsamaras
嗯,我也试过了,没有任何区别,让我试试其他方法,看看能找到什么 :) - David Saxon
如果你没有任何新的东西,我可能会接受这个答案,但你必须让我知道! - gsamaras
抱歉,我时间不够了,但我还是很感兴趣。今晚我会再仔细看看 :) 因为之前我使用的是旧版本的gcc,没有注意到这一点,所以请暂时不要接受目前的答案。 - David Saxon
你的代码中的 16 常量是什么意思?你的意思是说 -ftree-vectorize 没有效果。另外,我应该如何编译你写的代码?用我编译的两种方式都没有加速。:/ 尽管如此,还是要给你点赞,因为你尝试得很好! - gsamaras

1

GCC有扩展编译器,可以创建使用SIMD指令的新原语。详情请参见这里

大多数编译器声称它们会自动向量化操作,但这取决于编译器的模式匹配,但正如您所想象的那样,这可能非常难以捉摸。


有趣,但我不确定应该传递什么大小的属性,你能指导我吗? - gsamaras
我认为许多架构都有128位SIMD寄存器,因此请确保您的所有对象都是128位宽。同时请记住,SIMD并不会加速数据加载和存储时间,它只会加速算术运算。 - doron
仍然不清楚我应该如何做。我受限于4个维度吗?一个例子会有所帮助。 - gsamaras

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