特殊的工具和技术

--优化内存分配

引言:

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. golang学习笔记5 用bee工具创建项目 bee工具简介

    golang学习笔记5 用bee工具创建项目 bee工具简介 Bee 工具的使用 - beego: 简约 & 强大并存的 Go 应用框架https://beego.me/docs/instal ...

  2. C++ Primer学习笔记(三) C++中函数是一种类型!!!

    C++中函数是一种类型!C++中函数是一种类型!C++中函数是一种类型! 函数名就是变量!函数名就是变量!函数名就是变量! (---20160618最新消息,函数名不是变量名...囧) (---201 ...

  3. C++ Primer学习笔记(二)

    题外话:一工作起来就没有大段的时间学习了,如何充分利用碎片时间是个好问题. 接  C++ Primer学习笔记(一)   27.与 vector 类型相比,数组的显著缺陷在于:数组的长度是固定的,无法 ...

  4. Andorid:日常学习笔记(3)——掌握日志工具的使用

    Andorid:日常学习笔记(3)——掌握日志工具的使用 使用Android的日志工具Log 方法: Android中的日志工具类为Log,这个类提供了如下方法来供我们打印日志: 使用方法: Log. ...

  5. Java程序猿的JavaScript学习笔记(9—— jQuery工具方法)

    计划按例如以下顺序完毕这篇笔记: Java程序猿的JavaScript学习笔记(1--理念) Java程序猿的JavaScript学习笔记(2--属性复制和继承) Java程序猿的JavaScript ...

  6. APPCAN学习笔记003---原生开发与HTML5技术

    APPCAN学习笔记003---原生开发与HTML5技术 技术qq交流群:JavaDream:251572072 1.HTML5的优势:   HTML5强悍牢固的骨架   CSS3精致到每一个毛孔的皮 ...

  7. JUC源码学习笔记4——原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法

    JUC源码学习笔记4--原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法 volatile的原理和内存屏障参考<Java并发编程的艺术> 原子类源码基于JDK8 ...

  8. Java学习笔记 -- Java定时调度工具Timer类

    1 关于 (时间宝贵的小姐姐请跳过) 本教程是基于Java定时任务调度工具详解之Timer篇的学习笔记. 什么是定时任务调度 基于给定的时间点,给定的时间间隔或者给定的执行次数自动执行的任务. 在Ja ...

  9. C++ Primer 学习笔记_95_用于大型程序的工具 --多重继承与虚继承

    用于大型程序的工具 --多重继承与虚继承 引言: 大多数应用程序使用单个基类的公用继承,可是,在某些情况下,单继承是不够用的,由于可能无法为问题域建模,或者会对模型带来不必要的复杂性. 在这些情况下, ...

随机推荐

  1. dos批量替换当前目录后缀名

    有时候有些后缀名不满足条件,就需要进行批量的替换,如果人为的去替换,那么如果量少的话还好说,量多的话一个个去替换就太傻了,今天从网络上面查找了一些批量替换的dos命令,用起来还挺好用的,就直接把代码贴 ...

  2. poj 2786 - Keep the Customer Satisfied

    Description   Simon and Garfunkel Corporation (SG Corp.) is a large steel-making company with thousa ...

  3. [置顶] android关机闹钟设计思路

    1: 首先需要硬件支持,支持alarm中断触发开机,目前高通平台几乎都支持: 2:关机前需要在rtc-xxx.c中做到enable_irq_wake,和不disable alarm功能(默认开机后al ...

  4. 首个spring mvc 测试例子搭建遇到问题记录

    开发环境:jdk1.7 + tomcat7 + Eclipse Juno 首先下载spring 相关jar包 我用的是 spring-framework-3.2.1.RELEASE 下载地址:http ...

  5. 我在使用的Chrome插件

    首先本人为一名Android程序员,故下面的很多插件很多都是关于开发辅助相关的.当然还有涉及到其他方面的插件,比如社交,浏览,工具等.以下按照字母排序. 1.AdBlock The most popu ...

  6. Light OJ 1429 Assassin`s Creed (II) BFS+缩点+最小路径覆盖

    题目来源:Light OJ 1429 Assassin`s Creed (II) 题意:最少几个人走全然图 能够反复走 有向图 思路:假设是DAG图而且每一个点不能反复走 那么就是裸的最小路径覆盖 如 ...

  7. PreferenceActivity使用示例

    MainActivity如下: package cn.testpreferenceactivity; import android.content.SharedPreferences; import ...

  8. Delphi与Vista提供的UAC控制(1-代表资源编号,24-资源类型为RTMAINIFEST,最后用brcc32编译成资源文件)

    Vista提供的UAC机制,是Vista的新增功能之一.它的主要目的是防止对于操作系统本身的恶意修 改.如果想对于Vista的 系统设置进行改动,必须通过UAC的验 证才能够进行.通过这样的手段,大大 ...

  9. [Android学习笔记]扩展application

    扩展Application对象 每一个应用程序启动之后,都会分配一个linux用户,并且运行在一个独立的进程中.默认情况下,一个应用程序只会运行在一个进程中(可以通过配置android:process ...

  10. PageHeap,调试Heap问题的工具

    <Windows用户态程序高效排错>第二章主要介绍用户态调试相关的知识和工具.本文主要讲了PageHeap,调试Heap问题的工具. AD:51CTO学院:IT精品课程在线看! 2.4.2 ...