2.STL源码解析-空间配置器

news2024/10/7 10:15:05

2.STL源码解析-空间配置器alloc

空间配置器就是给容器分配空间的。像我们平时使用new和delete动态分配释放对象内存一样。空间配置器也封装了这些功能。但是STL的空间配置器不仅仅只简单调用分配空间,它在一些地方都做了优化来提升性能。

构造和析构

我们在调用new动态分配对象内存时,通常会先调用malloc分配空间然后调用默认构造函数。通过delete来释放内存时,会先调用默认析构函数来析构对象,然后调用free来释放空间。在new/delete中把这两步封装在了一起。

而在STL中空间分配则是将这两阶段操作区分开来。内存配置和释放操作由成员函数alloc:allocate()和alloc:deallocate()负责。对象的构造和析构由::construct()和::destory()负责。

这样做有什么好处?

有种情况就是默认的构造函数和析构函数其实并没有做业务处理,在STL中这类构造函数叫trivial constructor((直译:平凡的构造函数))和trivial deconstructor(平凡的析构函数),STL是否可以选择对于这类析构函数不做调用,来减少性能开销。特别是一次性构造或析构一段范围内的对象时,这里的开销节省还是可以的。STL的确这么做了。

在使用 allocator 分配内存时,可以上一节提到的traits技法来实现trivial constructor的判断,以此来避免额外的构造函数和析构函数调用的开销。

看看构造函数的源码:

// FUNCTION TEMPLATE uninitialized_default_construct
template<class _FwdIt> inline
	void _Uninitialized_default_construct_unchecked(const _FwdIt _First, const _FwdIt _Last, false_type)
	{	// default-initialize all elements in [_First, _Last), no special optimization
	_Uninitialized_backout<_FwdIt> _Backout{_First};
	for (; _Backout._Last != _Last; ++_Backout._Last)
		{
		::new (static_cast<void *>(_Unfancy(_Backout._Last))) _Iter_value_t<_FwdIt>;
		}

	_Backout._Release();
	}

template<class _FwdIt> inline
	void _Uninitialized_default_construct_unchecked(_FwdIt, _FwdIt, true_type)
	{	// default-initialize all elements in [_First, _Last), trivially default constructible types
		// nothing to do
	}

这两个函数的作用是在指定范围内(由 _First_Last 定义)执行构造操作,即调用元素类型的构造函数。

  • 上述两个模板函数分为两个构造版本,根据类型的是否是trivial constructor 进行选择使用。函数的目标是在指定范围内执行默认构造操作。
  • 对于 false_type 版本,即非trivial constructor 类型的版本,它使用 _Uninitialized_backout 类来执行默认构造。在循环中,对每个元素调用 ::new 运算符,使用 _Unfancy 将指针 _Backout._Last 转换为未修饰的指针,然后通过 _Iter_value_t<_FwdIt> 获得迭代器指向的元素类型,以调用相应类型的默认构造函数。
  • 对于 true_type 版本,即trivial constructor 类型的版本,因为这些类型的默认构造函数不执行任何实际操作,所以函数中没有特殊的优化,即“nothing to do”。

析构函数源码:

// FUNCTION TEMPLATE destroy_n
template<class _FwdIt,
	class _Diff> inline
	_FwdIt _Destroy_n1(_FwdIt _First, _Diff _Count, false_type)
	{	// destroy [_First, _First + _Count), no special optimization
	for (; 0 < _Count; ++_First, (void)--_Count)
		{
		_Destroy_in_place(*_First);
		}

	return (_First);
	}

template<class _FwdIt,
	class _Diff> inline
	_FwdIt _Destroy_n1(const _FwdIt _First, const _Diff _Count, true_type)
	{	// destroy [_First, _First + _Count), trivially destructible
	return (_STD next(_First, _Count)); // nothing to do
	}

这两个函数的作用是在指定范围内执行元素的销毁操作,即调用元素的析构函数。

…………性能优化点1

  • 上述两个模板函数分为两个析构版本,根据类型的是否是trivial deconstructor 进行选择使用。函数的目标是在指定范围内执行析构操作。
  • 对于 false_type 版本,即非trivial deconstructor 类型的版本,它使用循环对每个元素调用 _Destroy_in_place 函数,该函数执行析构操作。在循环中,对每个元素调用 _Destroy_in_place 函数,递减 _Count,直至 _Count 变为零。
  • 对于 true_type 版本,即trivial deconstructor 类型的版本,因为这些类型的析构函数不执行任何实际操作,所以函数中没有特殊的优化,即“nothing to do”。

空间配置与释放:alloc

