特殊的工具和技术

--优化内存分配

引言:

C++的内存分配是一种类型化操作:new为特定类型分配内存,并在新分配的内存中构造该类型的一个对象。new表达式自己主动执行合适的构造函数来初始化每一个动态分配的类类型对象。

new基于每一个对象分配内存的事实可能会对某些类强加不可接受的执行时开销,这种类可能须要使用用户级的类类型对象分配能够更快一些。

这种类使用的通用策略是,预先分配用于创建新对象的内存,须要时在预先分配的内存中构造每一个新对象

另外一些类希望按最小尺寸为自己的数据成员分配须要的内存。比如,标准库中的 vector类预先分配额外内存以保存添加的附加元素,将新元素添加到这个保留容量中。将元素保持在连续内存中的时候,预先分配的元素使vector能够高效地添加元素

在每种情况下(预先分配内存以保存用户级对象或者保存类的内部数据)都须要将内存分配对象构造分离开。将内存分配与对象构造分离开的明显的理由是,在预先分配的内存中构造对象非常浪费,可能会创建从不使用的对象。

当实际使用预先分配的对象的时候,被使用的对象必须又一次赋以新值。

更微妙的是,假设预先分配的内存必须被构造,某些类就不能使用它。比如,考虑vector,它使用了预先分配策略。假设必须构造预先分配的内存中的对象,就不能有基类型为没有默认构造函数的vector——vector没有办法知道如何构造这些对象。

【小心地雷】

本节提出的技术不保证使全部程序更快。即使它们确实能改善性能,也可能带来其它开销,如空间的使用或调试困难

最好将优化推迟到已知程序能够工作,而且执行时測试指出改进内存分配将解决已知的性能问题的时候。

一、C++中的内存分配

C++中,内存分配对象构紧密纠缠,就像对象和内存回收一样。

使用new 表达式的时候,分配内存,并在该内存中构造一个对象:使用delete表达式的时候,调用析构函数撤销对象,并将对象所用内存返还给系统

接管内存分配时,必须处理这两个任务。分配原始内存,必须在该内存中构造对象;释放内存之前,必须保证适当地撤销这些对象

【小心地雷】

未构造的内存中的对象进行赋值而不是初始化,其行为是没有定义的。对很多类而言,这样做引起执行时崩溃。赋值涉及删除现存对象,假设没有现存对象,赋值操作符中的动作就会有灾难性效果

C++提供下面两种方法分配和释放未构造的原始内存:

1)allocator,它提供可感知类型的内存分配。这个类支持一个抽象接口,以分配内存并随后使用该内存保存对象。

2)标准库中的operatornew 和 operatordelete,它们分配和释放须要大小的原始的、未类型化的内存。

C++还提供不同的方法在原始内存中构造撤销对象

1)allocator定义了名为construct和 destroy的成员,其操作正如它们的名字所指出的那样:construct成员在未构造内存初始化对象,destroy 成员在对象上执行适当的析构函数

2)定位new表达式接受指向未构造内存的指针,并在该空间中初始化一个对象或一个数组。

3)能够直接调用对象的析构函数来撤销对象。执行析构函数并不释放对象所在的内存

4)算法uninitialized_filluninitialized_copy像 fill和 copy 算法一样执行,除了它们的在目的地构造对象而不是给对象赋值之外。

二、allocator

allocator类是一个模板,它提供类型化的内存分配以及对象构造与撤销。

标准allocator类与定制算法

allocator<T>a;

定义名为a的allocator对象,能够用于分配内存或构造T类型的对象

a.allocate(n)

分配内存:分配原始的未构造内存以保存T类型的n个对象

a.deallocate(p,n)

释放内存:释放在名为p的T*指针中包括的地址处保存T类型的n个对象[原文Deallocatesmemory that held n objects of type T starting at addresscontained in the T* pointer named p]。

执行调用deallocate之前在该内存中构造的随意对象的destroy是用户的责任

a.construct(p,t)

在T*指针p所指内存中构造一个新元素。执行T类型的复制构造函数用t初始化该对象

a.destroy(p)

执行T*指针p所指对象的析构函数

uninitialized_copy(b,e,b2)

从迭代器b和e指出的输入范围将元素拷贝到从迭代器b2開始的未构造的原始内存中。该函数在目的地构造元素,而不是给它们赋值。假定由b2指出的目的地足以保存输入范围中元素的副本

