gpt4 book ai didi

c++ - 使用数组时的抽象与性能

转载 作者:行者123 更新时间:2023-11-30 03:27:38 26 4
gpt4 key购买 nike

这是一个关于我在处理数组时在更好的性能和更清晰的代码(更好的抽象)之间做出选择的担忧的问题。我试图将其提炼成一个玩具示例。

C++ 特别擅长允许抽象而不损害性能。问题是这在类似于下面的示例中是否可行。

考虑一个简单的任意大小的矩阵类,它使用连续的行优先存储:

#include <cmath>
#include <cassert>

class Matrix {
int nrow, ncol;
double *data;
public:
Matrix(int nrow, int ncol) : nrow(nrow), ncol(ncol), data(new double[nrow*ncol]) { }
~Matrix() { delete [] data; }

int rows() const { return nrow; }
int cols() const { return ncol; }

double & operator [] (int i) { return data[i]; }

double & operator () (int i, int j) { return data[i*ncol + j]; }
};

它有一个二维索引 operator () 使其易于使用。它也有用于连续访问的 operator [],但更好抽象的矩阵可能没有这个。

让我们实现一个函数,它接受一个 n×2 矩阵,本质上是一个二维 vector 列表,并就地对每个 vector 进行归一化。

清晰的方式:

inline double veclen(double x, double y) {
return std::sqrt(x*x + y*y);
}

void normalize(Matrix &mat) {
assert(mat.cols() == 2); // some kind of check for correct input
for (int i=0; i < mat.rows(); ++i) {
double norm = veclen(mat(i,0), mat(i,1));
mat(i,0) /= norm;
mat(i,1) /= norm;
}
}

快速但不太清晰的方法:

void normalize2(Matrix &mat) {
assert(mat.cols() == 2);
for (int i=0; i < mat.rows(); ++i) {
double norm = veclen(mat[2*i], mat[2*i+1]);
mat[2*i] /= norm;
mat[2*i+1] /= norm;
}
}

第二个版本 (normalize2) 有可能更快,因为它的编写方式明确表明循环的第二次迭代不会访问第一次计算的数据迭代。因此它可以更好地利用 SIMD 指令。 Looking at godbolt, this seems to be what happens (除非我误读了程序集)。

在第一个版本(normalize)中,编译器无法知道输入矩阵不是 n×1,这会导致重叠数组访问。

问题: 是否有可能以某种方式告诉编译器输入矩阵在 normalize() 中实际上是 n×2 以允许它优化为相同的水平与 normalize2() 中一样?


处理评论:

  • John Zwinck:我去做了基准测试。 normalize2() 相当快(2.4 对 1.3 秒),但仅当我删除了 assert 宏或如果我定义了 NDEBUG 。这是 -DNDEBUG 的一个相当违反直觉的效果,不是吗?它会降低性能而不是提高性能。

  • Max:证据是我链接到的 Godbolt 输出和上述基准。我也对无法内联这两个函数的情况感兴趣(例如,因为它们位于单独的翻译单元中)。

  • Jarod42 和 bolov:这就是我一直在寻找的答案。由第一点中提到的基准确认。不过,了解这一点很重要,以防有人实现自己的 assert(这正是我在我的应用程序中所做的)。

最佳答案

我相信模板可以让您同时获得性能和可读性。

通过模板化矩阵的大小(就像流行的数学库所做的那样),您可以让编译器在编译时知道很多信息。

我修改了一下你的小类:

template<int R, int C>
class Matrix {
double data[R * C] = {0.0};
public:
Matrix() = default;

int rows() const { return R; }
int cols() const { return C; }
int size() const { return R*C; }

double & operator [] (int i) { return data[i]; }

double & operator () (int row, int col) { return data[row*C + col]; }
};

inline double veclen(double x, double y) {
return std::sqrt(x*x + y*y);
}

template<int R>
void normalize(Matrix<R, 2> &mat) {
for (int i = 0; i < R; ++i) {
double norm = veclen(mat(i, 0), mat(i, 1));
mat(i, 0) /= norm;
mat(i, 1) /= norm;
}
}

template<int R>
void normalize2(Matrix<R, 2> &mat) {
for (int i = 0; i < R; ++i) {
double norm = veclen(mat[2 * i], mat[2 * i + 1]);
mat[2 * i] /= norm;
mat[2 * i + 1] /= norm;
}
}

我也更喜欢将数据作为普通成员(=没有指针),因此您可以在内存所在的矩阵构造期间选择(堆栈或堆)。

额外的好处是您现在可以在编译时确定规范化函数只接受 n×2 矩阵。

我没有在编译器资源管理器上测试我的代码,因为老实说我无法破译 asm.所以,是的,我声称我的版本更好但不确定 ;)

最后一句话:不要推出自己的矩阵,使用库,如 glm 或 eigen。

最后一句话²:如果您不知道选择什么,请选择可读性。

关于c++ - 使用数组时的抽象与性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47181445/

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