前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【力扣】设计内存分配器(高效实现)

【力扣】设计内存分配器(高效实现)

作者头像
六月丶
发布于 2023-10-17 08:38:11
发布于 2023-10-17 08:38:11
19000
代码可运行
举报
文章被收录于专栏:六月-游戏开发六月-游戏开发
运行总次数:0
代码可运行

题目

给你一个整数 n ,表示下标从 0 开始的内存数组的大小。所有内存单元开始都是空闲的。

请你设计一个具备以下功能的内存分配器:

分配 一块大小为 size 的连续空闲内存单元并赋 id mID 。 释放 给定 id mID 对应的所有内存单元。 注意:

多个块可以被分配到同一个 mID 。 你必须释放 mID 对应的所有内存单元,即便这些内存单元被分配在不同的块中。 实现 Allocator 类:

Allocator(int n) 使用一个大小为 n 的内存数组初始化 Allocator 对象。 int allocate(int size, int mID) 找出大小为 size 个连续空闲内存单元且位于 最左侧 的块,分配并赋 id mID 。返回块的第一个下标。如果不存在这样的块,返回 -1 。 int free(int mID) 释放 id mID 对应的所有内存单元。返回释放的内存单元数目。

示例

输入 ["Allocator", "allocate", "allocate", "allocate", "free", "allocate", "allocate", "allocate", "free", "allocate", "free"] [[10], [1, 1], [1, 2], [1, 3], [2], [3, 4], [1, 1], [1, 1], [1], [10, 2], [7]] 输出 [null, 0, 1, 2, 1, 3, 1, 6, 3, -1, 0]

解释

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Allocator loc = new Allocator(10); // 初始化一个大小为 10 的内存数组,所有内存单元都是空闲的。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 0 。内存数组变为 [1, , , , , , , , , ]。返回 0 。
loc.allocate(1, 2); // 最左侧的块的第一个下标是 1 。内存数组变为 [1,2, , , , , , , , ]。返回 1 。
loc.allocate(1, 3); // 最左侧的块的第一个下标是 2 。内存数组变为 [1,2,3, , , , , , , ]。返回 2 。
loc.free(2); // 释放 mID 为 2 的所有内存单元。内存数组变为 [1, ,3, , , , , , , ] 。返回 1 ,因为只有 1 个 mID 为 2 的内存单元。
loc.allocate(3, 4); // 最左侧的块的第一个下标是 3 。内存数组变为 [1, ,3,4,4,4, , , , ]。返回 3 。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 1 。内存数组变为 [1,1,3,4,4,4, , , , ]。返回 1 。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 6 。内存数组变为 [1,1,3,4,4,4,1, , , ]。返回 6 。
loc.free(1); // 释放 mID 为 1 的所有内存单元。内存数组变为 [ , ,3,4,4,4, , , , ] 。返回 3 ,因为有 3 个 mID 为 1 的内存单元。
loc.allocate(10, 2); // 无法找出长度为 10 个连续空闲内存单元的空闲块,所有返回 -1 。
loc.free(7); // 释放 mID 为 7 的所有内存单元。内存数组保持原状,因为不存在 mID 为 7 的内存单元。返回 0 。

提示

1 <= n, size, mID <= 1000 最多调用 allocate 和 free 方法 1000 次

解题思路

因为数据量不大,可以直接用数组来做,但这里提供另一种高效一些的实现方式:

我们可以用起始地址+大小的结构来表示一个内存块:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct Mem{
    int addr;
    int size;
};

同时优先使用地址更左侧的、大小合适的内存块,所以还提供一下比较器:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
inline bool operator<(const Mem &m1, const Mem &m2){
    if(m1.addr != m2.addr)
        return m1.addr < m2.addr;
    return m1.size < m2.size;
}

分配器在多次分配释放操作后可能会有大量离散的内存块,同时我们希望这些内存块能按起始地址保证有序,当回收一块内存块时也能尽快恢复有序,那么选用红黑树来存储就很合适了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
set<Mem> freeSet;

构造函数中初始化大小为n的内存块就可以这样表示了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Allocator(int n) {
    freeSet.insert({0, n});
}

