HeapAlloc的计算复杂性

时间:2013-12-09 17:13:24

标签: c++ windows heap complexity-theory

vector的C ++实现依赖于在超出当前容量时将其扩展两次。显然,push_back操作在Windows上使用HeapAlloc来解决此问题,并且根据C ++标准,{@ 3}}应该HeapAlloc。 Windows需要多长时间才push_back,如何在不事先知道某个特定程序中的指令集的情况下,如何计算{{1}}使用use amortized constant time恒定时间? / p>

3 个答案:

答案 0 :(得分:1)

术语“摊销”常数时间表示“如果执行长时间的操作,则每个操作平均需要一个恒定的时间。”这与vector一起使用的方式是表加倍。当向量空间不足时,它不会为单个项目创建更多空间,而是使当前向量的大小加倍。这将花费时间等于向量的大小( O(n)为你的算法人员),但你只需要在表的大小加倍时这样做。因此,如果您从大小为 n 的向量开始并添加 n 更多项目,则第一项将采用 O(n)时间(至将表的大小加倍并复制旧值,但下一个 n-1 项将占用 O(1)时间。这为 n 插入提供 O(n)+(n-1)O(1)时间,或者总共 O(2n)= O(n )时间。对此进行平均可以为每个操作提供 O(1)时间。

答案 1 :(得分:1)

请记住,STL将使用new(或自定义分配器),因此不需要在Windows平台上调用HeapAlloc

话虽这么说,STL文档无法保证与低级API调用的任何运行时一致性,它只是定义了自己内部实现的内容。

另请注意,HeapAlloc的实施可能会在操作系统甚至Service Pack版本之间发生变化,因此未来版本中的任何答案都很容易被淘汰。

就个人而言,我会更关心堆内锁的数量和长度(由于通过多个线程同时访问而需要确保数据结构的一致性。)

进一步阅读:MSDN -- Heap: Pleasures and Pains

答案 2 :(得分:1)

我认为可以非常安全地假设HeapAlloc(或任何操作系统中的动态分配)是分摊的常量。

首先,当你要求堆提供给定数量的内存时,它必须找到一大块可用内存,它可以分配给你。如何找到该块内存的内部逻辑特定于实现(但there are some good clues to be found),并且可能会有很大差异,具体取决于碎片和效率等。但是,如果堆的实现无法以摊销的常量时间传递,那么该实现非常糟糕。它通常被称为“堆”,因为这种“找到一个好的块”机制的基本实现是使用一种优先级队列或第一个可用块的列表,它们可以立即传送(接近)最好的块。更复杂的版本不会比平均更糟糕(在摊销意义上),但可能涉及额外的“簿记”工作,这取决于它如何跟踪免费块以及它尝试减少碎片的程度,以便开始点,看看经典的buddy allocator。所以,这就是“找到一个好块”的部分。当然,还有许多其他有趣的问题,比如争用,但总的来说,它仍然是按时间分摊的(这是常数因素,在这里更为重要)。

堆的另一部分是面向操作系统内核(或其他与操作系统相关的层),以便在耗尽时获得额外的内存。基本上,堆是一个内存管理器,它从OS(或后端)获取大块内存,然后对这些大块内存进行微管理,以分配程序请求的所有较小块(或前端 - 结束)(通过newmallocHeapAlloc等)。偶尔,你会要求堆更多的内存,并且堆会发现它已经耗尽了内存,因此,它必须转向操作系统并要求另一个大块。如您所见,堆本身现在必须表现得非常像std::vector,因为它必须以指数方式增长,以便分摊对操作系统的请求成本以获得更多内存。

当操作系统内核向堆中提供内存时,成本也是恒定的(最有可能),因为它可能以与堆在程序中分配内存的方式非常相似的方式实现。当然,不同之处在于,如果操作系统内存不足,则无法向任何人寻求更多内存,因此它只会失败。从OS内核请求内存最昂贵的是在用户模式/ kernel-mode,进程间争用和virtual address space的扩展之间切换。

  

如何计算push_back使用分摊的常数时间,而不事先了解一个特定程序中的指令集?

基本上,C ++标准要求push_back std::vector在摊销的常量时间内实现,不包括堆分配的成本,因为它们明确地排除了分析的内存分配时间,他们无法真正决定或预测。但是,如果你愿意的话,你可以根据经验进行测试,我已经过去了,而且我可以肯定地说(至少在Linux下)push_back std::vector确实按照常量时间摊销。