这个问题听起来很基础,但这是我与另一位与我合作的开发人员进行的辩论。

我一直在小心翼翼地在可能的地方分配东西,而不是堆分配东西。 他在和我说话,看着我的肩膀,并评论说这是不必要的,因为它们是相同的表现。

我一直给人的印象是,增长堆栈是固定时间的,堆分配的性能取决于分配(查找适当大小的孔)和取消分配(收缩孔以减少碎片)的堆的当前复杂度,因为如果我没有记错的话,许多标准库的实现在删除过程中需要花费一些时间。

这让我感到震惊,因为它可能与编译器非常相关。 特别是对于这个项目,我正在使用Metrowerks编译器作为PPC体系结构。 深入了解这种组合将是最有帮助的,但是对于GCC和MSVC ++,通常情况是什么? 堆分配的性能不如堆栈分配高吗? 没有区别吗? 还是差异如此之细,以至于无法进行微优化。


#1楼

您可以为性能非常好的特定大小的对象编写特殊的堆分配器。 但是, 一般堆分配器的性能不是特别好。

我也同意TorbjörnGyllebring关于物体的预期寿命。 好点子!


#2楼

堆栈分配要快得多,因为它真正要做的只是移动堆栈指针。 使用内存池,您可以从堆分配中获得可比的性能,但这会带来一点点复杂性和麻烦。

而且,堆栈与堆不仅是性能方面的考虑; 它还告诉您很多有关对象的预期寿命的信息。


#3楼

我不认为堆栈分配和堆分配通常是可互换的。 我也希望它们两者的性能足以通用。

我强烈建议您购买小件物品,无论哪种物品都更适合分配范围。 对于大型项目,可能需要堆。

在具有多个线程的32位操作系统上,堆栈通常受到相当大的限制(尽管通常至少为几个mb),因为需要划分地址空间并且一个或多个线程迟早会运行到另一个线程中。 在单线程系统(无论如何还是Linux glibc单线程)上,该限制要少得多,因为堆栈可以不断增长。

在64位操作系统上,有足够的地址空间来使线程堆栈很大。


#4楼

堆栈要快得多。 实际上,在大多数架构上,例如在x86上,它仅使用一条指令:

sub esp, 0x10

(这会将堆栈指针向下移动0x10字节,从而“分配”这些字节供变量使用。)

当然,堆栈的大小非常非常有限,因为您会很快发现是否过度使用了堆栈分配或尝试进行递归:-)

另外,没有必要优化不需要验证的代码的性能,例如通过性能分析证明。 “过早的优化”通常会引起更多的问题,而不是值得的。

我的经验法则:如果我知道在编译时需要一些数据,并且它的大小不足几百个字节,则可以对其进行堆栈分配。 否则,我会对其进行堆分配。


#5楼

通常,堆栈分配仅包括从堆栈指针寄存器中减去。 这比搜索堆快了很多吨。

有时堆栈分配需要添加虚拟内存页面。 添加零内存的新页面不需要从磁盘读取页面,因此通常这仍然比搜索堆快很多(特别是如果部分堆也被页面调出)。 在极少数情况下,您可以构造一个示例,堆中恰好有足够的空间可用,而该堆已经在RAM中,但是为堆栈分配新页必须等待其他页被写出到磁盘。 在那种罕见的情况下,堆会更快。


#6楼

我认为生命周期至关重要,是否必须以复杂的方式构造要分配的事物。 例如,在事务驱动的建模中,通常必须填写事务结构并将其传递给具有一系列字段的操作函数。 以OSCI SystemC TLM-​​2.0标准为例。

由于构造昂贵,将这些分配在靠近操作的堆栈上往往会导致巨大的开销。 好的方法是通过池或简单的策略(例如“此模块永远只需要一个事务对象”)在堆上分配并重用事务对象。

这比在每个操作调用上分配对象快许多倍。

原因很简单,因为该对象具有昂贵的构造和相当长的使用寿命。

我会说:尝试两者,看看哪种方法最适合您,因为它实际上取决于代码的行为。


#7楼

