一种高效的C++固定内存块分配器

发表于:2017-2-15 09:42

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:苏文鹏    来源:51Testing软件测试网采编

分享:
  基准测试
  在Windows PC上使用Allocator和全局堆内存的对比性能测试显示出Allocator的高性能。测试分配和释放20000个4096和2048大小的内存块来测试分配和释放内存的速度。测试的算法详见附件中的代码。
  使用调试模式执行时,Windows使用调试堆内存。调试堆内存添加额外的安全检查降低了性能。发布堆内存性能更好,因为不使用安全检查。通过在Visual Studio工程选项中,设置【调试】-【环境】中_NO_DEBUG_HEAP=1来禁止调试内存模式。
  全局调试堆内存模式需要平均1.8秒,是最慢的。释放对内存模式50毫秒左右,稍快。基准测试的场景非常简单,实际情况下,不同大小的内存块和随机的申请、释放可能产生不同的结果。然而,最简单的也最能说明问题。内存管理器比Allocator内存分配器慢,并且很大程度上依赖于平台的实现能力。
  内存分配器Allocator使用静态内存模式不依赖于堆内存的分配。一旦“释放列表”中含有内存块后,其执行时间大约为7毫秒。第一次耗时19毫秒用于将内存池中的内存防止到Allocator分配器中管理。
  Aloocator使用堆内存模式时,当“释放列表”中有可重用的内存后,其速度与静态内存模式一样快。堆内存模式依赖于全局堆来获取内存块,但是循环利用“释放列表”中的内存。第一次需要申请堆内存,耗时30毫秒。由于重用“释放列表”中的内存,之后的申请仅需要7毫秒。
  上面的基准测试结果表示,Allocator内存分配器更加高效,拥有7倍于Windows全局发布堆内存模式的速度。
  对于嵌入式系统,我使用Keil在ARM STM32F4 CPU(168Hz)上运行相同测试。由于资源限制,我将最大内存块数量降低到500,单个内存块大小降低到32和16字节。下面是结果:
  基于ARM的基准测试显示,使用Allocator分配器的类性能快15倍。这个结果会让Keil堆内存的表现相形见绌。基准测试分配500个16字节大小的内存块进行测试。每个16字节大小的内存删除后申请500个32字节大小的内存块。全局堆内存耗时11.6毫秒,而且,在内存碎片化后,内存管理器可能会在没有安全检查的情况下耗时更大。
  分配器决议
  第一个决定是你是否需要使用分配器。如果你的项目不关心执行的速度和是否需要容错,那么你可能不需要自定义的分配器,全局堆分配管理器足够用了。
  另一方面,如果你需要考虑执行速度和容错管理,分配器会起到作用。你需要根据项目的需要选择分配器的模式。重要任务系统的设计可能强制要求使用全局堆内存。而动态分配内存可能更高效,设计更优雅。这种情况下,你可以在调试开发时使用堆内存模式获取内存使用参数,然后发布时切换到静态内存池模式避免内存分配带来的性能消耗。一些编译时的宏可用于模式的切换。
  另外,堆内存模式可能对应用更适合。该模式利用堆来获取新内存,同时阻止了堆碎片错误。当“释放列表”链接足够的内存块后更能加快内存的分配效率。
  在源代码中没有实现的涉及多线程的问题不在本文的讨论范围内。运行系统一会后,可以方便地使用GetlockCount函数和GetName函数获取内存块数量和名称。这些度量参数提供关于内存分配的信息。尽量多申请点内存,以便给分配盘一些弹性来避免内存耗尽。
  调试内存泄漏
  调试内存泄漏非常困难,原因是堆内存就像一个黑盒,对于分配对象的类型和大小是不可见的。使用Allocator,由于Allocator跟踪记录内存块的总数,内存泄漏检查变得简单一点。对每个分配器实例重复输出(例如输出到终端)GetBlockCount和GetName并比对它们的不同能让我们更好的了解分配器对内存的分配。
  错误处理
  C++中使用new_handler函数处理内存分配错误。如果内存管理器在申请内存时发生错误,用户的错误处理函数就会被调用。通过将用户的错误处理函数地址复制给new_handler,内存管理器就能调用用户自定义的错误处理程序。为了让Allocator类的错误处理机制与内存管理器保持一致,分配器也通过new_handler调用错误处理函数,集中处理所有的内存分配错误。
  static void out_of_memory() {
  // new-handler function called by Allocator when pool is out of memory
  assert(0);
  }
  int _tmain(int argc, _TCHAR* argv[]) {
  std::set_new_handler(out_of_memory);
  ...
  限制
  分配器类不支持数组对象的内存分配。为每一个对象创建分开的内存是无法保证的,因为new的多次调用不保证内存块的连续,但这又是数组所需要的。因此Allocator只支持固定大小内存块的分配,对象数组不支持。
  移植问题
  Allocator在静态内存池耗尽时调用new_handle指向的函数,这对于某些系统不合适。假设new_handle函数没返回,例如无尽的循环或者断言,调用这个函数不起任何作用。使用固定内存池时这无济于事。
22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号