我正在将一些大量使用可变长度数组 (VLA) 的 C99 代码移植到 C++。
我用在堆上分配内存的数组类替换了 VLA(堆栈分配)。性能损失巨大,下降了 3.2 倍(参见下面的基准)。 我可以在 C++ 中使用什么快速 VLA 替换?我的目标是在为 C++ 重写代码时最大程度地减少性能损失。
向我建议的一个想法是编写一个数组类,该类在该类中包含一个固定大小的存储(即可以堆栈分配)并将其用于小型数组,并自动切换到较大数组的堆分配。我的实现在帖子的末尾。它工作得相当好,但我仍然无法达到原始 C99 代码的性能。为了接近它,我必须将这个固定大小的存储(MSL
下面)增加到我不喜欢的大小。即使对于许多不需要它的小数组,我也不想在堆栈上分配太大的数组,因为我担心它会触发堆栈溢出。C99 VLA 实际上不太容易出现这种情况,因为它永远不会使用比需要更多的存储空间。
我遇到了std::dynarray
,但我的理解是它没有被标准接受(还没有?)。
我知道 clang 和 gcc 支持 C++ 中的 VLA,但我也需要它与 MSVC 一起使用。事实上,更好的可移植性是重写为 C++ 的主要目标之一(另一个目标是将原本是命令行工具的程序变成可重用的库)。
基准
MSL
指的是我切换到堆分配的数组大小。我对一维和二维数组使用不同的值。
原始 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”实现。我需要一维和二维向量。我切换到 size 以上的堆分配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]; }
};