与堆分配相比,堆分配的最大问题可能是,在一般情况下堆分配是无限制的操作,因此,在计时很重要的情况下,您不能使用它。

对于其他时间无关紧要的应用程序,这可能没什么大不了的,但是如果您堆很多,这会影响执行速度。 始终尝试将堆栈用于寿命短且经常分配的内存(例如在循环中),并尽可能长地使用-在应用程序启动期间进行堆分配。


#8楼

除了数量级性能优于堆分配之外,对于长时间运行的服务器应用程序,堆栈分配更可取。 甚至最好的托管堆最终也会变得分散,以至于应用程序性能下降。


#9楼

堆栈的容量有限,而堆栈没有。 进程或线程的典型堆栈约为8K。 分配大小后便无法更改。

堆栈变量遵循作用域规则,而堆变量则不遵循。 如果指令指针超出函数范围,则与该函数关联的所有新变量都将消失。

最重要的是,您无法预先预测整个函数调用链。 因此,仅分配200个字节可能会引起堆栈溢出。 如果您要编写的是库而不是应用程序,那么这尤其重要。


#10楼

老实说,编写一个程序来比较性能很简单:

#include <ctime>
#include <iostream>namespace {class empty { }; // even empty classes take up 1 byte of space, minimum
}int main()
{std::clock_t start = std::clock();for (int i = 0; i < 100000; ++i)empty e;std::clock_t duration = std::clock() - start;std::cout << "stack allocation took " << duration << " clock ticks\n";start = std::clock();for (int i = 0; i < 100000; ++i) {empty* e = new empty;delete e;};duration = std::clock() - start;std::cout << "heap allocation took " << duration << " clock ticks\n";
}

有人说愚蠢的一致性是小头脑的妖精 。 显然,优化编译器是许多程序员的专精。 该讨论曾经是答案的底部,但是显然人们不愿意花那么多时间来阅读它,因此,我将其移至此处以避免收到已经回答的问题。

优化的编译器可能会注意到此代码不执行任何操作,并且可能会对其进行优化。 做这样的事情是优化器的工作,而与优化器抗争是傻子的事。

我建议在关闭优化的情况下编译此代码,因为没有很好的方法来欺骗当前正在使用或将来将要使用的每个优化器。

任何打开优化器然后抱怨与优化器抗争的人都应该受到公众的嘲笑。

如果我关心纳秒级精度,则不会使用std::clock() 。 如果我想将结果发表为博士学位论文,那么我会做更多的事情,并且我可能会比较GCC,Tendra / Ten15,LLVM,Watcom,Borland,Visual C ++,Digital Mars,ICC和其他编译器。 实际上,堆分配比栈分配要花费数百倍的时间,而且我认为进一步研究这个问题没有任何用处。

优化器的任务是摆脱我正在测试的代码。 我看不出有任何理由告诉优化器运行,然后尝试使优化器欺骗而不进行实际优化。 但是,如果我认为这样做有价值,那么我将执行以下一项或多项操作:

  1. 将数据成员添加到empty ,并在循环中访问该数据成员; 但是,如果我只从数据成员中读取数据,则优化器可以进行不断折叠并删除循环; 如果我只写过数据成员,则优化器可能会跳过循环的最后一个迭代,而不是最后一个迭代。 另外,问题不是“堆栈分配和数据访问与堆分配和数据访问”。

  2. 声明e volatile , 但volatile往往是不正确编译 (PDF)。

  3. e的地址放入循环内(并可能将其分配给声明为extern并在另一个文件中定义的变量)。 但是即使在这种情况下,编译器也可能会注意到-至少在堆栈上e总是分配在相同的内存地址,然后像上面的(1)一样进行常量折叠。 我得到了循环的所有迭代,但是从未真正分配对象。

除了显而易见的以外,该测试还存在缺陷,因为它可以同时测量分配和释放,并且最初的问题并未询问释放。 当然,在堆栈上分配的变量会在其作用域的末尾自动释放,因此不调用delete会(1)使数字倾斜(堆栈释放会包含在有关堆栈分配的数字中,因此仅能衡量堆释放) (2)造成非常严重的内存泄漏,除非我们保留对新指针的引用并在进行时间测量后调用delete