uninitialized_fill(b,e,t)

将由迭代器b和e指出的范围中的对象初始化为t的副本。假定该范围是未构造的原始内存。使用复制构造函数构造对象

uninitialized_fill_n(b,e,t,n)

将由迭代器b和e指出的范围中至多n个对象初始化为t的副本。假定范围至少为n个元素大小。

使用复制构造函数构造对象

allocator类内存分配对象构造分开。当allocator对象分配内存的时候,它分配适当大小并排列成保存给定类型对象的空间。可是,它分配的内存是未构造的,allocator的用户必须分别construct和 destroy放置在该内存中的对象。

1、使用allocator管理类成员数据

回顾:vector类将元素保存在连续的存储中。为了获得可接受的性能,vector 预先分配比所需元素很多其它的元素。每次将元素加到容器中时。 vector成员检查是否有可用空间以容纳还有一元素。假设有,该成员在预分配内存中下一可用位置初始化一个对象;假设没有自由元素,就又一次分配vector: vector获取新的空间,将如今元素复制到空间,添加新元素,并释放旧空间。

vector所用存储開始是未构造内存,它还没有保存不论什么对象。将元素复制或添加到这个预分配空间的时候,必须使用allocator类的construct成员构造元素。

简易实现Vector类:

template <class T> class Vector
{
public:Vector():elements(0),first_free(0),end(0) {}void push_back(const T &);//...private:static std::allocator<T> alloc;void reallocate();T *elements;T *first_free;T *end;//...
};

每一个Vector<T>类型定义一个allocator<T>类型的 static数据成员,以便在给定类型的Vector分配构造元素。每一个Vector对象在指定类型的内置数组中保存其元素,并维持该数组的下列三个指针,如图:

1)elements,指向数组的第一个元素

2)first_free,指向最后一个实际元素之后的那个元素

3)end,指向数组本身之后的那个元素

能够使用这些指针来确定Vector的大小和容量:

•Vector的size(实际使用的元素的数目)等于first_free- elements。

•Vector的capacity(在必须又一次分配Vector之前,能够定义的元素的总数)等于end- elements。

•自由空间(在须要又一次分配之前,能够添加的元素的数目)是end– first_free。

2、使用construct

push_back成员使用这些指针将新元素加到Vector末尾:

