简体   繁体   English

堆栈更喜欢堆?

[英]Prefer heap over stack?

I recently dove into graphics programming and I noticed that many graphic engines (ie Ogre), and many coders overall, prefer to initialize class instances dynamically. 我最近进入图形编程,我注意到许多图形引擎(即Ogre)和许多编码器总体上更喜欢动态地初始化类实例。 Here's an example from Ogre Basic Tutorial 1 这是Ogre Basic Tutorial 1的一个例子

//...

Ogre::Entity* ogreHead = mSceneMgr->createEntity("Head", "ogrehead.mesh");
Ogre::SceneNode* headNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("HeadNode");

//...

ogreHead and headNode data members and methods are then referred to as ogreHead->blabla . 然后将ogreHeadheadNode数据成员和方法称为ogreHead->blabla

Why mess around with object pointers instead of plain objects? 为什么要乱用对象指针而不是普通对象?

BTW, I've also read somewhere that heap memory allocation is much slower than stack memory allocation. 顺便说一下,我还读过堆内存分配慢于堆栈内存分配的地方。

Heap allocation is, inevitably much slower than stack allocation. 堆分配不可避免地比堆栈分配慢得多。 More on "How much slower?" 更多关于“慢多少?” later. 后来。 However, in many cases, the choice is "made for you", for several reasons: 但是,在许多情况下,选择是“为你做的”,原因如下:

  1. Stack is limited. 堆栈有限。 And if you run out, the application almost always gets terminated - there is no real good recovery, even printing an error message to say "I ran out of stack" may be hard... 如果你用完了,应用程序几乎总是被终止 - 没有真正好的恢复,甚至打印错误消息说“我跑出堆栈”可能很难......
  2. Stack allocation "goes away" when you leave the function where the allocation was made. 当您离开进行分配的功能时,堆栈分配“消失”。
  3. Variability is much more well defined and easy to deal with. 可变性更加明确,易于处理。 C++ does not cope with "variable length arrays" very well, and it's certainly not guaranteed to work in all compilers. C ++不能很好地处理“可变长度数组”,并且肯定不能保证在所有编译器中都能工作。

How much slower is heap over stack? 堆栈堆栈速度慢多少?

We'll get to "and does it matter" in a bit. 我们会稍微谈谈“而且重要”。