在我的机器上,在Windows上使用g ++ 3.4.4,对于小于100000分配的任何内容,对于堆栈和堆分配,我都会获得“ 0时钟滴答”,即使这样,对于堆栈分配和“ 15时钟滴答”也将获得“ 0时钟滴答”。 ”进行堆分配。 当我测量10,000,000个分配时,堆栈分配需要31个时钟滴答,堆分配需要1562个时钟滴答。


是的,优化编译器可能会忽略创建空对象。 如果我理解正确,它甚至可能会漏掉整个第一个循环。 当我将迭代次数提高到10,000,000时,堆栈分配花费了31个时钟周期,堆分配花费了1562个时钟周期。 我认为可以肯定地说,在没有告诉g ++优化可执行文件的情况下,g ++并没有忽略构造函数。


自从我写这篇文章以来,多年来,Stack Overflow一直偏向于发布经过优化的版本的性能。 总的来说,我认为这是正确的。 但是,我仍然认为在您实际上不希望优化代码时要求编译器优化代码是很愚蠢的。 我觉得这与为代客停车支付额外费用非常相似,但拒绝交出钥匙。 在这种情况下,我不希望优化程序运行。

使用经过稍微修改的基准测试版本(以解决原始程序每次都没有通过循环在堆栈上分配某些东西的有效点),并进行编译而不进行优化,而是链接到发行版库(以解决我们不希望使用的有效点不想包含任何由于链接到调试库而导致的速度下降):

#include <cstdio>
#include <chrono>namespace {void on_stack(){int i;}void on_heap(){int* i = new int;delete i;}
}int main()
{auto begin = std::chrono::system_clock::now();for (int i = 0; i < 1000000000; ++i)on_stack();auto end = std::chrono::system_clock::now();std::printf("on_stack took %f seconds\n", std::chrono::duration<double>(end - begin).count());begin = std::chrono::system_clock::now();for (int i = 0; i < 1000000000; ++i)on_heap();end = std::chrono::system_clock::now();std::printf("on_heap took %f seconds\n", std::chrono::duration<double>(end - begin).count());return 0;
}

显示:

on_stack took 2.070003 seconds
on_heap took 57.980081 seconds

在我的系统上使用命令行cl foo.cc /Od /MT /EHsc

您可能不同意我获取未优化构建的方法。 很好:随时随地修改基准。 启用优化后,我得到:

on_stack took 0.000000 seconds
on_heap took 51.608723 seconds

并不是因为堆栈分配实际上是瞬时的,而是因为任何半透明的编译器都可以注意到on_stack并没有做任何有用的事情,并且可以进行优化。 我的Linux笔记本电脑上的GCC还注意到on_heap并没有做任何有用的事情,并对其进行了优化:

on_stack took 0.000003 seconds
on_heap took 0.000002 seconds

#11楼

不是jsut堆栈分配更快。 使用堆栈变量也可以赢得很多好处。 它们具有更好的参考位置。 最后,重新分配也便宜得多。


#12楼

请注意,选择堆栈分配与堆分配时,通常不考虑速度和性能。 堆栈就像堆栈一样,这意味着它非常适合推入块,然后以后进先出的方式再次弹出。 程序的执行也是堆栈式的,最后输入的程序首先退出。 在大多数编程语言中,过程中所需的所有变量仅在过程执行期间可见,因此在进入过程时将其压入,并在退出或返回时将其弹出堆栈。

现在来看一个不能使用堆栈的示例:

Proc P
{pointer x;Proc S{pointer y;y = allocate_some_data();x = y;}
}

如果您在过程S中分配了一些内存并将其放在堆栈上,然后退出S,则分配的数据将从堆栈中弹出。 但是P中的变量x也指向该数据,因此x现在指向具有未知内容的堆栈指针下方的某个位置(假定堆栈向下增长)。 如果只是向上移动堆栈指针而不清除其下面的数据,则内容可能仍然存在,但是如果您开始在堆栈上分配新数据,则指针x实际上可能指向该新数据。