template <typename T>
void Vector<T>::push_back(const T &item)
{if (first_free == end){reallocate();   //分配新空间并复制现存元素。将指针重置为指向新分配的空间}alloc.construct(first_free,item);++ first_free;
}

一旦push_back函数知道还有空间容纳新元素,它就请求allocator对象构造一个新的最后元素。construct函数使用类型T的复制构造函数将item值拷贝到由first_free指出的元素。然后,将first_free加 1以指出又有一个元素在用。

3、又一次分配元素与复制元素

template <typename T>
void Vector<T>::reallocate()
{std::ptrdiff_t size = first_free - elements;std::ptrdiff_t newcapacity = 2 * max(static_cast<int>(size),1);T *newelements = alloc.allocate(newcapacity);uninitialized_copy(elements,first_free,newelements);for (T *p = first_free; p != elements; ){alloc.destroy(--p);}if (elements){alloc.deallocate(elements,end - elements);}elements = newelements;first_free = newelements + size;end = elements + newcapacity;
}

我们使用一个简单但效果惊人的策略:每次又一次分配时分配两倍内存。

函数首先计算当前在用的元素数目,将该数目翻倍,并请求allocator对象来获得所需数量的空间。假设Vector为空,就分配两个元素。

uninitialized_copy调用使用标准copy算法的特殊版本号。

这个版本号希望目的地是原始的未构造内存,它在目的地复制构造每一个元素,而不是将输入范围的元素赋值给目的地,使用T的复制构造函数从输入范围将每一个元素拷贝到目的地。

for循环对旧数组中每一个对象调用allocator的 destroy成员它按逆序撤销元素,从数组中最后一个元素開始,以第一个元素结束。destroy函数执行 T类型的析构函数来释放旧元素所用的不论什么资源。

一旦复制和撤销了元素,就释放原来数组所用的空间。在调用deallocate 之前,必须检查elements是否实际指向一个数组

【注解】

deallocate期待指向由allocate分配的空间的指针,传给 deallocate一个零指针是不合法的。

最后,必须重置指针以指向新分配并初始化的数组。将first_free和 end 指针分别置为指向最后构造的元素之后的单元以及所分配空间末尾的下一单元。

//P636 习题18.1/2
//in Vector.h
#ifndef VECTOR_H_INCLUDED
#define VECTOR_H_INCLUDED#include <algorithm>
#include <memory>template <class T> class Vector
{
public:typedef T* iterator;public:Vector():elements(0),first_free(0),end(0) {}void push_back(const T &);void reserve(const size_t );void resize(const size_t );void resize(const size_t ,const T &);T &operator[](const size_t);const T &operator[](const size_t) const;size_t size(){return first_free - elements;}size_t capacity(){return end - elements;}iterator begin(){return elements;}iterator last(){return first_free;}private:static std::allocator<T> alloc;void reallocate();T *elements;T *first_free;T *end;
};template <typename T> std::allocator<T> Vector<T>::alloc;template <typename T>
void Vector<T>::reallocate()
{std::ptrdiff_t size = first_free - elements;std::ptrdiff_t newcapacity = 2 * std::max(static_cast<int>(size),1);T *newelements = alloc.allocate(newcapacity);std::uninitialized_copy(elements,first_free,newelements);for (T *p = first_free; p != elements; ){alloc.destroy(--p);}if (elements){alloc.deallocate(elements,end - elements);}elements = newelements;first_free = newelements + size;end = elements + newcapacity;
}#endif // VECTOR_H_INCLUDED
//in Vector.cpp
#include "Vector.h"template <typename T>
void Vector<T>::push_back(const T &item)
{if (first_free == end){reallocate();}alloc.construct(first_free,item);++ first_free;
}template <typename T>
void Vector<T>::reserve(const size_t capa)
{size_t size = first_free - elements;T *newelements = alloc.allocate(capa);if (size <= capa){uninitialized_copy(elements,first_free,newelements);}else{uninitialized_copy(elements,elements + capa,newelements);}for (T *p = first_free; p != elements;){alloc.destroy(--p);}if (elements){alloc.deallocate(elements,end - elements);}elements = newelements;first_free = elements + std::min(size,capa);end = elements + capa;
}template<class T>
void Vector<T>::resize(const size_t n)
{size_t size = first_free - elements;if (n > capacity()){reallocate();std::uninitialized_copy(elements + size,elements + n,T());}else if (n > size){std::uninitialized_copy(elements + size,elements + n,T());}else{for (T *p = first_free; p != elements + n;){alloc.destroy(--p);}}first_free = elements + n;
}template<class T>
void Vector<T>::resize(const size_t n,const T &t)
{size_t size = first_free - elements;if (n > capacity()){reallocate();std::uninitialized_copy(elements + size,elements + n,t);}else if (n > size){std::uninitialized_copy(elements + size,elements + n,t);}else{for (T *p = first_free; p != elements + n;){alloc.destroy(--p);}}first_free = elements + n;
}template<class T>
T &Vector<T>::operator[](const size_t index)
{return elements[index];
}
template<class T>
const T &Vector<T>::operator[](const size_t index) const
{return elements[index];
}

版权声明:本文博客原创文章。博客,未经同意,不得转载。

C++ Primer 学习笔记_98_特殊的工具和技术 --优化内存分配相关推荐

  1. 【深入理解Java虚拟机学习笔记】第三章 垃圾收集器与内存分配策略

    最近想好好复习一下java虚拟机,我想通过深读 [理解Java虚拟机 jvm 高级特性与最佳实践] (作者 周志明) 并且通过写一些博客总结来将该书读薄读透,这里文章内容仅仅是个人阅读后简短总结,加强 ...

  2. OS学习笔记-6(清华大学慕课) 非连续内存分配管理

    非连续内存分配的需求 有可能找不到连续的一块内存空间满足进程需求 连续分配存在内碎片,外碎片,内存利用率比较低 非连续内存分配目标:提高内存利用效率以及管理灵活性 于是由于非连续内存块的大小分成了:段 ...

  3. 《深入理解java虚拟机》学习笔记--第三章:垃圾收集器与内存分配策略

    GC所关心的问题就是: (1)那些内存需要回收? (2)何时回收? (3)怎么回收? 关注点:在程序计数器.java虚拟机栈.本地方法栈中,这些内存都是随着线程的创建而创建,销毁而销毁,这部分是不需要 ...

  4. C++ Primer 学习笔记(第四章:表达式)

    2019独角兽企业重金招聘Python工程师标准>>> ##C++ Primer 学习笔记(第四章:表达式) [TOC] ###4.1 基础 左值和右值: 当一个对象被用作右值的时候 ...

  5. 【C++ Primer 学习笔记】: 容器和算法之【泛型算法】

    本系列博客主要是在学习 C++ Primer 时的一些总结和笔记. [C++ Primer 学习笔记]: 容器和算法之[泛型算法] 本文地址:http://blog.csdn.net/shanglia ...

  6. Kail学习笔记-kali信息搜集工具之IKE-Scan

    Kail学习笔记-kali信息搜集工具之IKE-Scan 一.用法 二.选项 三.示例 一.用法 ike-scan [选项] [主机地址...] 必须在命令行上指定目标主机,除非为–file选项,在这 ...

  7. Kail 学习笔记-kali信息搜集工具之Dmitry

    Kail 学习笔记-kali信息搜集工具之Dmitry DMitry(Deepmagic Information Gathering Tool)是一个一体化的信息收集工具.它可以用来收集以下信息: 根 ...

  8. C++ Primer 学习笔记 第十章 泛型算法

    C++ Primer 学习笔记 第十章 泛型算法 336 find函数 #include <iostream> #include <vector> #include <s ...

  9. mysql 学习笔记--存储引擎、索引、sq优化

    全面的 mysql学习笔记–通用语法.函数.数据类型.约束.多表查询.事务 全面的 mysql学习笔记–存储引擎.索引.sql优化 全面的mysql学习笔记–视图/存储过程/触发器.锁.InnoDB引 ...

  10. 学习笔记:cache 和spring cache 技术(1)

    title: 学习笔记:cache 和spring cache 技术(1) author: Eric liu tags: [] categories: hexo 缓存是实际工作中非常常用的一种提高性能 ...

最新文章

  1. python之IO操作
  2. VMWare安装黑苹果Mac OS
  3. 《网络安全——应用技术与工程实践》
  4. 中年程序员都在想什么?
  5. sql 某字段存储另一个表的多个id值并以逗号分隔,现根据id去中文并拼接同样以逗号分隔...
  6. 【v8】一些关于内存泄漏的踏坑
  7. 如果降低sbus总线的传输速率/帧率,sbus怎样转UART,sbus接到4G
  8. 梯度下降来龙去脉附matlab代码
  9. TSL1.0升级到TSL1.2遇到的问题
  10. Win11如何开启移动热点?Win11开启移动热点的方法
  11. cocos2d实现语音_【Cocos Creator与C++知识分享】Creator接入呀呀语音SDK
  12. 虚拟机共享计算机的网络连接,Parallels Desktop如何共享网络 Mac与虚拟机共享网络设置教程...
  13. 计算机与机械专业 有什么大学排名,2017机械排名211大学排名
  14. Angular学习笔记64:使用Render2安全操作DOM元素
  15. 在PowerShell中将QuickBooks在线数据导入QuickBooks桌面
  16. 批处教程 for /f 中的Delims和Tokens总结-dos通过for命令截取字符串
  17. 聊聊机器人学习中的“投资”与“消费”(节选)
  18. 华为云不能访问指定端口和telnet连接不了的问题
  19. iOS runtime面试题
  20. 《土地利用规划学》教学教案-PPT-空间规划-土地利用现状

热门文章

  1. 合肥磨店职教城论坛使用WNMP平台架设 http://301302.net/
  2. 使用组策略统一修改客户端本地管理员密码
  3. 配置vmcore通过ssh转储到其他主机的方法
  4. Linux内核通知链(Notifier)
  5. android cpu降温代码,Android手机CM设置中CPU模式解释
  6. linux内核分析与应用 -- 内存管理(上)
  7. OpenCV相机帧率低以及花屏问题
  8. Linux电源管理系统架构和驱动(1)-Linux电源管理全局架构
  9. Linux-2.6.21的负载均衡
  10. python中字符编码:coding utf-8, unicde, defaultencoding, UnicodeDecodeError, UnicodeEncodeError