再来看下释放操作,释放时是释放所有id为mID的内存单元,我们也希望释放时能快速找到所有id为mID的内存块,所以可以选用哈希表通过mID指向一个存储了所有该id的内存块的组成的链表:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
unordered_map<int, list<Mem>> id2NodeMap;

当分配一块大小为n,id为mID的内存块时,就把该内存块添加到对应链表上。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int allocate(int size, int mID) {
    int insertIdx = -1;
    for(auto it = freeSet.begin(); it != freeSet.end(); it++){
        if((*it).size >= size){
            Mem mem = (*it);
            freeSet.erase(it);
            freeSet.insert({mem.addr + size, mem.size - size});      //旧
            id2NodeMap[mID].push_back({mem.addr, size});             //使用
            insertIdx = mem.addr;
            break;
        }
    }

    return insertIdx;
}

释放时,遍历对应链表,将链表上的内存块依次放回freeSet中,但注意每放回一块内存块时,还需要检查该内存块在freeSet左右是否有相连的内存块,有的话需要合并,好在set提供的迭代器能在O(1)时间找到相邻内存块。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int free(int mID) {
    int num = 0;

    for(auto &mem : id2NodeMap[mID]){
        num += mem.size;
        auto freeIt = freeSet.insert(mem).first;
        auto tmpIt = freeIt;
        bool change = false;
        if(tmpIt!= freeSet.begin()){
            tmpIt--;
            if(tmpIt->addr + tmpIt->size == mem.addr){
                mem.size += tmpIt->size;
                mem.addr = tmpIt->addr;
                freeSet.erase(tmpIt);
                change = true;
            }
        }
        tmpIt = freeIt;
        tmpIt++;
        if(tmpIt != freeSet.end()){
            if(mem.addr + mem.size == tmpIt->addr){
                mem.size += tmpIt->size;
                freeSet.erase(tmpIt);
                change = true;
            }
        }
        if(change){
            freeSet.erase(freeIt);
            freeSet.insert(mem);
        }
    }
    id2NodeMap[mID] = list<Mem>();

    return num;
}

完整代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct Mem{
    int addr;
    int size;
};
inline bool operator<(const Mem &m1, const Mem &m2){
    if(m1.addr != m2.addr)
        return m1.addr < m2.addr;
    return m1.size < m2.size;
}
class Allocator {
private:
    set<Mem> freeSet;
    unordered_map<int, list<Mem>> id2NodeMap;
public:
    Allocator(int n) {
        freeSet.insert({0, n});
    }

    int allocate(int size, int mID) {
        int insertIdx = -1;
        for(auto it = freeSet.begin(); it != freeSet.end(); it++){
            if((*it).size >= size){
                Mem mem = (*it);
                freeSet.erase(it);
                freeSet.insert({mem.addr + size, mem.size - size});      //旧
                id2NodeMap[mID].push_back({mem.addr, size});             //使用
                insertIdx = mem.addr;
                break;
            }
        }

        return insertIdx;
    }

