gpt4 book ai didi

c++ - SSE 性能 Eigen

转载 作者:太空狗 更新时间:2023-10-29 21:34:30 24 4
gpt4 key购买 nike

我正在尝试使用 Eigen 编写一些 SSE 代码,但我无法理解某些行为。

给定代码:

#ifndef EIGEN_DONT_VECTORIZE // Not needed with Intel C++ Compiler XE 15.0
#define EIGEN_VECTORIZE_SSE4_2
#define EIGEN_VECTORIZE_SSE4_1
#define EIGEN_VECTORIZE_SSSE3
#define EIGEN_VECTORIZE_SSE3
#endif

#include "stdafx.h"
#include <iostream>
#include <unsupported/Eigen/AlignedVector3>
#include <Eigen/StdVector>
#include <chrono>

int _tmain(int argc, _TCHAR* argv[]) {
static const int SIZE = 4000000;
EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE(1, 1, 1);
//EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> B_SSE(2, 2, 2);
//std::vector<Eigen::AlignedVector3<float>> C_SSE(SIZE, Eigen::AlignedVector3<float>(0,0,0));


EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE1(1, 1, 1);
EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE2(1, 1, 1);
EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE3(1, 1, 1);
EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE4(1, 1, 1);

EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> B_SSE(2, 2, 2);
EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> B_SSE_increment_unroll(16, 16, 16);

A_SSE2 += B_SSE;
A_SSE3 = A_SSE2 + B_SSE;
A_SSE4 = A_SSE3 + B_SSE;


std::vector<Eigen::AlignedVector3<float>> C_SSE(SIZE, Eigen::AlignedVector3<float>(0, 0, 0));

auto start2 = std::chrono::system_clock::now();

// no unroll
for (int iteration = 0; iteration < SIZE; ++iteration) {
A_SSE += B_SSE;
C_SSE[iteration] = A_SSE;
}

//// own unroll
//for (int iteration = 0; iteration < SIZE / 8; ++iteration){
// A_SSE1 += B_SSE_increment_unroll;
// A_SSE2 += B_SSE_increment_unroll;
// A_SSE3 += B_SSE_increment_unroll;
// A_SSE4 += B_SSE_increment_unroll;

// C_SSE[iteration * 2] = A_SSE1;
// C_SSE[iteration * 2 + 1] = A_SSE2;
// C_SSE[iteration * 2 + 2] = A_SSE3;
// C_SSE[iteration * 2 + 3] = A_SSE4;

//}

auto end2 = std::chrono::system_clock::now();
auto elapsed2 = end2 - start2;
std::cout << "Eigen aligned vector " << elapsed2.count() << '\n';

Eigen::Matrix3Xf A = Eigen::Matrix3Xf::Zero(3, SIZE);
Eigen::Vector3f B(3, 3, 3);
Eigen::Vector3f C(2, 2, 2);

auto start1 = std::chrono::system_clock::now();

for (int iteration = 0; iteration < SIZE; ++iteration) {
B += C;
A.col(iteration) = B;
}
auto end1 = std::chrono::system_clock::now();
auto elapsed1 = end1 - start1;
std::cout << "Eigen matrix " << elapsed1.count() << '\n';


float *pResult = (float*)_aligned_malloc(SIZE * sizeof(float) * 4, 16); // align to 16-byte for SSE
auto start3 = std::chrono::system_clock::now();

__m128 x;
__m128 xDelta = _mm_set1_ps(2.0f); // Set the xDelta to (4,4,4,4)
__m128 *pResultSSE = (__m128*) pResult;

x = _mm_set_ps(1.0f, 1.0f, 1.0f, 1.0f); // Set the initial values of x to (4,3,2,1)

for (int iteration = 0; iteration < SIZE; ++iteration)
{
x = _mm_add_ps(x, xDelta);
pResultSSE[iteration] = x;
}

auto end3 = std::chrono::system_clock::now();
auto elapsed3 = end3 - start3;
std::cout << "Own sse " << elapsed3.count() << '\n';

}

在我的电脑上时间似乎很奇怪

  • Eigen 对齐 vector 展开:20057
  • 未展开的特征对齐 vector :~120320
  • 特征矩阵:~120207(与 Align no unroll 相同)
  • 拥有上交所:160784

