【发布时间】:2018-04-21 05:49:37
【问题描述】:
这是一个问题,我担心在处理数组时在更好的性能和更清晰的代码(更好的抽象)之间进行选择。我试图将其提炼成一个玩具示例。
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 矩阵,本质上是一个二维向量列表,并就地对每个向量进行归一化。
明确的方法:
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 以使其优化到与 @987654330 中相同的水平@?
解决 cmets:
John Zwinck:我去做了基准测试。
normalize2()的速度要快得多(2.4 秒对 1.3 秒),但只有在我删除了assert宏或者我定义了NDEBUG时。这是-DNDEBUG的一个相当违反直觉的效果,不是吗?它会降低性能而不是提高性能。Max:证据既是我链接到的神螺栓输出,也是上述基准。我也对不能内联这两个函数的情况感兴趣(例如,因为它们位于单独的翻译单元中)。
Jarod42 和 bolov:这就是我一直在寻找的答案。由第一点中提到的基准确认。尽管如此,如果有人实现自己的
assert(这正是我在我的应用程序中所做的),了解这一点很重要。
【问题讨论】:
-
当你为实际输入运行时,哪个实际上更快?
-
需要一些证据来证明“编译器无法知道输入矩阵不是 n×1。”我希望编译器足够聪明,可以内联两个方法调用,使您的两个示例相同。
-
如果您的
assert使用类似于__assume()/__builtin_unreachable的内容,代码可以生成相同的程序集。 -
if (mat.cols() != 2) __builtin_unreachable (void); -
如果您可以牺牲一点准确性,您可以使用倒数平方根并乘以它。如果没有 AVX512,倒数平方根将不得不在浮点数上完成,因此转换的额外时间可能会扼杀这个想法。无论哪种方式,添加
-march=native。
标签: c++ performance abstraction