STL的空间配置才是真正的核心点。为了避免过多的小型内存区块造成的内存碎片问题,STL设计了双层配置器来处理不同空间大小的内存申请场景。

  • 一级空间配置器:分配超过128bytes大小的内存使用。直接使用malloc和free来配置管理空间。
  • 二级空间配置器:分配小于128bytes大小的内使用。采用复杂的memory pool来管理空间。维护16个自由链表,负责16种小区块内存的配置能力。

是否只开放一级配置器还是同时开放一级和二级配置器是由__USE_MALLOC定义决定的。

在这里插入图片描述

一二级空间配置器都进行了标准的封装。通过allocate分配空间,通过deallcate释放空间

一级配置器

简单实现一个一级空间配置器

#include <cstdlib>
#include <new>  // 为了使用 std::bad_alloc

class __malloc_alloc_template{
public:
    // 分配内存
    static void* allocate(size_t size) {
        void* result = std::malloc(size);
				if(result == 0) result = oom_malloc(size);
				return result;
    }

    // 释放内存
    static void deallocate(void* ptr) {
        std::free(ptr);
    }

    // 重新分配内存
    static void* reallocate(void* ptr, size_t old_size, size_t new_size) {
        void* result = std::realloc(p, size);
				if(result == 0) result = oom_realloc(p, size);
				return result;
    }

    // 处理内存耗尽情况
    static void* oom_malloc(size_t size) {
        while (true) {
            // 不断尝试分配内存
            void* ptr = allocate(size);
            if (ptr != nullptr) {
                return ptr;  // 分配成功,返回指针
            }
            // 内存不足,尝试释放一些内存
            std::new_handler globalHandler = std::get_new_handler();
            if (globalHandler == nullptr) {
                throw std::bad_alloc();  // 如果没有新的处理器,抛出 bad_alloc 异常
            }
            globalHandler();  // 调用新的处理器尝试释放内存
        }
    }

    // 其他可能的成员函数,例如构造函数、析构函数等
};

一级空间配置器的allocate和realloc都是在调用malloc和realloc分配内存,当分配不成功时,改调用oom_malloc或者oom_realloc。后面两个函数会不断调用内存不足处理例程,去尝试释放多余的内存,再继续分配。如果globalHandler 未被客户端设置,则抛出异常。

二级空间配置器(内存池)

二级配置器多了一些机制,专门针对内存碎片。内存碎片化带来的不仅仅是回收时的困难,配置也是一个负担,额外负担永远无法避免,毕竟系统要划出这么多的资源来管理另外的资源,因此区块越小越多,额外负担率就越高。

第一级配置器是直接使用 malloc(), free(), realloc() 并配合类似 C++ get_new_handler 机制实现的。第二级配置器的工作机制要根据区块的大小是否大于 128bytes 来采取不同的策略。如果需要配置区块大于128bytes,就交给一级配置器。当小于128bytes,就交给内存池分配。

二级空间配置器需要维护16个free list,各自管理8,16,24,32,40,48,56,64,72,80,88,90,104,112,120,128bytes的小额区块。如果有小额区块申请,则从对应大小的free list中取出一块来使用。如下所示:
在这里插入图片描述

…………性能优化点2

这里可以了解一下free-list的节点结构:

union obj 
	{
		union  obj * free_list_link;
		char client_data[1];
	};

STL这里也做了性能优化。可以看到这里用到了union联合体,因为每个空闲链表区块的节点在空闲时需要作为链表连接下一个节点。在分配空间时又需要作为指针指向分配的空间。所以这里用到了联合体,一个obj节点在不同的时机代表两种不同的意义,但只用到了一个变量的空间。

简单实现一个二级空间配置器:核心的三个函数allocate、deallocate、refill、chunk_alloc

#include <cstdlib>   // 为了使用 malloc 和 free
#include <cstddef>   // 为了使用 size_t

// 定义二级空间配置器
template <bool threads, size_t inst>
class __default_alloc_template {
private:
    // free-list 节点的结构
    union obj {
        union obj* free_list_link;
        char client_data[1];
    };

    // 自由链表数组
    static obj* free_list[NFREELISTS];

    // 为了对齐,将 nbytes 上调至 8 的倍数
    static size_t ROUND_UP(size_t bytes) {
        return (bytes + ALIGN - 1) & ~(ALIGN - 1);
    }

    // 根据大小计算索引
    static size_t FREELIST_INDEX(size_t bytes) {
        return (bytes + ALIGN - 1) / ALIGN - 1;
    }
		static void* refill(size_t n);
    // 分配一大块内存
    static char* chunk_alloc(size_t size, int& nobjs);

public:
    // 分配内存
    static void* allocate(size_t n) {
			if (n > MAX_BYTES) {
        // 对于大于 MAX_BYTES 的内存块,使用第一级空间配置器分配
        // 这里简化为直接使用 malloc
        return std::malloc(n);
		    }
				obj* result;
		    obj* my_free_list = free_list + FREELIST_INDEX(n);
				result = *my_free_list;
		    if (my_free_list == nullptr) {
		        // 自由链表为空,重新填充
		        return refill(ROUND_UP(n));
		    }
		    //调整free_list
		    my_free_list = my_free_list->free_list_link;
		    return result;
		}