当我检查程序集时,对齐版本和 Own SSE 使用 addps movaps,但在我手动展开循环之前,我不会获得额外的性能,即使我在所有运行中都没有这样做 (50%),我也没有得到任何提升。具有特征矩阵的版本不使用 sse,实现相同的性能,内联汇编显示在 16 次迭代中展开。手动展开是否有影响?我们是否应该为 SSE 手动执行此操作,是否取决于 CPU 属性?

编辑:所以总结一下。 SSE 指令性能不佳,因为无法证明展开循环将保持与未展开循环相同的结果,因此它无法隐藏内存存储延迟。但是在汇编代码中,“单个”指令仅使用 1 个寄存器并在展开的循环中递增它。如果 SSE 上瘾是垂直执行的(对齐 vector 中的单个 float 累积相同数量的加法运算)编译器应该能够证明展开的相等性。默认情况下,SSE 操作是否未被编译器优化?如果展开循环保留执行顺序,那么保留非关联数学,自动展开应该是可能的,那么为什么它不会发生,以及如何强制编译器执行它?

编辑:按照建议我运行测试,但是来自 eigen 的 bench 单元在 visual studio 2017 下不起作用,所以它被替换为

#include <iostream>
#include <vector>
#include <unsupported/Eigen/AlignedVector3>
#include <chrono>
#include <numeric>

EIGEN_DONT_INLINE
void vector_no_unroll(std::vector<Eigen::AlignedVector3<float>>& out)
{
Eigen::AlignedVector3<float> A_SSE(1, 1, 1);
Eigen::AlignedVector3<float> B_SSE(2, 2, 2);
for (auto &x : out)
{
A_SSE += B_SSE;
x = A_SSE;
}
}

EIGEN_DONT_INLINE
void vector_unrolled(std::vector<Eigen::AlignedVector3<float>>& out)
{
Eigen::AlignedVector3<float> A_SSE1(1, 1, 1);
Eigen::AlignedVector3<float> A_SSE2(1, 1, 1);
Eigen::AlignedVector3<float> A_SSE3(1, 1, 1);
Eigen::AlignedVector3<float> A_SSE4(1, 1, 1);

Eigen::AlignedVector3<float> B_SSE(2, 2, 2);
Eigen::AlignedVector3<float> B_SSE_increment_unroll(16, 16, 16);

A_SSE2 += B_SSE;
A_SSE3 = A_SSE2 + B_SSE;
A_SSE4 = A_SSE3 + B_SSE;
for (size_t i = 0; i<out.size(); i += 4)
{
A_SSE1 += B_SSE_increment_unroll;
A_SSE2 += B_SSE_increment_unroll;
A_SSE3 += B_SSE_increment_unroll;
A_SSE4 += B_SSE_increment_unroll;
out[i + 0] = A_SSE1;
out[i + 1] = A_SSE2;
out[i + 2] = A_SSE3;
out[i + 3] = A_SSE4;
}
}

EIGEN_DONT_INLINE
void eigen_matrix(Eigen::Matrix3Xf& out)
{
Eigen::Vector3f B(1, 1, 1);
Eigen::Vector3f C(2, 2, 2);

for (int i = 0; i < out.cols(); ++i) {
B += C;
out.col(i) = B;
}
}

template<int unrolling> EIGEN_DONT_INLINE
void eigen_matrix_unrolled(Eigen::Matrix3Xf& out)
{
Eigen::Matrix<float, 3, unrolling> B = Eigen::Matrix<float, 1, unrolling>::LinSpaced(3.f, 1 + 2 * unrolling).template replicate<3, 1>();

for (int i = 0; i < out.cols(); i += unrolling) {
out.middleCols<unrolling>(i) = B;
B.array() += float(2 * unrolling);
}
}

