当前位置: 首页 > news >正文

xerces-c++内存管理策略为何耗费大量内存

xerces-c++内存管理策略&为何耗费大量内存

  • 本文结构
  • 1) 奇怪的new语句
  • 2) xerces-c++内存管理策略
  • 3) xerces-c++为何耗费内存?
  • 4) demo

本文结构

xerces-c++是一XML解析器。在讲其内存管理策略之前,需要先讲一下一个奇怪的new用法,之后会继续介绍它的内存管理策略,最后会说明它是如何耗费大量内存的。

1) 奇怪的new语句

DOMAttr *DOMDocumentImpl::createAttribute(const XMLCh *nam)
{
    if(!nam || !isXMLName(nam))
        throw DOMException(DOMException::INVALID_CHARACTER_ERR,0, getMemoryManager());
    return new (this, DOMMemoryManager::ATTR_OBJECT) DOMAttrImpl(this,nam);
}

placement new的用法一般为 new (address) (type) initializer 的形式,address是一个指针指向已经存在的一块内存,而上面的代码却有两个参数紧跟在new后面。于是查了下官方文档,还真有这种用法:

new expression
C++ C++ language Expressions
Creates and initializes objects with dynamic storage duration, that is, objects whose lifetime is not necessarily limited by the scope in which they were created.

Syntax
::(optional) new ( type ) initializer(optional) (1)
::(optional) new new-type initializer(optional) (2)
::(optional) new (placement-params) ( type ) initializer(optional) (3)
::(optional) new (placement-params) new-type initializer(optional) (4)

官方文档还有一个例子与xerces代码很像:
new(2,f) T; // calls operator new(sizeof(T), 2, f)
都是相当于下面两步:

  1. 调用重载的new函数分配内存
inline void * operator new(size_t amt, DOMDocumentImpl *doc, DOMMemoryManager::NodeObjectType type)
{
    void *p = doc->allocate(amt, type);
    return p;
}
  1. 调用构造函数初始化上面分配的内存
DOMAttrImpl::DOMAttrImpl(DOMDocument *ownerDoc, const XMLCh *aName)
    : fNode(ownerDoc), fParent (ownerDoc), fSchemaType(0)
{
    DOMDocumentImpl *docImpl = (DOMDocumentImpl *)ownerDoc;
    fName = docImpl->getPooledString(aName);
    fNode.isSpecified(true);
}

所以,看着怪异,其实和一般的placement new没什么大的区别。

2) xerces-c++内存管理策略

通过上面被重载的new函数可以看到它调用了DOMDocumentImpl::allocate(amt, type), 而后者又会调用到:

void* DOMDocumentImpl::allocate(XMLSize_t amount)
{
  //	Align the request size so that suballocated blocks
  //	beyond this one will be maintained at the same alignment.
  amount = XMLPlatformUtils::alignPointerForNewBlockAllocation(amount);

  // If the request is for a largish block, hand it off to the system
  //   allocator.  The block still must be linked into the list of
  //   allocated blocks so that it will be deleted when the time comes.
  if (amount > kMaxSubAllocationSize)
  {
    //	The size of the header we add to our raw blocks
    XMLSize_t sizeOfHeader = XMLPlatformUtils::alignPointerForNewBlockAllocation(sizeof(void *));

    //	Try to allocate the block
    void* newBlock;
    newBlock = fMemoryManager->allocate(sizeOfHeader + amount);

    //	Link it into the list beyond current block, as current block
    //	is still being subdivided. If there is no current block
    //	then track that we have no bytes to further divide.
    if (fCurrentBlock)
    {
      *(void **)newBlock = *(void **)fCurrentBlock;
      *(void **)fCurrentBlock = newBlock;
    }
    else
    {
      *(void **)newBlock = 0;
      fCurrentBlock = newBlock;
      fFreePtr = 0;
      fFreeBytesRemaining = 0;
    }

    void *retPtr = (char*)newBlock + sizeOfHeader;
    return retPtr;
  }

  //	It's a normal (sub-allocatable) request.
  //	Are we out of room in our current block?
  if (amount > fFreeBytesRemaining)
  {
    // Request doesn't fit in the current block.
    // The size of the header we add to our raw blocks
    XMLSize_t sizeOfHeader = XMLPlatformUtils::alignPointerForNewBlockAllocation(sizeof(void *));

    // Get a new block from the system allocator.
    void* newBlock;
    newBlock = fMemoryManager->allocate(fHeapAllocSize);

    *(void **)newBlock = fCurrentBlock;
    fCurrentBlock = newBlock;
    fFreePtr = (char *)newBlock + sizeOfHeader;
    fFreeBytesRemaining = fHeapAllocSize - sizeOfHeader;

    if(fHeapAllocSize<kMaxHeapAllocSize)
      fHeapAllocSize*=2;
  }

  //	Subdivide the request off current block
  void *retPtr = fFreePtr;
  fFreePtr += amount;
  fFreeBytesRemaining -= amount;

  return retPtr;
}

