【发布时间】:2016-07-23 06:38:22
【问题描述】:
我正在将一些大量使用可变长度数组 (VLA) 的 C99 代码移植到 C++。
我用在堆上分配内存的数组类替换了 VLA(堆栈分配)。性能损失巨大,下降了 3.2 倍(参见下面的基准)。 我可以在 C++ 中使用哪些快速 VLA 替换?我的目标是在为 C++ 重写代码时尽量减少性能损失。
向我建议的一个想法是编写一个数组类,该类在该类中包含一个固定大小的存储(即可以堆栈分配)并将其用于小型数组,并自动切换到较大数组的堆分配.我的实现在帖子的末尾。它工作得相当好,但我仍然无法达到原始 C99 代码的性能。为了接近它,我必须将这个固定大小的存储(下面的MSL)增加到我不喜欢的大小。我不想在堆栈上分配太大的数组即使对于许多不需要它的小数组,因为我担心它会触发堆栈溢出。 C99 VLA 实际上不太容易出现这种情况,因为它永远不会使用比需要更多的存储空间。
我遇到了std::dynarray,但我的理解是它没有被标准接受(还没有?)。
我知道 clang 和 gcc 支持 C++ 中的 VLA,但我也需要它与 MSVC 一起使用。事实上,更好的可移植性是重写为 C++ 的主要目标之一(另一个目标是将原本是命令行工具的程序变成可重用的库)。
基准测试
MSL 指的是我切换到堆分配的数组大小。我对 1D 和 2D 数组使用不同的值。
原始 C99 代码:115 秒。
MSL = 0(即堆分配):367 秒 (3.2x)。
1D-MSL = 50,2D-MSL = 1000:187 秒 (1.63x)。
1D-MSL = 200,2D-MSL = 4000:143 秒 (1.24x)。
1D-MSL = 1000,2D-MSL = 20000:131 (1.14x)。
增加MSL 进一步提高性能,但最终程序将开始返回错误结果(我假设是由于堆栈溢出)。
这些基准测试是在 OS X 上使用 clang 3.7,但 gcc 5 显示的结果非常相似。
代码
这是我当前使用的“smallvector”实现。我需要一维和二维向量。我切换到大于MSL的堆分配。
template<typename T, size_t MSL=50>
class lad_vector {
const size_t len;
T sdata[MSL];
T *data;
public:
explicit lad_vector(size_t len_) : len(len_) {
if (len <= MSL)
data = &sdata[0];
else
data = new T[len];
}
~lad_vector() {
if (len > MSL)
delete [] data;
}
const T &operator [] (size_t i) const { return data[i]; }
T &operator [] (size_t i) { return data[i]; }
operator T * () { return data; }
};
template<typename T, size_t MSL=1000>
class lad_matrix {
const size_t rows, cols;
T sdata[MSL];
T *data;
public:
explicit lad_matrix(size_t rows_, size_t cols_) : rows(rows_), cols(cols_) {
if (rows*cols <= MSL)
data = &sdata[0];
else
data = new T[rows*cols];
}
~lad_matrix() {
if (rows*cols > MSL)
delete [] data;
}
T const * operator[] (size_t i) const { return &data[cols*i]; }
T * operator[] (size_t i) { return &data[cols*i]; }
};
【问题讨论】:
-
VLA 在开销方面无可替代。 VLA 的存储完全免费。事实上,在大多数情况下,它是完全免费的,超出了函数调用的现有开销。真的不能比 0% 的成本做得更好,所以如果 MSVC 没有 VLA,你别无选择,只能使用其他替代方案,用于 VLA,并受到性能影响。
-
如果您乐于使用“特定平台”,那么
GCC会将 VLA 作为扩展并在所有这些平台上运行。 -
还有
alloca(平台特有功能,但存在于Linux/Windows/OS X):man7.org/linux/man-pages/man3/alloca.3.html在栈上动态分配内存。 -
alloca需要在应该使用其堆栈的函数中调用。也就是说,不在向量类(或初始化列表)的构造函数中。类可以将指针作为构造函数参数,如lad_vector vec( (int*)alloca(10 * sizeof(int)), 10 );。也许为此制作一个宏(但不是内联函数),以获得类似lad_vector vec = MAKE_LADVECTOR(10);的语法 -
增加 MSL 会进一步提高性能,但最终程序将开始返回错误结果(我假设是由于堆栈溢出)。 我不明白堆栈溢出会如何给出你错误的结果。在任何健全的系统上,最坏的情况是你应该得到一个段错误。 (除非发生一些非常不寻常的事情,比如溢出太多以至于你最终会进入其他有效内存区域。)所以也许你应该寻找一个错误。
标签: c++ arrays performance variable-length-array stack-allocation