有关SGI STL的alloc

news2024/11/20 9:43:31

    在STL的使用者层面上,空间配置器一般是隐藏的,使用者不需要知道其具体实现细节即
可进行使用;但是从STL的实现角度来说,由于整个STL的操作对象都存放在容器之内,而容器
需要配置一定的空间来进行存放数据,因此,想要实现或者说深入了解STL的第一步便是掌握
空间配置器的原理。

 

目录

主要还是说说SGI版本的STL的配置器


我们知道,stl有容器,空间配置器,适配器,迭代器,仿函数以及算法这6个组件:他们之间的运行关系大概是:容器通过配置器去获得数据和存储空间,算法通过迭代器获取容器内容,仿函数可以协助算法完成不同的策略变化,配接器可以修饰或套界仿函数。

allocator是STL的重要组成,但是一般用户不怎么熟悉他,因为allocator隐藏在所有容器(包括vector)身后,默默完成内存配置与释放,对象构造和析构的工作

首先我们知道c++中内存分配和释放操作是由new和delete去完成的

class A {};
A* p = new A;
//...执行其他操作
delete p;

在我们使用new来建造一个对象的时候,一般是分为两个步骤的:
	1.调用::operator new()来构建空间;
	2.调用对象的构造函数。
	注:::operator new内部由malloc实现、省略指针转换这步。

	同理,当我们使用delete函数的时候:
	1.调用对象的析构函数
	2.调用::operator delete()释放空间。
	注:::operator delete内部由free实现

在STL的实现中,为了精细分工、STL allocator决定将上述步骤的两阶段操作分开来,内存
配置操作由alloc::allocate()负责,内存释放由alloc::deallocate负责,对象构建
操作由construct负责,对象析构操作由destroy负责。对于实现我就不过多赘述了

下面主要还是说说SGI版本的STL的配置器

对于SGI STL的实现来说、他这里含有了两个不同的空间配置器

第一个是一个符合部分标准、名为allocatr的配置器std::allocator


仅仅是将::operator new()和
::operator delete()做了一层简单的封装,因此效率比较差。

它存在的意义仅在于为用户提供一个兼容老代码的折衷方法,我们不建议使用,而且SGI内部也不使用这种标准的配置器,看一下了解一下

#ifnedf DEFALLOC_H
#define DEFALLOC_H

#include <new.h>
#include <stddef.h>
#include <stdlib.h>
#include <limits.h>
#include <iostream.h>
#include <algobase.h>

template <class T>
inline T* allocate(ptrdiff_t size, T*) {
    set_new_handler(0);
    T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
    if (tmp == 0) {
        cerr << "out of memory" << endl;
        exit(1);
    }
    return tmp;
}

template <class T>
inline void deallocate(T* buffer) {
    ::operator delete(buffer);
}

template <class T>
class allocator {
public:
    typedef T value_type;
    typedef T* pointer;
    typedef const T* const_pointer;
    typedef T& reference;
    typedef const T& const_reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;

    pointer allocate(size_type n) { return ::allocate((difference_type)n, (pointer)0); }
    void deallocate(pointer p) { ::deallocator(p); }
    pointer address(reference x) { return (pointer)&x; }
    const_pointer const_address(const_reference x) { return (const_pointer)&x; }
    size_type init_page_size() {
        return max(size_type(1), size_type(UINT_MAX/sizeof(T)));
    }
};

class allocator<void> {
public:
    typedef void* pointer;
};

第二个是我们需要重点掌握的特殊配置器 std::alloc

这个配置器是SGI STL的默认配置器,它在<memory>中实现。

<memory>中包含三个文件:

  • <stl_construct.h>:定义了全局函数construct()和destroy(),负责对象构造和析构。
  • <stl_alloc.h>:内存配置和释放在此处实现,其内部有两级配置器第一级结构简单,封装malloc()和free(),第二级实现了自由链表和内存池,用于提升大量小额内存配置时的性能。
  • <stl_uninitialiezed.h>:一些用于用于填充和拷贝大块内存的全局函数。

对象构造和/析构,与内存配置/释放是分离实现的。

SGI对于alloc函数的要求如下所示:
	1.向system heap要求空间
	2.考虑多线程状态
	3.考虑内存不足的应变措施
	4.考虑过多“小型区块”可能造成的内存碎片问题。

 

 两层配置器的关系如下:

根据情况来判定,如果配置区块大于128bytes,说明“足够大”,调用第一级配置器,而小于等于128bytes,则采用复杂内存池(memory pool)来管理。