#13楼

我想说的是,实际上GCC生成的代码(我也记得VS) 没有进行堆栈分配的开销

说以下功能:

  int f(int i){if (i > 0){   int array[1000];}   }

以下是生成的代码:

  __Z1fi:Leh_func_begin1:pushq   %rbpLtmp0:movq    %rsp, %rbpLtmp1:subq    $**3880**, %rsp <--- here we have the array allocated, even the if doesn't excited.Ltmp2:movl    %edi, -4(%rbp)movl    -8(%rbp), %eaxaddq    $3880, %rsppopq    %rbpret Leh_func_end1:

因此,无论您有多少局部变量(即使在if或switch内),也只有3880会更改为另一个值。 除非您没有局部变量,否则只需要执行此指令即可。 因此分配局部变量没有开销。


#14楼

class Foo {
public:Foo(int a) {}
}
int func() {int a1, a2;std::cin >> a1;std::cin >> a2;Foo f1(a1);__asm push a1;__asm lea ecx, [this];__asm call Foo::Foo(int);Foo* f2 = new Foo(a2);__asm push sizeof(Foo);__asm call operator new;//there's a lot instruction here(depends on system)__asm push a2;__asm call Foo::Foo(int);delete f2;
}

在asm中就是这样。 当您使用funcf1和指针f2已分配在堆栈上(自动存储)。 顺便说一句,Foo f1(a1)对堆栈指针( esp )没有指令影响,它已分配,如果func想获得成员f1 ,指令就是这样的: lea ecx [ebp+f1], call Foo::SomeFunc() 。 堆栈分配的另一件事可能使某人认为内存就像FIFO一样,当您进入某个函数时, FIFO才发生,如果您在函数中并分配了诸如int i = 0类的东西,则不会发生推送。


#15楼

堆栈分配几乎总是与堆分配一样快或更快,尽管堆分配器当然可以简单地使用基于堆栈的分配技术。

但是,在处理基于堆栈的分配与基于堆的分配的整体性能时(或更好地说,本地分配与外部分配),存在更大的问题。 通常,堆(外部)分配很慢,因为它正在处理许多不同种类的分配和分配模式。 减小正在使用的分配器的范围(使其在算法/代码局部)将有助于提高性能,而无需进行任何重大更改。 为分配模式添加更好的结构,例如,通过以更简单,更结构化的方式使用分配器,对分配和解除分配对强制执行LIFO排序也可以提高分配器的性能。 或者,您可以使用或编写针对您的特定分配模式调整的分配器; 大多数程序会频繁分配一些离散的大小,因此基于一些固定(最好是已知的)大小的后备缓冲区的堆将表现得非常好。 Windows正是出于这个原因使用了低碎片整理堆。

另一方面,如果线程太多,则在32位内存范围上基于堆栈的分配也会带来危险。 堆栈需要一个连续的内存范围,因此拥有的线程越多,运行它们就需要更多的虚拟地址空间,而不会导致堆栈溢出。 对于64位来说,这暂时不会是一个问题,但是它肯定会在长时间运行的具有很多线程的程序中造成严重破坏。 由于碎片而使虚拟地址空间用尽总是很难解决的。


#16楼

堆栈分配是一对指令,而我所知最快的rtos堆分配器(TLSF)平均使用150条指令。 同样,堆栈分配不需要锁,因为它们使用线程本地存储,这是另一个巨大的性能优势。 因此,堆栈分配的速度可以加快2-3个数量级,具体取决于环境中多线程的使用量。

通常,如果您在乎性能,那么堆分配是您的最后选择。 可行的中间选项可以是固定池分配器,该分配器也只有几个指令,并且每个分配的开销非常小,因此对于较小的固定大小对象非常有用。 不利的一面是,它仅适用于固定大小的对象,本质上不是线程安全的,并且存在块碎片问题。


#17楼

关于这种优化有一个总体要点。

您获得的优化与程序计数器实际在该代码中的时间成正比。