int main() {
static const int SIZE = 4000000;

int tries = 30;
int rep = 10;


std::vector<int> Timings(tries, 0);
{
Eigen::Matrix3Xf A(3, SIZE);
#pragma loop( 1 )
for (int iter = 0; iter < tries; ++iter)
{
auto start1 = std::chrono::system_clock::now();
eigen_matrix(A);
Timings[iter] = (std::chrono::system_clock::now() - start1).count();
}
}
std::cout << "eigen matrix Min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n";
std::cout << "eigen matrix Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0) / tries << " ms\n";

{
Eigen::Matrix3Xf A(3, SIZE);
#pragma loop( 1 )
for (int iter = 0; iter < tries; ++iter)
{
auto start1 = std::chrono::system_clock::now();
eigen_matrix_unrolled<4>(A);
Timings[iter] = (std::chrono::system_clock::now() - start1).count();
}
}
std::cout << "eigen matrix unrolled 4 min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n";
std::cout << "eigen matrix unrolled 4 Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0) / tries << " ms\n";

{
Eigen::Matrix3Xf A(3, SIZE);
#pragma loop( 1 )
for (int iter = 0; iter < tries; ++iter)
{
auto start1 = std::chrono::system_clock::now();
eigen_matrix_unrolled<8>(A);
Timings[iter] = (std::chrono::system_clock::now() - start1).count();
}
}
std::cout << "eigen matrix unrolled 8 min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n";
std::cout << "eigen matrix unrolled 8 Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0) / tries << " ms\n";

{
std::vector<Eigen::AlignedVector3<float>> A(SIZE, Eigen::AlignedVector3<float>(0, 0, 0));
#pragma loop( 1 )
for (int iter = 0; iter < tries; ++iter)
{
auto start1 = std::chrono::system_clock::now();
vector_no_unroll(A);
Timings[iter] = (std::chrono::system_clock::now() - start1).count();
}
}
std::cout << "eigen vector min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n";
std::cout << "eigen vector Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0) / tries << " ms\n";

{
std::vector<Eigen::AlignedVector3<float>> A(SIZE, Eigen::AlignedVector3<float>(0, 0, 0));
#pragma loop( 1 )
for (int iter = 0; iter < tries; ++iter)
{
auto start1 = std::chrono::system_clock::now();
vector_unrolled(A);
Timings[iter] = (std::chrono::system_clock::now() - start1).count();
}
}
std::cout << "eigen vector unrolled min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n";
std::cout << "eigen vector unrolled Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0) / tries << " ms\n";

}

并在 8 台不同的机器(所有窗口)上检查结果并得到以下结果

特征矩阵最小值:110477 毫秒

特征矩阵均值:131691 毫秒

特征矩阵展开 4 分钟:40099 毫秒

特征矩阵展开 4 均值:54812 毫秒

特征矩阵展开 8 分钟:40001 毫秒

特征矩阵展开 8 均值:51482 毫秒

最小特征向量:100270 毫秒

特征向量平均值:117316 毫秒

特征向量展开最小值:59966 毫秒

特征向量展开平均值:65847 毫秒

在我测试的每台机器上,除了最旧的机器。看起来在新机器上小的展开可能是非常有益的(结果不同于 1.5 到 3.5 倍的加速 4 倍展开并且即使展开是 8、16、32 或 256 次也不会增加)。

最佳答案

您的计时非常不准确(多次运行您的代码时,我得到了很多变化)。为了获得更好的重现性,您应该多次运行每个变体并花费最少的时间。我使用作为 Eigen 一部分的 BenchUtils 组合了一个基准测试:

#include <iostream>
#include <unsupported/Eigen/AlignedVector3>
#include <bench/BenchUtil.h>

EIGEN_DONT_INLINE
void vector_no_unroll(std::vector<Eigen::AlignedVector3<float>>& out)
{
Eigen::AlignedVector3<float> A_SSE(1, 1, 1);
Eigen::AlignedVector3<float> B_SSE(2, 2, 2);
for(auto &x : out)
{
A_SSE += B_SSE;
x = A_SSE;
}
}