这便是内存分配的核心代码,逻辑不复杂,可以总结为以下几点:

  1. 如果要分配的内存大于kMaxSubAllocationSize(0x0100)直接走原始的系统new函数。
  2. 否则上次分配的大块内存还有剩余且大于等于需要的,则用剩余的。
  3. 剩余的不够则新分配一大块内存,大小为fHeapAllocSize。
  4. 这些大块内存会组成链表,fCurrentBlock是头指针,fFreeBytesRemaining是当前大块内存剩余未用的字节数。

DOMDocumentImpl是对外的接口,要想创建节点(Node)就必须通过一系列的createXXX来创建(工厂模式?),比如createAttribute,createElement,而这些create函数都会走allocate函数。也就是说每个Attribute/Element实例都来自链表上的大块内存。这个策略让我想起了《Effecive C++》也有类似的代码。
Item 10: Write operator delete if you write operator new
这些节点中途不会释放,直到最后要释放整个Document时才一起释放。请参考以下代码:

DOMDocumentImpl::~DOMDocumentImpl()
{
...
//  Delete the heap for this document.  This uncerimoniously yanks the storage
    //      out from under all of the nodes in the document.  Destructors are NOT called.
    this->deleteHeap();``
 }`
 void DOMDocumentImpl::deleteHeap()
{
    while (fCurrentBlock != 0)
    {
        void *nextBlock = *(void **)fCurrentBlock;
        fMemoryManager->deallocate(fCurrentBlock);
        fCurrentBlock = nextBlock;
    }
}

3) xerces-c++为何耗费内存?

根本原因是描述节点、属性等的数据结构太大,可以想像成重型卡车(每个节点或属性)只拉一袋大米。
既然所有的节点、属性等类的实例内存分配都走allocate,那我们就让它打印出为哪个类分配了多少字节,看看每辆卡车自身多重?

void * DOMDocumentImpl::allocate(XMLSize_t amount, DOMMemoryManager::NodeObjectType type)
{
    static std::map<int, std::string> maps = {
        {DOMMemoryManager::NodeObjectType::ATTR_OBJECT , "ATTR_OBJECT"},
        {DOMMemoryManager::NodeObjectType::ATTR_NS_OBJECT , "ATTR_NS_OBJECT"},
        {DOMMemoryManager::NodeObjectType::CDATA_SECTION_OBJECT , "CDATA_SECTION_OBJECT"},
        {DOMMemoryManager::NodeObjectType::COMMENT_OBJECT , "COMMENT_OBJECT"},
        {DOMMemoryManager::NodeObjectType::DOCUMENT_FRAGMENT_OBJECT , "DOCUMENT_FRAGMENT_OBJECT"},
        {DOMMemoryManager::NodeObjectType::DOCUMENT_TYPE_OBJECT , "DOCUMENT_TYPE_OBJECT"},
        {DOMMemoryManager::NodeObjectType::ELEMENT_OBJECT , "ELEMENT_OBJECT"},
        {DOMMemoryManager::NodeObjectType::ELEMENT_NS_OBJECT , "ELEMENT_NS_OBJECT"},
        {DOMMemoryManager::NodeObjectType::ENTITY_OBJECT , "ENTITY_OBJECT"},
        {DOMMemoryManager::NodeObjectType::ENTITY_REFERENCE_OBJECT , "ENTITY_REFERENCE_OBJECT"},
        {DOMMemoryManager::NodeObjectType::NOTATION_OBJECT , "NOTATION_OBJECT"},
        {DOMMemoryManager::NodeObjectType::PROCESSING_INSTRUCTION_OBJECT , "PROCESSING_INSTRUCTION_OBJECT"},
        {DOMMemoryManager::NodeObjectType::TEXT_OBJECT , "TEXT_OBJECT"}
    };
    std::cout<<"New for "<<maps[type]<<" size=0x"<<std::hex<<amount<<std::endl;
    if (!fRecycleNodePtr)
        return allocate(amount);

    DOMNodePtr* ptr = fRecycleNodePtr->operator[](type);
    if (!ptr || ptr->empty())
        return allocate(amount);

    return (void*) ptr->pop();
}

一个简单的XML及日志:

<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<TopNode>
  <SectionOfDataA>
    <TestData>MEMORY COST 1 1</TestData>
    <TestData>MEMORY COST 2 1</TestData>
  </SectionOfDataA>
</TopNode>

New for ELEMENT_OBJECT size=0x68
New for TEXT_OBJECT size=0x38
New for ELEMENT_OBJECT size=0x68
New for TEXT_OBJECT size=0x38
New for ELEMENT_OBJECT size=0x68
New for TEXT_OBJECT size=0x38
New for TEXT_OBJECT size=0x38
New for ELEMENT_OBJECT size=0x68
New for TEXT_OBJECT size=0x38
New for TEXT_OBJECT size=0x38
New for TEXT_OBJECT size=0x38

可见DOMElementImpl的SIZE为0x68, DOMTextImpl为0x38, 这得顶多少个字符串!

4) demo

抽取了xerces-c++关于内存管理的代码,便于demo或学习使用,请移步下面的链接下载。

相关文章:

  • STM32学习笔记:驱动SPI外设读写FLASH
  • 操作系统安全 基本概念
  • 猿创征文——C++|string类2
  • 【51单片机】认识单片机
  • Windows中执行C语言编译的程序乱码的解决方法
  • 商城项目10_JSR303常用注解、在项目中如何使用、统一处理异常、分组校验功能、自定义校验注解
  • 一天时间迅速准备前端面试|JS基础—原型和原型链【三座大山之一,必考】
  • Spring Security详细讲解(JWT+SpringSecurity登入案例)
  • 【Network】网络基础@应用层 —— 协议 | http | https
  • UGUI学习笔记(九)自制3D轮播图
  • R统计-单因素ANOVA/Kruskal-Wallis置换检验
  • 动态开点线段树(C++实现)
  • pytorch保存和加载模型权重以及CUDA在pytorch中的使用
  • UDF提权(mysql)
  • linux内核漏洞(CVE-2022-0847)
  • 《Javascript高级程序设计 (第三版)》第五章 引用类型
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • Debian下无root权限使用Python访问Oracle
  • EventListener原理
  • export和import的用法总结
  • express + mock 让前后台并行开发
  • log4j2输出到kafka
  • React-Native - 收藏集 - 掘金
  • TCP拥塞控制
  • Vue 动态创建 component
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 工作手记之html2canvas使用概述
  • 关于Flux,Vuex,Redux的思考
  • 警报:线上事故之CountDownLatch的威力
  • 类orAPI - 收藏集 - 掘金
  • 如何解决微信端直接跳WAP端
  • 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!
  • 一个JAVA程序员成长之路分享
  • 这几个编码小技巧将令你 PHP 代码更加简洁
  • # 执行时间 统计mysql_一文说尽 MySQL 优化原理
  • #stm32驱动外设模块总结w5500模块
  • (附源码)springboot码头作业管理系统 毕业设计 341654
  • (一)appium-desktop定位元素原理
  • (原创)攻击方式学习之(4) - 拒绝服务(DOS/DDOS/DRDOS)
  • (转)Sublime Text3配置Lua运行环境
  • (轉貼) 蒼井そら挑戰筋肉擂台 (Misc)
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • .NET MAUI学习笔记——2.构建第一个程序_初级篇
  • .NET文档生成工具ADB使用图文教程
  • @media screen 针对不同移动设备
  • [20160807][系统设计的三次迭代]
  • [20161214]如何确定dbid.txt
  • [2017][note]基于空间交叉相位调制的两个连续波在few layer铋Bi中的全光switch——
  • [Android]通过PhoneLookup读取所有电话号码
  • [Android实例] 保持屏幕长亮的两种方法 [转]
  • [codevs1288] 埃及分数
  • [excel与dict] python 读取excel内容并放入字典、将字典内容写入 excel文件
  • [GN] Vue3快速上手1
  • [GN] 后端接口已经写好 初次布局前端需要的操作(例)
  • [GPT]Andrej Karpathy微软Build大会GPT演讲(上)--GPT如何训练