动态与静态内存在清洁度和速度方面的最佳实践

Posted

技术标签:

【中文标题】动态与静态内存在清洁度和速度方面的最佳实践【英文标题】:Best practices of dynamic vs. static memory in terms of cleanliness and speed 【发布时间】:2009-07-24 07:55:20 【问题描述】:

我有一个数组,叫做 x,它的大小是 6*sizeof(float)。我知道声明:

float x[6];

将在堆栈内存中为 x 分配 6*sizeof(float)。但是,如果我执行以下操作:

float *x;   // in class definition

x = new float[6];   // in class constructor

delete [] x;    // in class destructor

我会为 x 分配 6*sizeof(float) 的动态内存。如果 x 的大小在类的生命周期内没有改变,就清洁度和速度的最佳实践而言(我确实模糊地记得,如果不正确的话,堆栈内存操作比动态内存操作快),我应该确保那 x 是静态而不是动态分配的内存?提前致谢。

【问题讨论】:

请注意,C++ 上下文中的“静态”与“堆栈上”的含义完全不同!我相信堆栈变量的官方术语是“自动”,而堆分配的变量是“从免费存储中获取的”。 【参考方案1】:

声明固定大小的数组肯定会更快。每个单独的动态分配都需要找到一个未占用的块,而且速度不是很快。

因此,如果您真的关心速度(已分析),则规则是如果您不需要动态分配 - 不要使用它。如果需要,请三思要分配多少,因为重新分配也不是很快。

【讨论】:

为什么在这种情况下使用“静态”这个词?这意味着与“在堆栈上”完全不同的东西...... (哦,我明白了,它在问题中......) @xtofl:是的,这不是在这里使用的幸运术语。固定。【参考方案2】:

使用数组成员会更简洁(更简洁,更不容易出错)并且更快,因为不需要调用分配和释放函数。您还将倾向于改善所分配结构的“参考位置”。

为此类成员使用动态分配内存的两个主要原因是所需大小仅在运行时已知,或者所需大小很大并且已知这将对可用堆栈产生重大影响目标平台上的空间。

【讨论】:

【参考方案3】:

堆栈上的 TBH 数据通常位于缓存中,因此速度更快。但是,如果您动态分配一次然后定期使用它,它也会被缓存,因此几乎一样快。

重要的是要避免定期分配和解除分配(即每次调用函数时)。如果您只是不做常规分配和释放(即只分配和释放一次),那么堆栈和堆分配的数组将彼此一样快地执行。

【讨论】:

【参考方案4】:

是的,静态声明数组会执行得更快。

这很容易测试,只需编写一个简单的包装循环来实例化 X 个这些对象。您还可以单步执行机器代码并查看动态分配内存所需的大量 OPCODE。

【讨论】:

【参考方案5】:

静态分配更快(无需询问内存),您不会忘记删除它或使用不正确的删除运算符(删除而不是删除[])删除它。

动态/堆数据的构造由以下步骤组成:

请求内存来分配对象(调用 new 运算符)。如果没有内存,new 运算符将抛出 bad_alloc 异常。 使用默认构造函数创建对象(也由 new 完成) 由用户释放内存(通过 delete/delete[] 运算符)- delete 将调用 对象析构函数。在这里,用户可能会犯很多错误: 忘记调用删除 - 这会导致内存泄漏 调用不正确的删除运算符(例如,删除而不是删除[]) - 会发生不好的事情 调用两次删除 - 可能会发生坏事

当使用静态对象/对象数组时,用户无需分配和释放内存。这使代码更简单,更不容易出错。

因此,总而言之,如果您在编译时就知道数组的大小并且对内存无关紧要(也许在运行时我不会使用数组中的条目),那么静态数组显然是首选。 对于动态分配的数据,值得寻找智能指针 (here)

【讨论】:

【参考方案6】:

以下情况不要混淆:

int global_x[6];        // an array with static storage duration

struct Foo 
    int *pointer_x;     // a pointer member in instance data
    int member_x[6];    // an array in instance data
    Foo()  
        pointer_x = new int[6];    // a heap-allocated array
    
    ~Foo()  delete[] pointer_x; 