EIGEN_DONT_INLINE
void vector_unrolled(std::vector<Eigen::AlignedVector3<float>>& out)
{
Eigen::AlignedVector3<float> A_SSE1(1, 1, 1);
Eigen::AlignedVector3<float> A_SSE2(1, 1, 1);
Eigen::AlignedVector3<float> A_SSE3(1, 1, 1);
Eigen::AlignedVector3<float> A_SSE4(1, 1, 1);

Eigen::AlignedVector3<float> B_SSE(2, 2, 2);
Eigen::AlignedVector3<float> B_SSE_increment_unroll(16, 16, 16);

A_SSE2 += B_SSE;
A_SSE3 = A_SSE2 + B_SSE;
A_SSE4 = A_SSE3 + B_SSE;
for(size_t i=0; i<out.size(); i+=4)
{
A_SSE1 += B_SSE_increment_unroll;
A_SSE2 += B_SSE_increment_unroll;
A_SSE3 += B_SSE_increment_unroll;
A_SSE4 += B_SSE_increment_unroll;
out[i + 0] = A_SSE1;
out[i + 1] = A_SSE2;
out[i + 2] = A_SSE3;
out[i + 3] = A_SSE4;
}
}

EIGEN_DONT_INLINE
void eigen_matrix(Eigen::Matrix3Xf& out)
{
Eigen::Vector3f B(1, 1, 1);
Eigen::Vector3f C(2, 2, 2);

for (int i = 0; i < out.cols(); ++i) {
B += C;
out.col(i) = B;
}
}

template<int unrolling> EIGEN_DONT_INLINE
void eigen_matrix_unrolled(Eigen::Matrix3Xf& out)
{
Eigen::Matrix<float,3,unrolling> B = Eigen::Matrix<float, 1, unrolling>::LinSpaced(3.f, 1+2*unrolling).template replicate<3,1>();

for (int i = 0; i < out.cols(); i+=unrolling) {
out.middleCols<unrolling>(i) = B;
B.array() += float(2*unrolling);
}
}

int main() {
static const int SIZE = 4000000;

int tries = 10;
int rep = 10;
BenchTimer t;

std::cout.precision(4);
{
std::vector<Eigen::AlignedVector3<float>> A(SIZE, Eigen::AlignedVector3<float>(0, 0, 0));
BENCH(t, tries, rep, vector_no_unroll(A));
std::cout << "no unroll: " << 1e3*t.best(CPU_TIMER) << "ms\n";
}
{
std::vector<Eigen::AlignedVector3<float>> A(SIZE, Eigen::AlignedVector3<float>(0, 0, 0));
BENCH(t, tries, rep, vector_unrolled(A));
std::cout << "unrolled: " << 1e3*t.best(CPU_TIMER) << "ms\n";
}
{
Eigen::Matrix3Xf A(3, SIZE);
BENCH(t, tries, rep, eigen_matrix(A));
std::cout << "eigen matrix: " << 1e3*t.best(CPU_TIMER) << "ms\n";
}
{
Eigen::Matrix3Xf A(3, SIZE);
BENCH(t, tries, rep, eigen_matrix_unrolled<4>(A));
std::cout << "eigen unrd<4>: " << 1e3*t.best(CPU_TIMER) << "ms\n";
}
{
Eigen::Matrix3Xf A(3, SIZE);
BENCH(t, tries, rep, eigen_matrix_unrolled<8>(A));
std::cout << "eigen unrd<8>: " << 1e3*t.best(CPU_TIMER) << "ms\n";
}
}

我得到非常相似的时间,几乎独立于使用 -msse2-msse4.2-mavx2 进行编译:

no unroll:    66.72ms
unrolled: 66.83ms
eigen matrix: 57.56ms
eigen unrd<4>: 50.39ms
eigen unrd<8>: 51.19ms

值得注意的是,AligenedVector3 变体始终是最慢的,展开与否之间没有显着差异。矩阵变体大约需要 7/8 的时间,手动展开矩阵变体(每次迭代处理 4 或 8 列),将时间减少到原始时间的大约 3/4。

这表明内存带宽可能是所有矢量化变体的瓶颈。展开的矩阵变体可能会受到实际操作(或手动复制单个标量)的限制。

基准测试是在 Intel Core i5-4210U CPU @1.70GHz 上进行的,在 Ubuntu 16.04 上使用 g++5.4.1,最近检查了 Eigen 开发分支。

关于c++ - SSE 性能 Eigen ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46407332/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com