For a given allocation, stack allocation is simply a subtract operation [1], where at the very minimum new or malloc will be a function call, and probably even the most simple allocator will be several dozen instructions, in complex cases thousands [because memory has to be gotten from the OS, and cleared of it's previous content]. 对于给定的分配,堆栈分配只是一个减法运算[1],其中最小的newmalloc将是一个函数调用,甚至最简单的分配器可能是几十个指令,在复杂的情况下数千[因为内存必须从操作系统中获取,并清除它以前的内容]。 So anything from a 10x to "infinitely" slower, give or take. 所以从10倍到“无限”慢的任何东西,给予或接受。 Exact numbers will depend on the exact system the code is running in, size of the allocation, and often "previous calls to the allocator" (eg a long list of "freed" allocations can make allocating a new object slower, because a good fit has to be searched for). 确切的数字将取决于运行代码的确切系统,分配的大小,以及通常“以前对分配器的调用”(例如,“释放”分配的长列表可能会使分配新对象的速度变慢,因为它非常适合必须搜索)。 And of course, unless you do the "ostrich" method of heap management, you also need to free the object and cope with "out of memory" which adds more code/time to the execution and complexity of the code. 当然,除非你执行堆管理的“鸵鸟”方法,否则你还需要释放对象并应对“内存不足”,这会增加执行代码/时间和代码的复杂性。

With some reasonably clever programming, however, this can be mostly hidden - for example, allocating something that stays allocated for a long time, over the lifetime of the object, will be "nothing to worry about". 然而,通过一些相当聪明的编程,这可能基本上是隐藏的 - 例如,在对象的生命周期内分配长时间保持分配的东西,将“无需担心”。 Allocating objects from the heap for every pixel or every trianle in a 3D game would CLEARLY be a bad idea. 对于3D游戏中的每个像素或每个三角形,从堆中分配对象将是一个坏主意。 But if the lifetime of the object is many frames or even the entire game, the time to allocate and free it will be nearly nothing. 但是如果对象的生命周期是很多帧甚至整个游戏,那么分配和释放它的时间几乎为零。

Similarly, instead of doing 10000 individual object allocations, make one for 10000 objects. 类似地,不是进行10000个单独的对象分配,而是为10000个对象创建一个。 Object pool is one such concept. 对象池就是这样一个概念。

Further, often the allocation time isn't where the time is spent. 此外,分配时间通常不是花费时间的地方。 For example, reading a triangle list from a file from a disk will take much longer than allocating the space for the same triangle list - even if you allocate each single one! 例如,从磁盘中读取文件中的三角形列表所花费的时间比为同一个三角形列表分配空间要长得多 - 即使您分配了每个单独的三角形列表!

To me, the rule is: 对我来说,规则是:

  1. Does it fit nicely on the stack? 它是否适合堆叠? Typically a few kilobytes is fine, many kilobytes not so good, and megabytes definitely not ok. 通常几千字节很好,很多千字节不太好,兆字节绝对不行。
  2. Is the number (eg array of objects) known, and the maximum such that you can fit it on the stack? 数字(例如,对象数组)是否已知,以及最大值是否可以将其放入堆栈中?
  3. Do you know what the object will be? 你知道对象会是什么吗? In other words abstract/polymorphic classes will probably need to be allocated on the heap. 换句话说,可能需要在堆上分配抽象/多态类。
  4. Is its lifetime the same as the scope it is in? 它的寿命是否与它的范围相同? If not, use the heap (or stack further down, and pass it up the stack). 如果没有,请使用堆(或进一步向下堆栈,并将其传递给堆栈)。

[1] Or add if stack is "grows towards high addresses" - I don't know of a machine which has such an architecture, but it is conceivable and I think some have been made. [1]或者添加堆栈是否“向高地址增长” - 我不知道具有这种架构的机器,但可以想象,我认为已经有一些。 C certainly makes no promises as to which way the stack grows, or anything else about how the runtime stack works. 对于堆栈增长的方式,或者关于运行时堆栈如何工作的其他任何方式,C当然不做任何承诺。

The scope of the stack is limited: it only exists within a function. 堆栈的范围是有限的:它只存在于一个函数中。 Now, modern user-interfacing programs are usually event driven, which means that a function of yours is invoked to handle an event, and then that function must return in order for the program to continue running. 现在,现代用户界面程序通常是事件驱动的,这意味着调用您的函数来处理事件,然后该函数必须返回以便程序继续运行。 So, if your event handler function wishes to create an object which will remain in existence after the function has returned, clearly, that object cannot be allocated on the stack of that function, because it will cease to exist as soon as the function returns. 因此,如果您的事件处理函数希望创建一个在函数返回后仍然存在的对象,显然,该对象不能在该函数的堆栈上分配,因为它会在函数返回后立即停止存在。 That's the main reason why we allocate things on the heap. 这就是我们在堆上分配东西的主要原因。

There are other reasons, too. 还有其他原因。

Sometimes, the exact size of a class is not known during compilation time. 有时,在编译期间不知道类的确切大小。 If the exact size of a class is not known, it cannot be created on the stack, because the compiler needs to have precise knowledge of how much space it needs to allocate for each item on the stack. 如果未知类的确切大小,则无法在堆栈上创建它,因为编译器需要准确了解需要为堆栈中的每个项分配多少空间。

Furthermore, factory methods like whatever::createEntity() are often used. 此外,经常使用像whatever::createEntity()这样的工厂方法。 If you have to invoke a separate method to create an object for you, then that object cannot be created on the stack, for the reason explained in the first paragraph of this answer. 如果必须调用单独的方法为您创建对象,则无法在堆栈上创建该对象,原因在本答案的第一段中有所解释。

Why pointers instead of objects? 为什么指针而不是对象?

Because pointers help make things fast. 因为指针有助于加快速度。 If you pass an object by value, to another function, for example 例如,如果按值传递对象,则传递给另一个函数

shoot(Orge::Entity ogre)

instead of 代替

shoot(Orge::Entity* ogrePtr)

If ogre isn't a pointer, what happens is you are passing the whole object into the function, rather than a reference. 如果ogre不是指针,那么您将整个对象传递给函数而不是引用。 If the compiler doesn't optimize, you are left with an inefficient program. 如果编译器没有进行优化,那么您将得到一个效率低下的程序。 There are other reasons too, with the pointer, you can modify the passed in object (some argue references are better but that's a different discussion). 还有其他原因,使用指针,您可以修改传入的对象(一些争论引用更好,但这是一个不同的讨论)。 Otherwise you would be spending too much time copying modified objects back and forth. 否则你会花太多时间来回复制修改过的对象。

Why heap? 为什么堆?

  1. In some sense heap is a safer type of memory to access and allows you to safely reset/recover. 在某种意义上,堆是一种更安全的内存类型,可以让您安全地重置/恢复。 If you call new and don't have memory, you can flag that as an error. 如果您调用new并且没有内存,则可以将其标记为错误。 If you are using the stack, there is actually no good way to know you have caused stackoverflow, without some other supervising program, at which point you are already in danger zone. 如果你正在使用堆栈,实际上没有什么好方法可以知道你造成了stackoverflow,没有其他一些监督程序,此时你已经处于危险区域。
  2. Depends on your application. 取决于您的申请。 Stack has local scope so if the object goes out of scope, it will deallocate memory for the object. 堆栈具有本地范围,因此如果对象超出范围,它将释放对象的内存。 If you need the object in some other function, then no real way to do that. 如果你需要其他功能中的对象,那么没有真正的方法可以做到这一点。
  3. Applies more to OS, heap is comparatively much larger than stack, especially in multi-threaded application where each thread can have a limited stack size. 适用于OS,堆比堆栈大得多,特别是在多线程应用程序中,每个线程的堆栈大小都有限。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM