频繁的分配和回收内存会严重降低程序的性能。原因在于默认的内存管理器是通用的、应用程序可能会以某种特定的方式使用内存,并且为不需要的功能付出性能上的代价。通过开发专用的内存管理器可以解决这个问题。
两个方面考虑内存管理器的设计:大小和并发
固定大小、可变大小
单线程:内存管理器局限在一个线程内,内存被一个线程使用
多线程:内存管理器被多个线程并发的使用,需包含互斥执行的代码段。无论什么时候,只有一个线程在执行一个代码段
示例:
class Rational{
public:
Rational (int a = 0, int b = 1) : n(a), d(b) {}
private:
int n; //分子
int d; //分母
};
Rational *array = new Rational(i);
delete array; // 循环执行100万次,1500ms
版本1:专用的Rational内存管理器
避免频繁使用默认内存管理器,Rational需要维护一个预先分配的Rational对象的静态链接链表,列出空闲的可用对象,当需要Rational对象时,从空闲列表取出一个即可,使用后再把它放回空闲列表以便今后分配。
class NextOnFreeList{
public:
NextOnFreeList *next;
};
空闲列表被声明为一个有NextOnFreeList元素组成的列表,这样空闲列表的每个元素都声明为NextOnFreeList结构,但他们也是Rational对象。
为了遍历对象,每个Rational对象的前几个字节用于指向空闲列表的下一个对象(next)。可以通过将Rational对象转换成指向NextOnFreeList类型的指针来实现。这样空闲列表双重身份:Rational对象的序列andNextOnFreeList元素的序列
class Rational{
...
static NextOnFreeList *freelist;
};
空闲列表声明为Rational静态成员,Rational的New和delete操作符可以管理该静态列表,重载全局的new和delete。
class Rational{
public:
inline void *operator new (size_t size);
inline void operator delete (void *doomed, size_t size);
static void newMemPool() { expandTheFreeList(); }
static void deleteMemPool();
private:
static NextOnFreeList *freelist;
static void expandTheFreeList();
enum { EXPANSION_SIZE = 32};
int n;
int d;
};
new()是在空闲列表中分配一个Rational对象,列表为空就扩展列表。先去掉空闲列表的头部,调整完指针后再返回head。
inline void* Rational::operator new(szie_t size) {
if(0 == freelist) {
expandTheFreeList();
}
NextOnFreeList *head = freelist;
freelist = head->next;
return head;
}
delete()把Rational对象直接添加到空闲列表的头部,以返回一个Rational对象。这样每次delete都会使得freelist变为delete调的空间,而其next则指向之前的freelist,因此可以继续形成一条链
inline void Rational::operator delete(void *doomed, size_t size) {
NextOnFreeList *head = static_cast<NextOnFreeList *> doomed;
head->next = freelist;
freelist = head;
}
Rational和NextOnFreeList之间的类型转换有危险。必须确保空闲列表的元素足够大以支持任意一种类型。因此,当我们用Rational对象填充空闲列表时,需要比较两个类型之间的大小,从而分配较大的一个。
void Rational::expandTheFreeList() {
size_t size = (sizeof(Rational) > sizeof(NextOnFreeList *)) ?
sizeof(Rational) : sizeof(NextOnFreeList *);
NextOnFreeList *runner = static_cast<NextOnFreeList *> new char[size];
freelist = runner;
for(int i = 0; i < EXPANSION_SIZE; i++) {
runner->next = static_cast<NextOnFreeList *> new char[size];
runner = runner->next;
}
runner->next = 0;
}
void Rational::deleteMemPool() {
NextOnFreeList *nextPtr;
for(nextPtr = freelist; nextPtr != NULL; nextPtr = freelist) {
freelist = freelist->next;
delete []nextPtr;
}
}
重复性能测试
for(500){
for(1000){
array[1-1000] = new Rational(i);
}
for(1000){
delete array[1-1000];
}
执行时间从1500描述将至43ms。
版本2:固定大小对象的内存池
版本1只可以管理Rational对象,想管理多个对象的话,用模板。因为Rational可以看出,内存管理逻辑是独立于Rational之外的。
template <class T>
class MemoryPool {
public:
MemoryPool (size_t size = EXPANSION_SIZE) ;
~MemoryPool ();
inline void* alloc (size_t size);
inline void free (void *someElement);
private:
MemoryPool<T> *next;
void expandTheFreeList (int howMany = EXPANSION_SIZE);
};
template <class T>
MemoryPool<T>:: MemoryPool (size_t size = EXPANSION_SIZE) {
expandTheFreeList(size);
}
template <class T>
MemoryPool<T>:: ~MemoryPool () {
MemoryPool<T> *nextPtr = next;
for(nextPtr =next; nextPtr != NULL; nextPtr = next) {
next = next->next;
delete []nextPtr;
}
}
template <class T>
inline void* MemoryPool<T>:: alloc (size_t size) {
if(!next) expandTheFreeList();
MemoryPool<T> *head = next;
next = head->next;
return head;
}
template <class T>
inline void MemoryPool<T>:: free (void *doomed) {
MemoryPool<T> *head = static_cast<MemryPool<T> *> doomed;
head->next = next;
next = head;
}
template <class T>
void MemoryPool<T>:: expandTheFreeList (int howMany = EXPANSION_SIZE) {
size_t size = (sizeof(T) > sizeof(MemoryPool<T> *)) ?
sizeof(T) > sizeof(MemoryPool<T> *);
MemoryPool<T> *runner = static_cast< MemoryPool<T> *> new char[size];
next = runner;
for(int i = 0; i < howmany; i++) {
runner->next = static_cast< MemoryPool<T> *> new char[size];
runner = runner->next;
}
runner->next = 0;
}
上述代码解析:与版本1类似,alloc函数负责扩充,free函数负责把T元素放回空闲列表,以此来释放它。
Rational就不需要再维护自己的空闲列表,因此Rational变为
class Rational{
public:
Rational (int a = 0, int b = 1) : n(a), d(b) {}
void* operator new(size_t size) { return memPool->alloc(szie); }
void operator delete (void *doomed, size_t size) { memPool->free(doomed); }
static void newMemPool() { memPool = new MemoryPool<Rational>(); }
static void deleteMemPool() { delete memPool; }
private:
static MemryPool<Rational> *memPool;
int n;
int d;
};
MemoryPool<Rational> *Rational::memPool = 0;
int main(){
Rational *array[1000];
Rational::newMemPool();
for(01-500)
for(1-1000)
array[i] = new Rational(i);
for(1-1000)
delete array[i];
Rational::deleteMemPool();
}
这里的耗时大约是63ms
注意:之前的static_cast的类型转换在编译时可能报错,可以采用强制类型转换
版本3:单线程可变大小的内存管理器
版本2分配的是固定大小的内存,但有些程序需要分配可变大小的内存。
下面是Web服务器中常用的方式:
MemoryChunk类取代了之前版本中使用的NextOnFreeList类,他把不同大小的内存块连接起来形成块序列。
完整代码如下(#####可运行)
#include<iostream>
using namespace std;
#include <string>
class MemoryChunk{
public:
MemoryChunk(MemoryChunk *nextChunk, size_t chunkSize);
~MemoryChunk();
inline void *alloc(size_t size);
inline void free(void* someElement);
MemoryChunk *nextMemChunk(){ return next; }
size_t spaceAvailable(){ return chunkSize - bytesAlreadyAllocated; }
enum {DEFAULT_CHUNK_SIZE = 4096};
private:
MemoryChunk *next;
void *mem;
size_t chunkSize;
size_t bytesAlreadyAllocated;
};
// MemoryChunk是NextOnFreeList的一个更为简洁的版本。将next指针从用于已分配对象的实际内存中分离出来。MemoryChunk使用显式的next和mem指针因此不需要转换。
//MemoryChunk首先确定内存块大小(mem指向),然后next链接下一个MemoryChunk内存块
MemoryChunk::MemoryChunk(MemoryChunk *nextChunk, size_t reqSize){
chunkSize = (reqSize > DEFAULT_CHUNK_SIZE) ? reqSize : DEFAULT_CHUNK_SIZE;
next = nextChunk;
bytesAlreadyAllocated = 0;
mem = new char[chunkSize];
}
MemoryChunk::~MemoryChunk() {
delete[]mem;
}
void* MemoryChunk::alloc(size_t requestSize){
void *addr = (void*)((size_t)mem + bytesAlreadyAllocated);
bytesAlreadyAllocated += requestSize;
return addr;
}
inline void MemoryChunk::free(void *doomed){}//无需担心空闲内存段的释放,对象被删除,内存块也就被释放了
//MemoryChunk只是辅助类,ByteMemoryPool类用它首先可变大小的内存管理。
class ByteMemoryPool{
public:
ByteMemoryPool(size_t initSize = MemoryChunk::DEFAULT_CHUNK_SIZE);
~ByteMemoryPool();
inline void* alloc(size_t size);
inline void free(void* someElement);
private:
MemoryChunk *listOfMemChunks;
void expandStorage(size_t reqSize);
};
//内存块列表可能包含多个块,但只有第一块可用于分配的内存,其他块表示已分配的内存。即列表的首个元素是唯一能够分配可用内存的块。
ByteMemoryPool::ByteMemoryPool(size_t initSize){
expandStorage(initSize);
}
ByteMemoryPool::~ByteMemoryPool(){
MemoryChunk *memChunk = listOfMemChunks;
while (memChunk){
listOfMemChunks = memChunk->nextMemChunk();
delete memChunk;
memChunk = listOfMemChunks;
}
}
//alloc如果确保有足够的可用空间,则会把分配任务托付给列表头的MemoryChunk:
void* ByteMemoryPool::alloc(size_t requestSize){
size_t space = listOfMemChunks->spaceAvailable();
if (space < requestSize){
expandStorage(requestSize); //表示如果当前块的大小不够的话就需要另外扩充新的request大小的MemoryChunk,并把它放在列表头部返回
}
return listOfMemChunks->alloc(requestSize);
}
inline void ByteMemoryPool::free(void *doomed){ listOfMemChunks->free(doomed); }
void ByteMemoryPool::expandStorage(size_t reqSize){
listOfMemChunks = new MemoryChunk(listOfMemChunks, reqSize);
}
//这里需要修改Rational类的实现,用ByteMemoryPool代替MemoryPool<Rational>对象。
class Rational{
public:
Rational(int a = 0, int b = 1) : n(a), d(b){}
void *operator new(size_t size){ return memPool->alloc(size); }
void operator delete(void *doomed){ memPool->free(doomed); }
static void newMemPool(){
memPool = new ByteMemoryPool();
}
static void deleteMemPool(){ delete memPool; }
private:
static ByteMemoryPool *memPool;
int n, d;
};
ByteMemoryPool* Rational::memPool = nullptr;
int main(){
Rational *array[1000];
Rational::newMemPool();
for (int j = 0; j < 500; j++){
for (int i = 0; i < 1000; i++){
array[i] = new Rational(i);
}
for (int i = 0; i < 1000; i++){
delete array[i];
}
}
Rational::deleteMemPool();
}
1. 常量 2.枚举 【public】enum 枚举名 枚举名命名规则要和Pascal 枚举跟Int之间是可以相互转换的 所有类型都可以转换成string类型 注意:如果要转换的数字不在枚举里,会原样输出; 如果在枚举里找没有的数值,会抛出异常 3.结构体 结构体嵌套结构体的话,嵌套的内容的变量要用下划线开头 4. 数组 5.函数 在类中定义函数,函数中不能在定义函数,这是错误的; 如果main跟...
此文是对《JAVA多线程编程核心技术》的一点总结,如果想要了解具体细节可以去看原书。 第六章 单列模式与多线程 我之前最经常使用的单列: 使用静态内置类实现单列模式...
前言:连接池管理数据库的连接,作用就是提高项目的性能。就是在连接池初始化的时候存入一定数量的连接,用的时候通过方法获取,不用的时候归还连接即可。常用的连接池是DBCP和C3P0,这一章节我们先来认识一下DBCP连接池的用法。 --------使用步骤------ 1.导入jar包(commons-dbcp-1.4.jar和commons-pool-1.5.6.jar)(请自行百度下载) 2.使用a...
内联类似于宏,在调用方法放入内部展开被调用方法,以此方法来代替方法的调用(内联显著提升性能,但同时增加了编译时间) 示例:方法调用代价(通常方法非调用/返回机制都会对寄存器进行存储和恢复操作) 寄存器介绍: *指令指针(Instruction Pointer,IP),也成为程序计数器 :存放吓一跳将要执行的指令地址。方法调用时,跳转到被调用方法的指令并修改IP。 *链接寄存器(Link Regis...
6.1.1 C++词法作用域 C++中有六种作用域: (1)块作用域 (2)函数作用域 (3)文件作用域 (4)整个程序作用域 (5)类作用域 (6)名字空间作用域 块作用域也是右开闭花括号定界的,块作用域和函数作用域的区别是函数有参数和名字。在程序执行时候调用函数时 ,就进入了函数作用域,块作用域是不能被调用的。在执行了位于前面的语句之后,块才能被执行。 在这里,类型Account的作用域是整个...
一、java反射 sinat_38259539的博客-Java基础之—反射(非常重要): https://blog.csdn.net/sinat_38259539/article/details/71799078 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法...
多线程简介...
...
判断是否是回文有三种方法: 一、 先使指针指向字符串中第一个元素和最后一个元素,进行比较: 如果不相同,则不是回文,返回 False; 如果相同,两边指针各往中间移动一个单位,继续比较。 若两指针相遇,证明字符串对称的位置元素相同,也就是回文,返回True 代码如下: 二、回文字符串的递归判断: 假设只...
chart_left1是一个全局变量,而 并没有这句代码,所以当我在本地登录成功后,画的图还是还是最初第一个图(正确的图被隐藏了)...