静态内存分配的成本与C中的动态内存分配

时间:2015-05-12 19:23:33

标签: c linux memory memory-management

我非常有兴趣知道在static vs dynamic C上知道对象/项目的确切数量时,内存分配的首选方法Linux是否有利于提高性能(例如,运行时间) {1}}。少量对象(少量内存)以及大量对象(大量内存)的成本。

e.g., type A[N] vs type *A = malloc(sizeof(type) * N)

请告诉我。谢谢。

注意:我们可以对此进行基准测试并且可能知道答案。但我想知道解释这两种分配方法之间性能差异的概念。

4 个答案:

答案 0 :(得分:14)

静态分配会快得多。静态分配可以在全局范围内发生,也可以在堆栈上发生。

在全局范围内,静态分配的内存内置于二进制映像中。这是所需内存的总大小,以及它在运行二进制文件中的位置是在编译时计算的。然后当程序加载时,操作系统加载器将为所有全局静态数组分配足够的内存。我很确定它会在所有分配的恒定时间内发生。 (例如,更多的分配不会花费更多的时间)

在本地范围内,静态分配在堆栈上分配。这涉及简单地在堆栈上保留固定数量的字节,并且在每个分配的恒定时间内发生。堆栈空间非常有限。

动态内存必须从堆中分配,即使在最好的情况下,大多数分配都需要花费时间,每次分配的时间超过线性,比如n log n time或者其他东西。

实际上,动态分配比静态分配慢很多倍。

@update:正如下面的评论中指出的那样:堆栈分配在技术上不是静态分配(但它们是在OP的问题中使用的语法形式的分配)。

另外,当谈到"分配时间"时,我考虑了管理内存的总时间(分配和免费)。

在某些动态分配器中,分配时间比释放时间快。

一些快速分配器也可以通过内存效率来分配速度。在这些情况下,静态仍然更好"因为静态和堆栈分配在分配精确大小的块时分别没有时间和常量时间。

动态分配器可以快速权衡显着的内存效率(例如,伙伴分配器向上舍入到两个大小块的下一个幂,如33k alloc将使用64k)

答案 1 :(得分:7)

真正的静态分配(全局变量和标记为静态的局部变量)粘合到各个部分中,并使用{{1}在加载时(<?xml version="1.0" encoding="utf-8"?> <packages> <package id="CommonServiceLocator" version="1.3" targetFramework="net46" /> <package id="EntityFramework" version="6.1.3" targetFramework="net46" /> <package id="MahApps.Metro" version="1.4.1" targetFramework="net46" /> <package id="MahApps.Metro.IconPacks" version="1.6.0" targetFramework="net46" /> <package id="MahApps.Metro.Resources" version="0.6.1.0" targetFramework="net46" /> <package id="MaterialDesignColors" version="1.1.3" targetFramework="net46" /> <package id="MaterialDesignThemes" version="2.2.1.750" targetFramework="net46" /> <package id="MaterialDesignThemes.MahApps" version="0.0.8" targetFramework="net46" /> <package id="MvvmLight" version="5.3.0.0" targetFramework="net46" /> <package id="MvvmLightLibs" version="5.3.0.0" targetFramework="net46" /> </packages> )加载该部分的其余部分}。 它们基本上是免费的,已经被加载程​​序的成本所覆盖。

具有静态已知大小的自动数组可以与其他局部变量粘合在一起,并通过调整堆栈指针进行分配。 在现代处理器上,这基本上是一个整数减法(堆栈向下增长)或接近 1 ns 的东西。

可变长度数组不能粘贴到其他变量上,因此每个变量的成本约为 1 ns

我尝试在单线程流程中测量不同大小的execve,我得到了这个,这意味着mmap对的成本约为 50-100 倍作为分配的堆栈变量&lt; 16MiB 。之后,它突然上升( 32MiB 64MiB 的成本约为分配&lt; = 16MiB的一百倍):

Malloc times

这些只是获取指针的成本。实际访问可能导致页面错误,从而进一步增加了内存块的成本。堆栈分配时页面错误应该非常少见,但可能首次访问动态分配的内存。