如果您对程序计数器进行采样,则会发现它在哪里花费时间,而这通常只是代码的一小部分,而且通常在库例程中您无法控制。

只有发现它在对象的堆分配中花费大量时间,才可以明显更快地对其进行堆栈分配。


#18楼

切勿过早假设,因为其他应用程序代码和用法会影响您的功能。 因此,将功能视为隔离是没有用的。

如果您对应用程序很认真,则可以使用VTune或使用任何类似的性能分析工具查看热点。

Ketan


#19楼

C ++语言特有的问题

首先, 没有C ++强制执行的所谓“堆栈”或“堆”分配 。 如果您在谈论块范围内的自动对象,它们甚至不会被“分配”。 (顺便说一句,在C中,自动存储的持续时间绝对不同于“分配的”;在C ++中,后者是“动态的”。)动态分配的内存在空闲存储中 ,不一定在“堆”中,尽管后者通常是(默认) 实现

尽管按照抽象的机器语义规则,自动对象仍会占用内存,但是当符合标准的C ++实现可以证明这无关紧要时(当它不改变程序的可观察行为时),可以忽略此事实。 此许可由ISO C ++中的as-if规则授予,这也是启用常规优化的常规子句(ISO C中也存在几乎相同的规则)。 除了as-if规则外,ISO C ++还具有复制省略规则,以允许省略特定对象的创建。 从而省略了所涉及的构造函数和析构函数调用。 结果,与源代码所隐含的天真的抽象语义相比,这些构造函数和析构函数中的自动对象(如果有的话)也被消除了。

另一方面,按设计,免费商店的分配绝对是“分配”。 在ISO C ++规则下,可以通过调用分配函数来实现这种分配 。 但是,从ISO C ++ 14开始,有一个新的(非按原样)规则允许在特定情况下合并全局分配函数(即::operator new )调用。 因此,动态分配操作的某些部分也可以像自动对象一样无操作。

分配功能分配内存资源。 可以基于分配器使用分配器进一步分配对象。 对于自动对象,它们是直接显示的-尽管可以访问基础内存并用于为其他对象提供内存(通过放置new ),但这对于免费存储来说意义不大,因为无法移动其他资源。

所有其他问题不在C ++的范围之内。 但是,它们仍然很重要。

关于C ++的实现

C ++不会公开化的激活记录或某种一流的延续(例如,通过著名的call/cc ),无法直接操作激活记录框架-实现需要将自动对象放置在其中。 一旦与基础实现(“本机”非便携式代码,例如内联汇编代码)不存在(非便携式)互操作,则忽略框架的基础分配可能就变得微不足道了。 例如,当内联被调用函数时,框架可以有效地合并到其他框架中,因此无法显示什么是“分配”。

但是,一旦尊重互操作性,事情就会变得复杂。 C ++的典型实现将通过一些调用约定(作为与本机(ISA级计算机)代码共享的二进制边界)公开ISA(指令集体系结构)上的互操作能力。 特别是在维护堆栈指针 (这通常由ISA级寄存器直接保存(可能需要访问特定的机器指令))时,这显然是昂贵的。 堆栈指针指示(当前活动的)函数调用的顶部帧的边界。 输入函数调用时,需要一个新帧,并且堆栈指针的增加或减少(取决于ISA的约定)的值不得小于所需的帧大小。 然后在操作完成后,当堆栈指针被称为分配帧时。 根据用于调用的调用约定,函数的参数也可以传递到堆栈框架上。 框架可以保存由C ++源代码指定的自动对象(可能包括参数)的内存。 从这种实现的意义上说,这些对象是“分配的”。 当控件退出函数调用时,不再需要该帧,通常通过将堆栈指针恢复到调用之前的状态(根据调用约定先前保存的)来释放该帧。 这可以视为“重新分配”。 这些操作使激活记录有效地成为LIFO数据结构,因此通常称为“ (调用)堆栈 ”。 堆栈指针有效地指示了堆栈的顶部位置。