    int free(int mID) {
        int num = 0;

        for(auto &mem : id2NodeMap[mID]){
            num += mem.size;
            auto freeIt = freeSet.insert(mem).first;
            auto tmpIt = freeIt;
            bool change = false;
            if(tmpIt!= freeSet.begin()){
                tmpIt--;
                if(tmpIt->addr + tmpIt->size == mem.addr){
                    mem.size += tmpIt->size;
                    mem.addr = tmpIt->addr;
                    freeSet.erase(tmpIt);
                    change = true;
                }
            }
            tmpIt = freeIt;
            tmpIt++;
            if(tmpIt != freeSet.end()){
                if(mem.addr + mem.size == tmpIt->addr){
                    mem.size += tmpIt->size;
                    freeSet.erase(tmpIt);
                    change = true;
                }
            }
            if(change){
                freeSet.erase(freeIt);
                freeSet.insert(mem);
            }
        }
        id2NodeMap[mID] = list<Mem>();

        return num;
    }
};
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-08-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
LeetCode周赛323,LeetCode官方的福利专场
本次的周赛是第323场,是LeetCode官方的学习福利专场。除了前10名可以获得盲盒奖励之外,还有一些排名在幸运位置的同学可以获得LeetBook的奖励。并且,只要通过一题,就可以获得力扣的学习专属福利。属于是普天同庆大礼包了。
TechFlow-承志
2022/12/22
4110
LeetCode周赛323,LeetCode官方的福利专场
Linux内核最新的连续内存分配器(CMA)——避免预留大块内存【转】
在我们使用ARM等嵌入式Linux系统的时候,一个头疼的问题是GPU,Camera,HDMI等都需要预留大量连续内存,这部分内存平时不用,但是一般的做法又必须先预留着。目前,Marek Szyprowski和Michal Nazarewicz实现了一套全新的Contiguous Memory Allocator。通过这套机制,我们可以做到不预留内存,这些内存平时是可用的,只有当需要的时候才被分配给Camera,HDMI等设备。下面分析它的基本代码流程。
233333
2018/12/28
3.9K0
C++ 内存管理(一)
在编程时可以通过上图的几种方法直接或间接地操作内存。下面将介绍四种C++内存操作方法:
公众号guangcity
2019/09/20
1.6K0
C++ 内存管理(一)
Memcache内存分配机制
memcached 默认情况下采用了 Slab Allocator 的机制分配和管理内存. 在该机制出现之前内存分配简单的通过 malloc 和 free 来管理所有的记录, 旧的方式会导致产生很多内存碎片, 加重机器管理内存的负担, 甚至有可能导致操作系统比 memcached 进程本身还慢, Slab Allocator 则解决了该问题.
tunsuy
2022/10/27
7850
【c++实战项目】从零实现一个高并发内存池
当前项目是实现一个高并发的内存池,他的原型是google的一个开源项目tcmalloc,tcmalloc全称Thread-Caching Malloc,即线程缓存的malloc,实现了高效的多线程内存管理,用于替代系统的内存分配相关的函数(malloc、free)
用户10925563
2024/09/26
1540
【c++实战项目】从零实现一个高并发内存池
为什么 C++ 中需要内存分配器,而不能像 C 语言一样直接从操作系统申请内存
在现代软件开发中,性能、灵活性和资源管理是开发者需要高度关注的问题。C++ 作为一门兼具高效性和灵活性的编程语言,提供了许多用于内存管理的工具,其中内存分配器(allocator)是一项重要的特性。本文将探讨为什么 C++ 中需要引入内存分配器,而不能像 C 语言那样直接通过 malloc 或系统调用来申请内存。
编程小妖女
2025/01/17
1410
为什么 C++ 中需要内存分配器,而不能像 C 语言一样直接从操作系统申请内存
一文搞懂Go1.20内存分配器
关于Go内存分配器的分析文章很多,看到的比较经典的有刘丹冰Aceld的一站式Golang内存管理洗髓经,最近学习了该篇文章和其他相关文章,结合Go1.20最新的源码,复习了下Go内存分配的知识,输出了自己的学习笔记。要学习Go GC实现,需要先搞定内存分配,内存分配是GC垃圾回收的前传。
涂明光
2024/03/10
9380
Go语言内存分配器的实现
前面断断续续的写了3篇关于Go语言内存分配器的文章,分别是Go语言内存分配器设计、Go语言内存分配器-FixAlloc、Go语言内存分配器-MSpan,这3篇主要是本文的前戏,其实所有的内容本可以在一
李海彬
2018/03/19
1K0
Go语言内存分配器的实现
m7s v5 中实现优雅内存分配器
v4 中使用了链表存储了不同大小的内存块的方式进行内存池的实现,实际测试中发现内存浪费比较严重,因此如何设计出使用效率高,操作简洁的内存池就成了 v5 的一个任务。
我不是码神
2024/04/09
1040
m7s v5 中实现优雅内存分配器
引导内存分配器
1.引导内存分配器的作用因为内核里面有很多内存结构体,不可能在静态编译阶段就静态初始化所有的这些内存结构体。另外,在系统启动过程中,系统启动后的物理内存分配器本身也需要初始化,如伙伴分配器,那么伙伴分配器如何获取内存来初始化自己呢 ?为了达到这个目标,我们先实现一个满足要求的但是可能效率不高的笨家伙,引导内存分配器。用它来负责系统初始化初期的内存管理, 最重要的, 用它来初始化我们内存的数据结构, 直到我们真正的内存管理器被初始化完成并能投入使用, 我们将旧的内存管理器丢掉。
刘盼
2023/01/05
8910
引导内存分配器
从零开始学C++之STL(二):实现简单容器模板类Vec(vector capacity 增长问题、allocator 内存分配器)
首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下: template < class _Ty, class _Ax = allocator<
s1mba
2017/12/25
1.5K0
从零开始学C++之STL(二):实现简单容器模板类Vec(vector capacity 增长问题、allocator 内存分配器)
C++学习笔记-分配器,基础学习
它是要生成的对应对象空间的个数,比如size * sizeof(T):生成size个T对象的空间;size类型一般为ptrdiff_t,一般用于定义两个指针的距离,,因为两个指针的加减,结果已经不再是指针,而是一种距离的概念,,因此C++引入ptrdiff_t的概念,相当于long int , size_t 可以理解为 long long unsigned int....。
买唯送忧
2021/05/06
5470
Netty13# 池化内存分配器
PooledByteBufAllocator作为池化内存分配的入口,提供了众多的配置参数和便捷方法。这篇主要撸下他们大体都啥含义、干啥用的。为后面池化内存其他组件做铺垫。
瓜农老梁
2021/03/16
1.1K0
高效内存管理:探索C++17中的pmr模块
在C++17之前,标准库提供了std::allocator,而在C++17中,这一功能得到了加强,引入了polymorphic_allocator。
公众号guangcity
2024/01/23
2.1K0
高效内存管理:探索C++17中的pmr模块
三张图带你弄懂STL中内存分配器
还是来先通过思维导图来看一下本篇文章会从哪些方面来讲解stl中内存分配器和萃取器,如下:
cpp加油站
2021/06/07
2.2K0
三张图带你弄懂STL中内存分配器
Go语言的内存管理和垃圾回收
内存管理是指操作系统或编程语言运行时对内存资源的分配、使用和回收的过程。在Go语言中,内存管理包括堆内存和栈内存的分配与回收。
数字扫地僧
2024/06/24
1710
Golang语言--内存分配器的实现
我把整个核心代码的逻辑给抽象绘制出了这个内存布局图,它基本展示了Go语言内存分配器的整体结构以及部分细节(这结构图应该同样适用于tcmalloc)。从此结构图来看,内存分配器还是有一点小复杂的,但根据具体的逻辑层次可以拆成三个大模块——cache,central,heap,然后一个一个的模块分析下去,逻辑就显得特别清晰明了了。位于结构图最下边的Cache就是cache模块部分;central模块对应深蓝色部分的MCentral,central模块的逻辑结构很简单,所以结构图就没有详细的绘制了;Heap是结构图中的核心结构,对应heap模块,也可以看出来central是直接被Heap管理起来的,属于Heap的子模块。
李海彬
2018/07/26
8410
Golang语言--内存分配器的实现
2. Linux-3.14.12内存管理笔记【系统启动阶段的memblock算法(2)】
memory:表示可用可分配的内存; 结束完memblock算法初始化前的准备工作,回到memblock算法初始化及其算法实现上面。memblock是一个很简单的算法。
233333
2019/09/27
1.2K0
linux内存源码分析 - SLAB分配器概述
之前说了管理区页框分配器,这里我们简称为页框分配器,在页框分配器中主要是管理物理内存,将物理内存的页框分配给申请者,而且我们知道也可页框大小为4K(也可设置为4M),这时候就会有个问题,如果我只需要1KB大小的内存,页框分配器也不得不分配一个4KB的页框给申请者,这样就会有3KB被白白浪费掉了。为了应对这种情况,在页框分配器上一层又做了一层SLAB层,SLAB分配器的作用就是从页框分配器中拿出一些页框,专门把这些页框拆分成一小块一小块的小内存,当申请者申请的是小内存时,系统就会从SLAB中获取一小块分配给
233333
2018/07/04
2.1K0
std源码剖析及C++内存管理(二)
在第一节中提到,malloc的内存块布局如上,其中cookie(记录区块大小)小,浪费率高,因为cookie始终占8字节。cookie是我们不需要的,如果大量调用malloc的话cookie总和会增多,这会造成较大的浪费,因此减少malloc调用次数,去掉cookie总是好的。
公众号guangcity
2019/09/20
1.7K0
std源码剖析及C++内存管理(二)
相关推荐
LeetCode周赛323,LeetCode官方的福利专场
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验