此外,许多小的动态分配会给缓存带来很大的压力,因为你的内存将被分割。 (您可以通过使用内存池来减少此碎片,无论是您自己的还是作为glibc的一部分提供的obstack功能。)

答案 2 :(得分:5)

全局内存通常在程序(或共享模块/ dll)加载并预先填充其初始值时分配。这通常有自己的内存部分。

堆栈是内核在创建新线程时分配的内存块(内存系列页面)。在堆栈上分配堆栈内存通常是通过递减堆栈指针来完成的(一个机器指令 - (堆栈通常完全下降 - 较新的分配具有较低的地址)。当删除堆栈对象时,堆栈指针递增)。因此,堆栈具有严格的先到后语义。堆栈也是固定大小的,所以当你用完时会出现堆栈溢出。

当使用malloc(在C中)或new(C ++)时,内存在堆/免费存储上分配。这是任何数字内存块。当需要的内存比已经分配的内存多时,malloc会进入内核以从系统请求更多内存。通常malloc将使用已从系统中获取的释放内存。

必须假定对malloc的调用很慢,对于任何性能关键或实时例程都应该避免。这有两个原因。

  1. 堆需要以任何顺序分配和释放任何大小的内存。较旧的算法用于遍历已释放块的列表,直到找到合适大小的块。由于内存可能高度分散,因此可能会很慢。如果在多个线程上使用堆,则需要提供某种锁定。已经进行了大量研究以改善这种情况,并且现代堆积的jemalloc和tcmalloc确实改善了事物。但是,不要指望这一点。
  2. 如果无法从堆分配器已管理的内容中分配所需内存,则堆分配器将需要向内核请求更多内存。 (在unix上,这是通过mmapsbrk系统调用完成的。内核需要找到一些未分配的内存页面,并且必须将这些页面映射到进程内存空间中。任何形式的缓存都会消失。所以期望这个超级慢(对于电脑而言)。
  3. 因此,如果您需要执行任何性能关键处理,请预先分配所有内存,然后重用已分配的内存。总是假设对malloc和free的调用会很慢。

答案 3 :(得分:2)

如果您确切知道需要留出多少空间,那么主要关注的是内存管理的性能,您的代码无需重新编写-entrant,然后您将要通过在文件范围内声明它们或使用static存储类说明符来分配具有静态存储持续时间的对象。

int data[SOME_NUMBER];

void foo( /* some list of parameters here */ )
{
  static int some_more_data[SOME_OTHER_NUMBER];
  ...
}

datasome_more_data都在程序的生命周期内存在,但some_more_data仅在foo函数 1 中可见。

实际上,具有静态存储持续时间的项目在二进制映像本身中为它们留出了空间,因此只要加载程序就可以使用内存。就地点而言,可能具有优势;如果数据和代码在同一页面中,则无需交换。当然,这取决于代码。

明显的缺点是您的可执行文件占用空间更大。另一个缺点是您的代码不是re-entrant; foo的每次调用在读取或写入some_more_data时都在同一块内存上工作。根据您的代码所做的事情,这可能是也可能不是什么大问题。

如果您的代码需要重入,则不能使用具有静态存储持续时间的对象。如果数据块相对较小,则使用具有自动存储持续时间的对象(即常规局部变量)。

void foo( /* some list of parameters here */ )
{
  int some_more_data[SOME_SMALLISH_NUMBER];
  ...
}

在这种情况下,some_more_data仅在foo函数的生命周期内存在;每次调用foo时,它都会自动分配另一个some_more_data对象。无论开销存在什么开销,都是首先调用函数的开销的一部分(至少在我的经验中)。无论您是否使用局部变量,堆栈指针仍然会被调整,因此使用局部变量不会使它变慢。主要问题是自动对象的可用内存相对较小;对于超过一定大小的对象,这种方法根本不起作用。

如果你的代码需要重新进入,你需要分配大块内存,那么你几乎要坚持使用动态内存管理(malloc/calloc/realloc和{{1} })。根据您设计代码的方式,您可以最大限度地减少一些性能问题。

<小时/> 1。在从源代码到机器代码的转换过程中强制执行可见性规则;它们在运行时并不真正适用。