同时为了自由选择,STL又规定了 __USE_MALLOC 宏,如果它存在则直接调用第一级配置器,不然则直接调用第二级配置器。SGI未定义该宏,也就是说默认使用第二级配置器

 由于一级配置器存在内存碎片的问题,所以有了二级配置器

转自《STL源码剖析》 的SGI STL特殊配置器的内部实现图

图示左边是用户代码,右边是STL的内部。

我们可以看到,用户代码实例化一个vector对象,vector对象调用alloc的接口,注意,不同于之前标准的allocator,这里不需要实例化一个空间配置器,只需要调用alloc的静态函数就行了。

std::alloc的接口与标准非常相似:

  • static T* allocate()函数负责空间配置,返回一个T对象大小的空间。
  • static T* allocate(size_t)函数负责批量空间配置。
  • static void deallocate(T*)函数负责空间释放。
  • static void deallocate(T*,size_t)函数负责批量空间释放。

在接口之下,我们看到还有两级配置器,上面的接口根据情况调用这两个配置器,第二级配置器实现了内存池和自由链表,当程序多次进行小空间的配置时,可以从内存池和自由链表中获取空间,减少系统调用,提升性能。当进行大空间的配置时,上面的接口直接调用第一级配置器。最终,它们都是用malloc()free()来配置和释放空间。

一般C++开发者了解到此已经足够应付大多数开发场景了。

这样看起来std::alloc的结构还算清晰,但是实际实现还会出现多种边界情况,比如,当系统调用malloc()空间不足时,我们需要让第一级配置器来处理,这可能涉及从第二级配置器中回收已经缓存但还未使用的空间,事情就变得繁琐了。

下面来手撕两层配置器的实现:

  • 一级空间配置器

一级配置器直接用C中的malloc()以及free()函数来进行处理,当我们的内存分配不足的时候,我们调用oom_malloc()函数和oom_realloc进行处理,这两个函数里面都有循环,不断地去调用“内存不足,要调用例程”,去处理。

#if 1
#include<new>
#define __THROW_BAD_ALLOC throw bad_alloc
#else !defined(__THROW_BAD_ALLOC)
#define __THROW_BAD_ALLOC cerr<<"out of memory"<<endl;
#endif