    // 释放内存
    static void deallocate(void* p, size_t n){
	    if (n > MAX_BYTES) {
	        // 对于大于 MAX_BYTES 的内存块,使用第一级空间配置器释放
	        // 这里简化为直接使用 free
	        std::free(p);
	        return;
	    }
	    // 将释放的内存块添加到自由链表中
	    size_t index = FREELIST_INDEX(n);
	    obj* my_free_list = static_cast<obj*>(p);
	    my_free_list->free_list_link = free_list[index];
	    free_list[index] = my_free_list;
		}
};

在这里插入图片描述在这里插入图片描述

当free_list上没有可用的区块了,就需要用到refill函数为free_list重新填充空间。

    // 重新填充自由链表
    static void* refill(size_t n) {
	    int nobjs = 20;  // 一次分配 20 个节点
	    char* chunk = chunk_alloc(n, nobjs);
	
	    if (nobjs == 1) {
	        return chunk;  // 如果只分配了一个节点,直接返回
	    }
	    // 将多余的节点加入到自由链表中
	    size_t index = FREELIST_INDEX(n);
	    obj* my_free_list = reinterpret_cast<obj*>(chunk + n);
	    free_list[index] = my_free_list;
	    for (int i = 2; i <= nobjs; ++i) {//从2开始,因为第一块区域需要返回去使用
	        my_free_list->free_list_link = reinterpret_cast<obj*>(chunk + i * n);
	        my_free_list = my_free_list->free_list_link;
	    }
	    my_free_list->free_list_link = nullptr;
	    return chunk;  
		}

从内存池中取空间出来,就需要用到chunk_alloc函数。

如下,书中提了一个示例:
在这里插入图片描述

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

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

相关文章

AI工具有哪些?国内4款主流的AI软件盘点推荐!

去年以GPT为代表的AI对话软件&#xff0c;再一次引发了人们对人工智能的关注和讨论&#xff0c;从小博自己的观察看&#xff0c;人们对AI工具持有3种态度—— “看不懂”&#xff1a;AI工具是一种新事物&#xff0c;但和自己的工作或所在的行业无关&#xff0c;不想在这上面花…

【pikachu csrf】

cxrf 个人理解getPOST 个人理解 当被攻击用户登陆访问网站时&#xff0c;在保持登陆状态时点击小黑子&#xff08;黑客&#xff09;搭建的恶意链接而导致用户受到攻击。 举个例子 我去攻击网站&#xff0c;但是我找不到漏洞&#xff0c;这个时候我注册一个账号&#xff0c;发现…

Linux第42步_移植ST公司uboot的第3步_uboot命令测试,搭建nfs服务器和tftp服务器

测试uboot命令&#xff0c;搭建nfs服务器和tftp服务器&#xff0c;是测试uboot非常关键的一步。跳过这一节&#xff0c;后面可能要踩坑。 一、输入“help回车”&#xff0c;查询uboot所支持的命令 二、输入“? bootz回车”&#xff0c;查询“bootz”怎么用 注意&#xff1a;和…

如何利用大模型结合文本语义实现文本相似度分析?

常规的文本相似度计算有TF-IDF&#xff0c;Simhash、编辑距离等方式&#xff0c;但是常规的文本相似度计算方式仅仅能对文本表面相似度进行分析计算&#xff0c;并不能结合语义分析&#xff0c;而如果使用机器学习、深度学习的方式费时费力&#xff0c;效果也不一定能达到我们满…

Java与SpringBoot:实现高效车险理赔信息管理系统

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

学习python第六天

一.if - else if 后面的语句是当判断条件成立时&#xff0c;需要执行的操作。 else 后面的语句是判断条件不成立时&#xff0c;执行的操作。 yourScore 80 myScore 90if yourScore > myScore:print("你的得分比我高") else:print("你的得分不比我高"…

【数据结构与算法】(5)基础数据结构之队列 链表实现、环形数组实现详细代码示例讲解

目录 2.4 队列1) 概述2) 链表实现3) 环形数组实现 2.4 队列 1) 概述 计算机科学中&#xff0c;queue 是以顺序的方式维护的一组数据集合&#xff0c;在一端添加数据&#xff0c;从另一端移除数据。习惯来说&#xff0c;添加的一端称为尾&#xff0c;移除的一端称为头&#xf…

VR全景技术可以应用在哪些行业,VR全景技术有哪些优势