因为大多数C ++实现(尤其是针对ISA级本机代码并使用汇编语言作为其直接输出的实现)都使用类似的策略,所以这种令人困惑的“分配”方案很受欢迎。 这样的分配(以及取消分配)确实要花费机器周期,并且(非优化的)调用频繁发生时,即使现代的CPU微体系结构可以通过硬件为通用代码模式实现复杂的优化(例如使用实现PUSH / POP指令的堆栈引擎 )。

但是总而言之,总的来说, 堆栈帧分配的成本确实比调用操作免费存储的分配函数(除非已完全优化)的成本要低得多,该函数本身可以具有数百个(如果不是百万个) :-)维护堆栈指针和其他状态的操作。 分配功能通常基于托管环境提供的API(例如,操作系统提供的运行时)。 与为函数调用保留自动对象的目的不同,此类分配是通用的,因此它们不会像堆栈那样具有框架结构。 传统上,它们从称为堆 (或几个堆)的池存储中分配空间。 与“堆栈”不同,此处的“堆”概念并不表示正在使用的数据结构。 它源自数十年前的早期语言实现 。 (顺便说一句,在程序或线程启动时,调用栈通常由环境从堆中分配固定大小或用户指定的大小。)用例的性质使得从堆中进行分配和释放要复杂得多(比push或pop of堆栈帧),并且几乎不可能通过硬件直接进行优化。

对内存访问的影响

通常的堆栈分配总是将新框架放在顶部,因此它具有很好的局部性。 这很容易缓存。 OTOH,在免费存储区中随机分配的内存没有这种属性。 从ISO C ++ 17开始, <memory>提供了池资源模板。 这种接口的直接目的是允许连续分配的结果在内存中紧密排列在一起。 这承认了这样一个事实,即该策略通常对当代实现具有良好的性能,例如,易于在现代体系结构中进行缓存。 不过,这是关于访问的性能而不是分配

并发

对内存并发访问的期望可能在堆栈和堆之间产生不同的影响。 调用堆栈通常由C ++实现中的一个执行线程专有。 OTOH,堆通常在进程中的线程之间共享 。 对于此类堆,分配和释放功能必须保护共享的内部管理数据结构免受数据争用。 结果,由于内部同步操作,堆分配和释放可能会产生额外的开销。

空间效率

由于用例和内部数据结构的性质,堆可能会遭受内部内存碎片的困扰,而堆栈则不会。 这不会直接影响内存分配的性能,但是在具有虚拟内存的系统中,低空间效率可能会降低内存访问的整体性能。 当将HDD用作物理内存交换时,这尤其糟糕。 这可能会导致相当长的延迟-有时数十亿个周期。

堆栈分配的局限性

尽管实际上堆栈分配在性能上通常比堆分配要好,但这并不意味着堆栈分配总是可以代替堆分配。

首先,无法使用ISO C ++以可移植的方式在运行时在堆栈上分配具有指定大小的空间。 诸如alloca和G ++的VLA(可变长度数组)之类的实现提供了一些扩展,但是有避免它们的理由。 (IIRC,Linux源代码最近取消了对VLA的使用。)(还请注意,ISO C99确实已强制执行VLA,但ISO C11则将支持变为可选。)

其次,没有可靠且可移植的方法来检测堆栈空间耗尽。 这通常称为堆栈溢出(此站点的词源) ,但更准确地说,可能是堆栈溢出 。 实际上,这通常会导致无效的内存访问,然后程序的状态将被破坏(...或更糟的是有安全漏洞)。 实际上,ISO C ++没有“堆栈”的概念,并且在资源耗尽时使其变为未定义的行为 。 注意自动对象应留多少空间。

如果堆栈空间用完,则堆栈中分配的对象过多,这可能是由于调用函数过多或对自动对象的使用不当造成的。 这种情况可能表明存在错误,例如没有正确退出条件的递归函数调用。