template<int inst>
class __malloc_alloc_template
{
private:
	static void* oom_malloc(size_t);//malloc--指针函数
	static void* oom_realloc(void*, size_t);//realloc--指针函数
	static void(*__malloc_alloc_oom_handler)();//函数指针
public:
	static void* allocate(size_t n)//申请空间
	{
		void* result = malloc(n);//第一级配置器直接使用malloc
		//如果无法满足需求,费用oom_malloc()
		if (result == 0) {
			result == oom_malloc(n);
		}
		return result;
	}
	static void deallocate(void* p, size_t)
	{
		free(p);//第一级配置器直接使用free()
	}
	static void* reallocate(void* p, size_t)//不够了追加,是对allocate的补救
	{
		void* result = realloc(p, new_sz);//第一级配置器直接使用realloc
		//无法满足需求,改用oom_realloc()
		if (result == 0) {
			result = oom_realloc(p, new_sz);
			return result;
		}
	} 
	//下面这个可以通过他去指定自己的
	//out-of-memory handler
	static void (*set_malloc_handler(void(*f)()))()
	{
		void (*old)() = __malloc_alloc_oom_handler;
		__malloc_alloc_oom_handler = f;
		return old;
	}
};
//初始值为0,可以在客户端自己设定
template<int inst>
void(*__malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;

template<int inst>
void* __malloc_alloc_template<inst>::oom_malloc(size_t n)
{
	void (*my_malloc_handler)();
	void* result;
	for (;;)//死循环,不断尝试释放,配置,释放,配置。。
	{	
		my_malloc_handler = __malloc_alloc_oom_handler;
		if (0 == my_malloc_handler)
		{
			__THROW_BAD_ALLOC;
		}
		(*my_malloc_handler)();//调用处理例程,企图释放内存
		result = malloc(n);//尝试配置内存
		if (result)
			return result;
	}
}
template<int inst>
void* __malloc_alloc_template<inst>::oom_realloc(void* p, size_t n)
{
	void (*my_malloc_handler)();
	void* result;
	for (;;)
	{ //不断尝试释放、配置、再释放、再配置。。
		my_malloc_handler = __malloc_alloc_oom_handler;
		if (0 == my_malloc_handler)
		{
			__THROW_BAD_ALLOC;
		}
		(*my_malloc_handler)();//调用处理例程、企图释放释放内存
		result = realloc(p, n);//再次尝试配置内存
		if (result)
			return result;
	}
}
typedef __malloc_alloc_template<0> malloc_alloc;

void Out_Of_Memory()//测试
{
	cout << "this is out of memory" << endl;
	// exit(1);
}
//测试
int main()
{
	__malloc_alloc_template<0>::set_malloc_handler(Out_Of_Memory);
	int* p = (int*)__malloc_alloc_template<0>::allocate(sizeof(int) * 536870911);
	return 0;
}
  • 二级空间配置器

    第二级空间配置器多了一些机制,避免太多小额区块造成的内存的碎片,小额区块带来的其实
不仅是内存碎片,配置时的额外负担也是一个大问题(cookie),SGI第二级配置器的做法是,
如果区块够大,超过4096bytes时,就移交第一级配置器,否则就以内存池的方式管理:每次
配置一大块内存,并维护对应的自由链表,下次若再有相同大小的内存需求,就直接从free list
中拔出。如果请求端释放小额内存,就由配置器回收到free list中,为了方便管理,SGI第二级
配置器会主动将任何小额区块的内存需求量上调至8的倍数。

 

 

typedef __malloc_alloc_template<0> malloc_alloc;
enum { __ALIGN = 8 };//小区块的上调边界
enum { __MAX_BYTES = 128 };//小型区块的上线
enum { __NFREELISTS = __MAX_BYTES / __ALIGN };//自由链表个数

template<bool threads, int inst>
class __default_alloc_template
{
private:
	//把ROUND_UP上调到8的倍数
	static size_t ROUND_UP(size_t bytes)
	{
		return (((bytes)+__ALIGN - 1) & ~(__ALIGN - 1));
	}
private:
	union obj//共用体
	{
		union obj* free_list_link;
		char client_data[1];
	};
private:
	//16个自由链表
	static obj* free_list[__NFREELISTS];
	//下面根据区块的大小,决定使用第n号free_list,n从1开始算
	static size_t FREELIST_INDEX(size_t bytes)
	{
		return ((bytes)+__ALIGN - 1) / __ALIGN - 1;
	}
	//返回一个大小为n的对象,并可能加入大小为n的其他区块到 free list
	static void* refill(size_t n)
	{
		int nobjs = 20;
		char* chunk = chunk_alloc(n, nobjs);
		obj** my_free_list;
		obj* result;
		char* current_obj, * next_obj;
		int i;
		if (1 == nobjs)
			return chunk;
		my_free_list = free_list + FREELIST_INDEX(n);
		result = (obj*)chunk;
		*my_free_list = next_obj = (obj*)(chunk + n);
		for (i = 1;; i++)//把free list各个节点串起来
		{
			current_obj = next_obj;
			next_obj = (obj*)((char*)next_obj + n);
			if (nobjs - 1 == i)
			{
				current_obj->free_list_link = 0;
				break;
			}
			else
			{
				current_obj->free_list_link = next_obj;
			}
		}
		return result;
	}
	//配置一大块空间,可以容纳nogjs个大小为size的区块
	//内存池
	static char* chunk_alloc(size_t size, int& nobjs)
	{
		char* result;
		size_t total_bytes = size * nobjs;  //8*20
		size_t bytes_left = end_free - start_free;
		if (bytes_left >= total_bytes)
		{//内存池剩余空间完全满足需求量
			result = start_free;
			start_free += total_bytes;
			return result;
		}
		else if (bytes_left >= size)
		{//不完全满足,但够一个以上的区块
			nobjs = bytes_left / size;
			total_bytes = size * nobjs;
			result = start_free;
			start_free += total_bytes;
			return result;
		}
		else
		{//一个区块也无法提供
			//320
			size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
			if (bytes_left > 0)
			{
				obj** my_free_list = free_list + REFFLIST_INDEX(bytes_left);
				((obj*)start_free)->free_list_link = *my_free_list;
				*my_free_list = (obj*)start_free;
			}
			start_free = (char*)malloc(bytes_to_get);
			if (0 == start_free)
			{
				int i;
				obj** my_free_list, * p;
				for (i = size; i <= __MAX_BYTES; i += __ALIGN)
				{
					my_free_list = free_list + FREELIST_INDEX(i);
					if (0 != p)
					{
						*my_free_list = p->free_list_link;
						start_free = (char*)p;
						end_free = start_free + i;
						return chunk_alloc(size, nobjs);
					}
				}
				end_free = 0;
				start_free = (char*)malloc_alloc::allocate(bytes_to_get);

			}
			heap_size += bytes_to_get;
			end_free = start_free + bytes_to_get;
			return (chunk_alloc(size, nobjs));
		}
	}
	static char* start_free;//内存池起始位置
	static char* end_free;//内存池结束位置
	static size_t heap_size;
public:
	//空间申请的接口
	//首先判断区块的大小,如果大于 128bytes –> 调用第一级配置器;
	//小于128bytes–> 就检查对应的 free_list(如果没有可用区块,就将区块上调至 8 倍数的边界,
	//然后调用 refill(), 为 free list 重新填充空间。
	static void* allocate(size_t n)
	{
		obj** my_free_list;
		obj* result;
		if (n > (size_t)__MAX_BYTES)  //如果>128 调用一级
		{
			return (malloc_alloc::allocate(n));
		}
		//寻找16个free list的适合的一个
		my_free_list = free_list + FREELIST_INDEX(n);
		result = *my_free_list;
		if (result == 0)
		{//没有找到可用的,准备重新填充free list
			void* r = refill(ROUND_UP(n));
			return r;
		}
		*my_free_list = result->free_list_link;
		return result;
	}
	//释放空间
	static void deallocate(void* p, size_t n)
	{
		obj* q = (obj*)p;
		obj* volatile* my_free_list;
		if (n > (size_t)__MAX_BYTES)
		{
			malloc_alloc::deallocate(p, n);
			return;
		}
	}
	static void* reallocate(void* p, size_t old_sz, size_t new_sz);

};
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::start_free = 0;
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::end_free = 0;

template<bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;
template<bool threads, int inst>
__default_alloc_template<threads, inst>::obj*
__default_alloc_template<threads, inst>::free_list[__NFREELISTS] = { 0,0,0,0,0,0 }

 推荐阅读:_herongweiV的博客-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/80597.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mybatis:动态SQL(8)

动态SQL1. 动态sql简介2. if3. where4. trim5. choose、when、otherwise6. foreachforeach实现批量添加foreach实现批量删除7. SQL片段8. 总结1. 动态sql简介 Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能&#xff0c;它存在的意义是为了解决拼接SQL语句…

I-04Python中与C语言STL部分模板的类似模块

C语言中,我们打ACM可以用<vector>、<stack>等模板来快速实现一些经典的数据结构,可我在很多地方都没找到Python中类似于C里面的STL模板这么好用的东西.于是我在Python的标准库里面总结了些模块来直接实现类似的功能(当然也可能是我真的没找到,如果各位来客有知道的…

【浅学Java】SpringMVC程序开发

SpringMVC程序开发1. 认识SpringMVC1.1 SpringMVC是什么1.2 SpringMVC的定义1.3 MVC和SpringMVC的关系经典问题&#xff1a;Spring / SpringBoot / SpringMVC有什区别2. 学习SpringMVC的思路3. Spring MVC的创建和连接3.0 创建方法3.1 使用到的一些注解3.2 返回一个页面3.3 关于…

Qt实现全局鼠标事件监听器-Windows

Qt实现全局鼠标事件监听器-Windows版&#x1f347; 文章目录Qt实现全局鼠标事件监听器-Windows版&#x1f347;1、概述&#x1f348;2、实现效果&#x1f349;3、实现方式&#x1f34a;4、关键代码&#x1f34b;5、源代码&#x1f34c;更多精彩内容&#x1f449;个人内容分类汇…

Quartz任务调度

Quartz概念 Quartz是openSymphony开源组织在Job scheduling领域的开源项目&#xff0c;它可以与J2EE与J2SE应用程序相结合&#xff0c;也可以单独使用。 Quartz是开源且具有丰富特性的“任务调度库”&#xff0c;能够集成于任何的Java应用&#xff0c;小到独立的应用&#xf…

支持向量机SVM

文章目录SVM简单理解SVM代码实现导入数据集SVM实现画出支持向量总结SVM简单理解 在下二维平面存在以下数据点&#xff0c;不同颜色代表不同类别&#xff0c;现在需要画一条直线&#xff0c;想将两个类别分别开来&#xff0c;当有新数据加入时&#xff0c;根据这条直线&#xf…

springboot+jsp母婴用品商城网站系统

开发语言&#xff1a;Java 后端框架&#xff1a;springboot(SpringSpringMVCMyBatis) 前端框架&#xff1a;jsp 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 母婴用品网站&#xff0…

客快物流大数据项目(九十六):ClickHouse的VersionedCollapsingMergeTree深入了解

文章目录 ClickHouse的VersionedCollapsingMergeTree深入了解 一、创建VersionedCollapsingMergeTree引擎表的语法 二、折叠数据

人工智能轨道交通行业周刊-第26期(2022.12.5-12.11)

本期关键词&#xff1a;智慧检修、障碍物检测、监管数据平台、ChatGPT、脑机接口、图像增强 1 整理涉及公众号名单 1.1 行业类 RT轨道交通中关村轨道交通产业服务平台人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁…

Canvas 性能优化:脏矩形渲染

大家好&#xff0c;我是前端西瓜哥。 使用 Canvas 做图形编辑器时&#xff0c;我们需要自己维护自己的图形树&#xff0c;来保存图形的信息&#xff0c;并定义元素之间的关系。 我们改变画布中的某个图形&#xff0c;去更新画布&#xff0c;最简单的是清空画布&#xff0c;然…

Java项目:SSM个人博客管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 管理员角色包含以下功能&#xff1a; 发博客,审核评论,博客增删改查,博客类别增删改查,修改导航,评论增删改查,个人信息修改,登陆页面等功能。 …

TOOD: Task-aligned One-stage Object Detection 原理与代码解析

paper&#xff1a;TOOD: Task-aligned One-stage Object Detection code&#xff1a;https://github.com/fcjian/TOOD 存在的问题 目标检测包括分类和定位两个子任务&#xff0c;分类任务学习的特征主要关注物体的关键或显著区域&#xff0c;而定位任务是为了精确定位整个…

SpringBoot yaml语法详解

SpringBoot yaml语法详解1.yaml基本语法2.yaml给属性赋值3.JSR303校验4.SpringBoot的多环境配置1.yaml基本语法 通常情况下&#xff0c;Spring Boot 在启动时会将 resources 目录下的 application.properties 或 apllication.yaml 作为其默认配置文件&#xff0c;我们可以在该…

【云原生 | Kubernetes 实战】11、K8s 控制器 Deployment 入门到企业实战应用(下)

目录 四、通过 k8s 实现滚动更新 4.3 自定义滚动更新策略 取值范围 建议配置 总结 测试&#xff1a;自定义策略 重建式更新&#xff1a;Recreate 五、生产环境如何实现蓝绿部署&#xff1f; 5.1 什么是蓝绿部署&#xff1f; 5.2 蓝绿部署的优势和缺点 优点&#x…

图数据库 Neo4j 学习之JAVA-API操作

Neo4j 系列 1、图数据库 Neo4j 学习随笔之基础认识 2、图数据库 Neo4j 学习随笔之核心内容 3、图数据库 Neo4j 学习随笔之基础操作 4、图数据库 Neo4j 学习随笔之高级操作 5、图数据库 Neo4j 学习之JAVA-API操作 6、图数据库 Neo4j 学习之SpringBoot整合 文章目录Neo4j 系列前…

mac pro M1(ARM)安装vmware虚拟机及centos8详细教程

前言 mac发布了m1芯片&#xff0c;其强悍的性能收到很多开发者的追捧&#xff0c;但是也因为其架构的更换&#xff0c;导致很多软件或环境的安装成了问题&#xff0c;这次我们接着来看如何在mac m1环境下安装centos8 Centos8安装安装vmware虚拟机Centos8 镜像支持M1芯片安装Cen…

DDPM原理与代码剖析

前言 鸽了好久没更了&#xff0c;主要是刚入学学业压力还蛮大&#xff0c;挺忙的&#xff0c;没时间总结啥东西。 接下来就要好好搞科研啦。先来学习一篇diffusion的经典之作Denoising Diffusion Probabilistic Models(DDPM)。 先不断前向加高斯噪声&#xff0c;这一步骤称为…

论文笔记(二十三):Predictive Sampling: Real-time Behaviour Synthesis with MuJoCo

Predictive Sampling: Real-time Behaviour Synthesis with MuJoCo文章概括摘要1. 介绍2. 背景3. MuJoCo MPC (MJPC)3.1. 物理模拟3.2. 目标3.3. 样条3.4. 规划师4. 结论4.1. 图形用户界面4.2. 例子5. 讨论5.1. 预测抽样5.2. 用例5.3. 局限和未来的工作文章概括 作者&#xff…

25-Vue之ECharts-基本使用

ECharts-基本使用前言ECharts介绍ECharts快速上手ECharts配置说明前言 本篇开始来学习下开源可视化库ECharts ECharts介绍 ECharts是百度公司开源的一个使用 JavaScript 实现的开源可视化库&#xff0c;兼容性强&#xff0c;底层依赖矢量图形 库 ZRender &#xff0c;提供直…

Oracle High Water Mark问题

公司写SQL时遇到一个奇怪的问题&#xff0c;往表中频繁插入和删除大量数据&#xff0c;几次操作后&#xff0c;使用Select查询(表中没数据)特别慢&#xff0c;后得知是高水位线的问题。 该问题已通过: truncate table tableName语句解决。 本想写篇文章详细记录一下的&#xff…