我正在移植一些C99代码,这些代码大量使用可变长度数组(VLA)到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显示了非常相似的结果.
这是我使用的当前"小向量"实现.我需要1D和2D矢量.我切换到大小超过堆分配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 …Run Code Online (Sandbox Code Playgroud) c++ arrays performance variable-length-array stack-allocation
考虑以下示例:
struct vector {
int size() const;
bool empty() const;
};
bool vector::empty() const
{
return size() == 0;
}
Run Code Online (Sandbox Code Playgroud)
生成的汇编代码vector::empty(通过 clang,经过优化):
push rax
call vector::size() const
test eax, eax
sete al
pop rcx
ret
Run Code Online (Sandbox Code Playgroud)
为什么要分配堆栈空间?它根本没有被使用。该push和pop可以省略。MSVC 和 gcc 的优化构建也为此功能使用堆栈空间(请参阅有关Godbolt 的内容),因此必须有一个原因。
从Java 6的某个地方开始,Hotspot JVM可以进行转义分析并在堆栈上而不是在垃圾收集堆上分配非转义对象.这导致生成的代码加速并减少垃圾收集器的压力.
Hotspot何时能够堆叠分配对象的规则是什么?换句话说,我什么时候可以依靠它来进行堆栈分配?
编辑:这个问题是重复的,但是(IMO)下面的答案比原始问题提供的答案更好.
在大约十年前的一个项目中,我们发现std::vector动态分配导致了严重的性能损失.在这种情况下,它分配了许多小向量,因此快速解决方案是编写一个类似于向量的类,包装在基于堆栈的预分配char数组中,用作其容量的原始存储.结果是static_vector<typename T, std::size_t Max>.如果您了解一些基础知识,这样的事情很容易写,你可以在网上找到很多这样的野兽.事实上,现在也有一个提升.
现在在嵌入式平台上工作,我们碰巧需要一个static_basic_string.这将是一个字符串,它预先在堆栈上分配固定的最大内存量,并将其用作容量.
起初我认为这应该相当容易(static_vector毕竟它可以基于现有的),但再看看std::basic_string界面我不再那么肯定了.它比std::vector界面更复杂.特别是实现find()功能系列std::basic_string不仅仅是一项繁琐的工作.
这让我再次思考.毕竟,这就是创建分配器的原因:基于new和delete使用其他方法替换分配.但是,要说分配器接口不实用将是轻描淡写.有一些文章在那里解释它,但有一个原因,我在过去的15年中看到这么少的本土分配器.
所以这是我的问题:
如果你必须实现一个basic_string相似的,你会怎么做?
static_basic_string?std::basic_string?与往常一样,对我们来说存在相当重要的限制:在嵌入式平台上,我们与GCC 4.1.2绑定,因此我们只能使用C++ 03,TR1和boost 1.52.
我有以下代码:
extern void func1(char *array);
extern void func2(char *array);
void myfunction(void) {
if (somecondition) {
char var2[256];
func2(var2);
}
if (someothercondition) {
{
char var3[3];
func3(var3);
}
}
Run Code Online (Sandbox Code Playgroud)
我无法让编译器 (gcc)只使用堆栈中func3使用的 3 个字节进行调用。它总是为调用范围中使用的变量var3分配空间,即使该变量超出范围并且可以安全地从堆栈中删除。var2func2
我尝试了在 gcc 文档中找到的几个选项:
fomit-frame-pointerfconserve-stack我已经在 x86 和 ARM 架构上尝试过了,它的行为是相同的。
这个问题背后的基本原理是仅使用必要的堆栈大小。
以及相关的问题:如果可以优化堆栈空间,是否可以在选项生成的.su文件中添加每个子函数调用使用的堆栈-fstack-usage。
谢谢
HotSpot Java 9-13 的堆栈分配优化规则是否不太严格?
在 Java 7 和 Java 8 HotSpot 中,对象的堆栈分配(由于称为标量对象替换的 JVM 优化)是可能的,但要在线程堆栈上实现无垃圾分配,必须满足许多严格的约束。
针对 Java 8(HotSpot、OpenJDK 8)启用堆栈分配优化的规则总结在这个问题的精彩回答中: Hotspot 什么时候可以在堆栈上分配对象? 至于 Java 7,这里: Eligibility for escapeanalysis/stack allocate with Java 7
在这个问题中,我想了解较新的 JDK 版本中 EA 和堆栈分配优化的当前状态是什么?其他 Java VM(例如 OpenJ9)是否有不同的规则?
java jvm compiler-optimization escape-analysis stack-allocation
是为了避免碎片化吗?还是其他什么原因?与malloc()手动生命周期相比,内存分配的设置生命周期是一个非常有用的构造。
我认为在Common Lisp cons的大多数实现中,通常/总是堆分配(请参阅为什么Lisp中的consing缓慢?)
Common Lisp确实提供了一个从函数返回多个值的工具(values在返回时和multiple-value-bind在调用站点时使用).我在这里猜测一下,但我认为这种结构的动机是双重的:1)使函数truncate更容易在典型的情况下使用,你不关心丢弃的值和2)使它成为可能返回多个值而根本不使用堆分配的数据结构(取决于实现(?))完全避免堆(以及后来的GC开销).
Common Lisp(或者像SBCL这样的特定实现)是否能够使用堆栈分配的数据(可能与弱引用一起使用)或创建复合/大型值(类似于C中的结构)?
我目前正在处理向量,并试图确保我在堆栈上拥有本质上是向量数组的内容。我无法调用,Vec::into_boxed_slice因为我正在动态分配Vec. 这是可能吗?
阅读了关于如何实现的VecRustonomicon 后,它似乎跨越了堆上的指针,在每个条目处取消引用。我想将Vec堆中的条目分块到堆栈中以便快速访问。
c++ ×3
c ×2
java ×2
jvm ×2
abi ×1
allocator ×1
arrays ×1
common-lisp ×1
gcc ×1
heap-memory ×1
jvm-hotspot ×1
performance ×1
rust ×1
string ×1
vector ×1