但是,有时需要深度递归调用。 在需要支持未绑定活动调用的语言的实现中(其中调用深度仅受总内存限制), 不可能像典型的C ++实现一样直接使用(当代)本机调用堆栈作为目标语言激活记录。 要变通解决此问题,需要替代方法来创建激活记录。 例如, SML / NJ在堆上显式分配帧并使用仙人掌堆栈 。 这种激活记录帧的复杂分配通常不如调用堆栈帧快。 但是,如果在保证适当的尾递归的情况下进一步实现此类语言,则目标语言中的直接堆栈分配(即该语言中的“对象”不会存储为引用,而是可以存储为-映射到未共享的C ++对象的“一对一”)通常更加复杂,并且性能损失更大。 使用C ++实现此类语言时,很难估计性能影响。


#20楼

之前已经提到过,堆栈分配只是在移动堆栈指针,即在大多数体系结构上只有一条指令。 将其与堆分配情况下通常发生的情况进行比较。

操作系统将空闲内存的部分保持为链接列表,并带有有效载荷数据,该有效载荷数据由指向空闲部分的起始地址的指针和空闲部分的大小组成。 要分配X个字节的内存,将遍历链接列表,并按顺序访问每个注释,以查看其大小是否至少为X。当找到大小P> = X的部分时,P分为两部分,尺寸X和PX。 链接列表将更新,并返回指向第一部分的指针。

如您所见,堆分配取决于可能的因素,例如请求的内存量,内存的碎片程度等等。


#21楼

通常,栈分配比堆分配更快,正如上面几乎每个答案所述。 堆栈推入或弹出操作为O(1),而从堆进行分配或释放则可能需要遍历先前的分配。 但是,通常不应该在性能密集的紧凑循环中进行分配,因此选择通常取决于其他因素。

进行这种区分可能会很好:您可以在堆上使用“堆栈分配器”。 严格来说,我将堆栈分配表示实际的分配方法,而不是分配的位置。 如果您在实际的程序堆栈上分配了很多东西,由于各种原因,这可能是不好的。 另一方面,尽可能使用堆栈方法在堆上分配是分配方法的最佳选择。

由于您提到了Metrowerks和PPC,所以我猜您是说Wii。 在这种情况下,内存非常宝贵,并且在可能的情况下使用堆栈分配方法可以确保您不会在片段上浪费内存。 当然,与“常规”堆分配方法相比,这样做需要更多的注意。 评估每种情况的权衡是明智的。


#22楼

我对Xbox 360 Xenon处理​​器上的堆栈与堆分配了解到的一件有趣的事情(也可能适用于其他多核系统)是,在堆上进行分配会导致输入关键部分以暂停所有其他内核,因此分配不会不冲突。 因此,在一个紧密的循环中,堆栈分配是固定大小阵列的一种选择,因为它可以防止停顿。

如果您正在为多核/多进程进行编码,则这可能是要考虑的另一种提速方法,因为堆栈分配只能由运行作用域函数的内核查看,而不会影响任何其他内核/ CPU。


#23楼

正如其他人所说,堆栈分配通常要快得多。

但是,如果对象的复制成本很高,则在不小心的情况下使用对象时,在堆栈上进行分配可能会导致性能严重下降。

例如,如果您在堆栈上分配一些东西,然后将其放入容器中,则最好在堆上分配并将指针存储在容器中(例如,使用std :: shared_ptr <>)。 如果您要按值传递或返回对象,以及其他类似情况,也是如此。

关键是,尽管在许多情况下堆栈分配通常比堆分配要好,但是有时如果在最不适合计算模型的情况下放弃堆栈分配的方法,则可能导致更多的问题无法解决。