引言&#xff1a; VR全景技术&#xff08;Virtual Reality Panorama Technology&#xff09;是一种以虚拟现实技术为基础&#xff0c;通过360度全景影像、立体声音、交互元素等手段&#xff0c;创造出沉浸式的虚拟现实环境。该技术不仅在娱乐领域有着广泛应用&#xff0c;还可…

Python命令行工具库之argcomplete使用详解

概要 命令行工具是开发者和系统管理员的得力助手&#xff0c;但随着命令行选项的增多&#xff0c;用户可能会感到困惑。Python 中的 argcomplete 库可以帮助轻松地为命令行工具添加自动补全功能&#xff0c;提高用户体验。本文将介绍如何使用 Python argcomplete 库实现命令行…

【Matplotlib】figure方法 你真的会了吗!?

&#x1f388;个人主页&#xff1a;甜美的江 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;matplotlib &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…

初始mach-o文件及在项目中应用

本文字数&#xff1a;2250字 预计阅读时间&#xff1a;15分钟 01 认识mach-o的必要性 了解mach-o的结构可以帮助认识系统加载二进制文件的动态链接和静态链接。应用层面&#xff0c;使用initialize的c函数计算启动时间耗时也需要以mach-o的结构知识为铺垫。还可以用在使用clang…

Stable Diffusion 模型下载:ReV Animated

文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十下载地址模型介绍 该模型能够创建 2.5D 类图像生成。此模型是检查点合并,这意味着它是其他模型的产物,以创建从原始模型派生的产品。 条目内容类型大模型

【c/python】GtkGrid

一、GtkGrid GtkGrid 是 GTK (GIMP Toolkit) 中的一个基础容器构件&#xff08;widget&#xff09;&#xff0c;它可以用来安排其他构件在一个灵活的多行多列的网格中。每个加入网格的构件都可以占据一个或多个行和列。由于 GtkGrid 提供了在二维空间中安排构件的方式&#xf…

kvm qemu 优化 windows 虚拟机速度

主要优化磁盘 io 和网络 io 都选为 virtio windows 驱动下载 https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.185-2/virtio-win-0.1.185.iso I also had incredibly slow performance with my virtual HDD. The followin…

【LeetCode】刷题总结 - 15. 三数之和

15. 三数之和 | LeetCode 思路 首先对 nums 进行排序&#xff0c;然后设置三个指针&#xff1a;left、mid、right&#xff1a; left 从最左边开始&#xff0c;依次向后遍历每次固定住 left 后&#xff0c;就化为一个 2sum 问题&#xff1a; mid left 1&#xff0c;right …

基于Java (spring-boot)的实验室管理系统

一、项目介绍 基于Java (spring-boot)的交通管理系统功能&#xff1a;注册登录、个人信息管理、驾驶证业务类型管理、机动车业务类型管理、新闻类型管理、违法处理业务类型管理、驾驶证业务管理、机动车业务管理、新闻管理、违法处理业务管理、用户管理。 二、作品包含 ​ 三、…

[软件工具]文档页数统计工具软件pdf统计页数word统计页数ppt统计页数图文打印店快速报价工具

文档页数统计工具软件——打印方面好帮手 在信息化时代&#xff0c;文档已成为我们工作、学习、生活中不可或缺的一部分。无论是学术论文、商业报告&#xff0c;还是个人日记&#xff0c;都需要我们对其进行有效的管理。而在这个过程中&#xff0c;文档页数统计工具软件就显得…

[office] 教你实现Excel中工作表重命名的诀窍 #知识分享#职场发展#其他

教你实现Excel中工作表重命名的诀窍 在Excel中要实现工作表的重命名其实不是难事&#xff0c;重在你要掌握技巧。一些初学者&#xff0c;可能还不是特别的懂。今天&#xff0c;小编就要一步步来教一下大家了。有两种方法&#xff0c;大家学好了。 方法一、打开excel表格&#x…

111.乐理基础-五线谱-五线谱的节奏型、打拍子

内容参考于&#xff1a;三分钟音乐社 上一个内容&#xff1a;110.乐理基础-五线谱-五线谱的速度-CSDN博客 首先必须先看 打拍子 这些东西 简谱里的节奏型总结图&#xff1a; 换成五线谱的节奏型&#xff1a;简谱里会把两个八分音符用根横线连起来&#xff0c;所以五线谱里也…

C#中dll引用常见错误

当你在使用C#开发程序时&#xff0c;经常会遇到需要引用外部的dll文件来扩展程序的功能或者使用一些第三方库。然而&#xff0c;在引用这些dll文件的过程中&#xff0c;有时候会遇到一些问题&#xff0c;比如上面提到的错误信息&#xff1a;“未能加载文件或程序集“System.Run…