;

int main() 
    int auto_x[6];           // an array on the stack (automatic variable)
    Foo auto_f;              // a Foo on the stack
    Foo *dyn_f = new Foo();  // a heap-allocated Foo.

现在:

auto_f.member_x 在堆栈上,因为 auto_f 在堆栈上。 (*dyn_f).member_x 在堆上,因为*dyn_f 在堆上。 对于两个 Foo 对象,pointer_x 指向一个堆分配数组。 global_x 位于每次程序运行时操作系统或运行时创建的某些数据部分。这可能与动态分配来自同一个堆,也可能不同,这通常无关紧要。

所以不管它是否在堆上,在长度始终为 6 的情况下,member_xpointer_x 更好,因为:

代码更少,更不容易出错。 如果对象是堆分配的,则您的对象只需要一次分配,而不是 2 次。 如果对象在堆栈上,则您的对象不需要堆分配。 它使用的总内存更少,因为分配更少,也因为不需要存储指针值。

喜欢pointer_x的原因:

如果您需要在对象的生命周期内重新分配。 如果不同的对象需要不同大小的数组(可能基于构造函数参数)。 如果 Foo 对象将被放置在堆栈中,但数组太大以至于无法放入堆栈。例如,如果您有 1MB 的堆栈,则不能使用包含 int[262144] 的自动变量。

【讨论】:

【参考方案7】:

组合更高效,速度更快,内存开销更低,内存碎片更少。

你可以这样做:

template <int SZ = 6>
class Whatever 
   ...
   float floats[SZ];
;

【讨论】:

【参考方案8】:

尽可能使用堆栈分配的内存。它将使您免于释放内存、虚拟地址空间碎片等令人头疼的问题。此外,与动态内存分配相比,它更快。

【讨论】:

【参考方案9】:

这里有更多变量在起作用:

    数组的大小与堆栈的大小:堆栈大小与免费存储相比非常小(例如 1MB 到 30MB)。栈上的大块会导致栈溢出

    你需要的数组数量:大量的小数组

    数组的生命周期:如果只在函数内部本地需要,堆栈非常方便。如果在函数退出后需要它,你必须在堆上分配它。

    垃圾回收:如果你在堆上分配它,你需要手动清理它,或者让一些智能指针为你工作。

【讨论】:

【参考方案10】:

正如另一个回复中提到的,大对象不能在堆栈上分配,因为您不确定堆栈大小是多少。出于可移植性的考虑,大对象或大小可变的对象应始终分配在堆上。

现在由操作系统提供的 malloc/new 例程(例如,Solaris 的 libumem)已经有了很大的发展。动态内存分配通常不是瓶颈。

【讨论】:

【参考方案11】:

如果你静态分配arraty,它只会有一个实例。使用类的重点是您需要多个实例。根本不需要动态分配数组:

class A 
   ...
   private:
      float x[8];
;

是你想要的。

【讨论】:

"如果你静态分配数组,它只会有一个实例。使用类的要点是你想要多个实例。" - 嗯?他的对象的每个实例都会有一个数组,不管它是在类构造函数中动态分配的,还是作为成员变量静态分配的。 @hobodave:这不正确。在堆栈上分配对象和在类中分配静态对象是有区别的。尼尔指的是第二个。那么无论类的实例数量如何,都只会有一个数组实例。 @hobodave 无论 ikt 出现在哪里,静态变量永远只有一个实例。 啊,OP使用“静态”一词来表示错误地分配在堆栈上。我知道他试图说什么,但继续使用他不正确的术语。 尼尔,我想删除我的 -1,但它太旧了。你能编辑你的答案吗?

以上是关于动态与静态内存在清洁度和速度方面的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

基于K8s插件版的Jenkins动态节点实践内含最佳实践

在 CodeIgniter 中处理动态内容(最佳实践)

厉害了,IntelliJ IDEA 内存优化最佳实践

阿里云 CDN HTTPS 最佳实践系列——动态证书

IntelliJ IDEA 内存优化最佳实践

IntelliJ IDEA内存优化最佳实践