哪个更快:堆栈分配或堆分配相关推荐

  1. linux线程堆分配,如何在Linux中的相同进程下为线程分配堆栈或内存

    Linux中当前的"线程"概念是 NPTL. NPTL使用 clone(),包装 sys_clone().为新的'线程'分配堆栈在用户空间(即libc)中处理,而不是在内核(即Li ...

  2. 内存区划分;内存分配;堆、栈概念分析;动态内存管理数据结构及程序样例;核心态与用户态...

    一. 在c中分为这几个存储区1.栈 - 由编译器自动分配释放 2.堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 3.全局区(静态区),全局变量和静态变量的存储是放在一块的,初 ...

  3. 哪个更快:Java 堆还是本地内存

    使用Java的一个好处就是你可以不用亲自来管理内存的分配和释放.当你用new关键字来实例化一个对象时,它所需的内存会自动的在Java堆中分配.堆会被垃圾回收器进行管理,并且它会在对象超出作用域时进行内 ...

  4. JVM学习笔记之-堆,年轻代与老年代,对象分配过程,Minor GC、Major GC、Full GC,堆内存大小与OOM,堆空间分代,内存分配策略,对象分配内存,小结堆空间,逃逸分析,常用调优工具

    堆的核心概述 概述 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域.Java堆区在JVM 启动的时候即被创建,其空间大小也就确定了.是JVM管理的最大一块内存空间. 堆内存的大小是可 ...

  5. java 本地内存_哪个更快:Java堆还是本地内存

    使用Java的一个好处就是你可以不用亲自来管理内存的分配和释放.当你用new关键字来实例化一个对象时,它所需的内存会自动的在Java堆中分配.堆会被垃圾回收器进行管理,并且它会在对象超出作用域时进行内 ...

  6. 让C++对象只能分配到堆/栈和静态区上并判断分配位置

    对象分配到堆上的过程:三个形式的new 要把对象分配到栈上,需要使用到new operator,而new operator会调用operator new和placement new. operator ...

  7. vs 编译器的堆空间不足_原创|面试官:Java对象一定分配在堆上吗?

    最近在看 Java 虚拟机方面的资料,以备工作中的不时之需.首先我先抛出一个我自己想的面试题,然后再引出后面要介绍的知识点如逃逸分析.标量替换.栈上分配等知识点 面试题 Java 对象一定分配在堆上吗 ...

  8. 小蚂蚁学习数据结构(16)——串的堆分配储存代码演示

    2019独角兽企业重金招聘Python工程师标准>>> 根据对书上伪代码的理解,编写了这么一段堆分配储存的程序,反正运行着没有什么问题,看看网上其他人的代码,具体细节上有所不同外,思 ...

  9. FreeRTOS堆分配大小对任务数的影响

    FreeRTOS堆分配(内存)如果不够大,可能引起FreeRTOS运行异常. 对于STM32芯片, 如果使用STM32CubeMX配置FreeRTOS,创建4个任务时,会引起FreeRTOS运行异常, ...

最新文章

  1. 如何找寻最适合的WEB应用安全解决方案(视频)
  2. 颜宁强烈推荐:给研究生的四条金玉良言
  3. java019异常、File类
  4. Java——super的使用
  5. 使用Visio—UML画类图
  6. 会议通知|2019暑期全国高校Python数据分析与实训课程高级研修班
  7. SonicWall 紧急提醒:EOL 设备正遭勒索攻击!
  8. linux shell ps kill 某个名字的所有进程
  9. 三个内置模块shutil logging hashlib config
  10. mtk2503电流设定失败
  11. 产品需求分析工具和方法
  12. 郑立:一个80后互联网创业神话的倒掉
  13. ABAP 关于 delete adjacent duplicates from的小心得
  14. sql server关系代数练习--进阶
  15. python100day - day24 - DataAnalysis
  16. UERANSIM 配置和使用
  17. Pytorch中, torch.einsum详解。
  18. NAS:以数据为中心的数据存储模式[zt]
  19. 解决虚拟机复制粘贴不能用的问题
  20. 神经网络和深度学习(5)-- 逻辑回归

热门文章

  1. android不公开的面试题偶!!!
  2. Android SystemProperties系统属性分析
  3. 除了iframe还有什么方法加载第三方网页_IE9常见问题的解决方法
  4. uniapp自定义导航栏
  5. django源码解析一(请求处理流程)
  6. 求二叉树第K层的节点个数+求二叉树叶子节点的个数
  7. Android 最火的快速开发框架XUtils
  8. C#基础—不完整类型(局部类型)
  9. oracle学习小知识点总结
  10. windows下用easybcd引导ubuntu出现grub的解决方案