OceanBase内存管理小窍门

news2025/1/9 13:09:40

本文来自OceanBase热心用户的实践分享。

本文主要是对OceanBase内存管理的实用技巧分享,而并非直接深入OceanBase的代码层面进行阐述。​​​​​​​

阅读本文章你将了解:

  1. 重载运算符new 与malloc在返回值上区别?
  2. 在ceph 双向链表新用法,一个类定义时候 成员变量就是包含了 双向链表节点,可以通过该节点反推 类其他变量吗?
  3. 在stl中 中如何利用单链表存储申请批量对象?从对象中拿出固定字节就就可充当单链表?
  4. ob ob_allocator.h  与stl ob_allocator.h 分配器实现 有什么差别?

内存管理

C++中通过new和delete两个关键字进行动态内存管理。 c语言通过 malloc 和free 两个关键字进行动态内存管理

函数支持重载,运算符同样也支持重载

​     C++的提供了 重载运算符这一特性,  本质也是operators()函数重载,当遇到该运算符时就调用函数一样。

运算符重载的限制

1723207980

小提示:Markdown左对,在原来基础上,后面一个空格就解决了 右对齐HTML css语法

重载运算符new


throwing (1)	void* operator new (std::size_t size); 
// throwing allocation ,On failure, it throws a bad_alloc exception

nothrow (2)	    void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;
//nothrow allocation   on failure it returns a null pointer  instead of throwing an exception

placement (3)	void* operator new (std::size_t size, void* ptr) noexcept;
//placement Simply returns ptr (no storage is allocated).
// A pointer to an already-allocated memory block 
    

代码示例

  MyClass * p1 = new MyClass();
// allocates memory by calling: operator new (sizeof(MyClass))
// and then constructs an object at the newly allocated space

  std::cout << "2: ";
  MyClass * p2 = new (std::nothrow) MyClass();
// allocates memory by calling: operator new (sizeof(MyClass),std::nothrow)
// and then constructs an object at the newly allocated space

std::cout << "3: ";	
new (p2) MyClass();//p2
delete p1;
delete p2;

malloc

https://en.cppreference.com/w/c/memory/malloc
void *malloc( size_t size );
Allocates size bytes of uninitialized storage,
alloc is thread-safe
    
Parameters
size	-	number of bytes to allocate 
sizeof  Queries size of the object or type.

On failure, returns a null pointer.

ob代码:ob_alter_table_resolver.cpp

 //申请批量内存时候使用,
  __MemoryContext__ *tmp = new (std::nothrow) __MemoryContext__();
  abort_unless(tmp != nullptr); //

void *tmp_ptr = NULL;
 common::ObIAllocator *allocator_;//分配器
  if (NULL == (tmp_ptr = (ObAlterPrimaryArg *)allocator_->alloc(sizeof(obrpc::ObAlterPrimaryArg)))) {
   } else {
      alter_pk_arg = new (tmp_ptr) ObAlterPrimaryArg(); //这里没有使用delete
  }

重载new运算符 使用场景 

  • 批量申请内容时候,使用std::nothrow 不抛出异常,通过返回值判断nullptr 来处理
  • C++ placement new与内存池有关系,能帮助更节省内存吗?不清楚继续看

有些时候我们需要能够长时间运行的程序(例如监听程序,服务器程序)对于这些7*24运行的程序,我们不应该使用标准库提供的new 和 delete (malloc和free也算)。这是因为随着程序的运行,内存不断的被申请和被释放,频繁的申请和释放将会引发内存碎片、内存不足等问题,影响程序的正常运行。

更多的时候核心程序不允许内存申请失败,更不允许异常的出现,因此必须保证每次内存申请都是成功的(一般都是内核程序,当然不希望被中断的后台程序也是如此)。在这种极端要求下,内存池的好处就大大的凸现出来了。

在C++中,可以通过placement new 来实现内存池

如果分配能节省内存

内存池是很大概念,我平时用不到,上来不会说明原理,这是自己给自己挖坑,自己不会还要去自己讲清楚 先看一段代码,你发现什么错误吗?

一般定义链表,都有T 成员表示,但是ceph 中 定义 elist为什么没有,它怎么存储数据呢?

class Node
{
public:
    int data; //存储数据
    Node * last;
    Node * next;

};

class DoubleNode
{
private:
    Node * head;   //头结点
    Node * tail;   //尾节点
};

一般定义链表,都有T 成员表示,但是elist为什么没有,它怎么存储数据呢?

完整代码:
https://lab.forgefriends.org/ceph/ceph/-/blob/wip-rgw-placement-rule-empty/src/include/elist.h
/*
 * elist: embedded list. 这是一个双向链表,必须和类耦合起来。
 * elist(embedded list)是一种特殊类型的链表,它允许将链表节点直接嵌入到用户定义的数据结构中。这种设计使得每个数据项可以作为链表的一部分
 * requirements:
 *   - elist<T>::item be embedded in the parent class  定义类时候,必须使用 elist<T>::item 当作一个成员
 
 *   - items  are _always_ added to the list via the same elist<T>::item at the same
 *     fixed offset in the class. //items 在类中偏移量
 
 *   - begin(), front(), back() methods take the member offset as an argument for traversal.
 *
 */

//计算成员变量在类中的偏移量
#define member_offset(cls, member) ((size_t)(&((cls*)1)->member) - 1)

template<typename T>
class elist {
public:
  struct item {
    item *_prev, *_next;
    
    //通过偏移量
    T get_item(size_t offset) {
      ceph_assert(offset);
      return (T)(((char *)this) - offset); 
    }
  }; //elist<T>::item  是作为用户定义结构体的成员变量存在的。
   //意味着 item 的内存是从用户结构体的内存中分配的,而不是独立分配。

private:
  item _head;
  size_t item_offset;
}

 class iterator {
  private:
    item *head;
    item *cur, *next;
    size_t item_offset;
  public:
 
    T operator*() {
      return cur->get_item(item_offset);
    }
};

  • c++ 内存模型 (了解)

GCC 或 Clang,你可以使用 __builtin_offsetof 函数来获取成员的偏移量:

#define member_offset(cls, member) ((size_t)(&((cls*)1)->member) - 1)
class Example {
public:
    char a;      // 1 byte
    int b;       // 4 bytes, aligned to 4 bytes
    double c;    // 8 bytes, aligned to 8 bytes
    bool d;      // 1 byte, but often padded to align with 'b'
};
 size_t offset_a = __builtin_offsetof(Example, a);__

 size_t offset_b = __builtin_offsetof(Example, b)
     
     
 
能否提供一个完整的示例,展示如何在一个复杂的类中嵌入 `elist` 并使用它?

https://kimi.moonshot.cn/share/cqqc6ga1n4gqsenn4ur0
https://kimi.moonshot.cn/share/cqqcdsdskq8g1pv5ces0

STL源码剖析 by 侯捷 提到一个同样技巧

资料:STL标准库与泛型编程
  • what:关于STL中空间配置器中free_list的理解,理解不了_Obj 单链表将多个 对象组织起来?

1723208305

union _Obj {
        union _Obj* _M_free_list_link; // 单链表
        char _M_client_data[1];    /* The client sees this.        */
  }; 关于STL中空间配置器中free_list的理解
  • how:参考资料

自己动手实现STL 01:内存配置器的实现(stl_alloc.h)
https://github.com/wangcy6/sgi-stl/blob/master/stl_alloc.h
https://www.cnblogs.com/wangjzh/p/4097355.html

https://github.com/wangcy6/STLSourceCodeNote

第一级配置器malloc_alloc 就是,直接调用系统的malloc分配内存

//第一级配置器malloc_alloc 就是,直接调用系统的malloc分配内存
typedef __malloc_alloc_template<0> malloc_alloc;

template <int __inst> //这个模板没啥意义,区分一级二级区别
class __malloc_alloc_template {
private:
  static void* _S_oom_malloc(size_t);
  static void* _S_oom_realloc(void*, size_t);
public:
  static void* allocate(size_t __n)
  {
    void* __result = malloc(__n);
    if (0 == __result)  //malloc是否返回0
         __result = _S_oom_malloc(__n); //分配失败继续分配
    return __result;
  }
  static void deallocate(void* __p, size_t /* __n */)
  {
    free(__p);
  }
}

第二级配置器(Second-level allocator):。

default_alloc 尝试通过分配大块内存(称为 "chunks")来减少内存碎片,并使用这些大块内存来满足较小的内存请求。 它使用一个自由列表(free list)机制来管理这些大块内存中的小块内存。

default_alloc 可以是线程安全的,并且提供了更好的内存局部性和缓存性能。

//第二级配置器

typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
template <bool threads, int inst>
class __default_alloc_template {
  union _Obj {
        union _Obj* _M_free_list_link;
        char _M_client_data[1];    /* The client sees this.        */
  };
}

_S_refill(size_t __n) 
{
    // 定义分配的对象数量为20,这个值可以根据需要调整。
    int __nobjs = 20;
    
    // 调用 _S_chunk_alloc 函数分配足够存储 __nobjs 个大小为 __n 的对象的内存块。
    char* __chunk = _S_chunk_alloc(__n, __nobjs);
    
    // __my_free_list 指向适当大小的自由列表的指针。
    _Obj* __STL_VOLATILE* __my_free_list;
    
    // __result 指向新分配的内存块的起始位置,将被返回给调用者。
    _Obj* __result;
    
    // __current_obj 和 __next_obj 用于遍历和设置对象链表的指针。
    _Obj* __current_obj;
    _Obj* __next_obj;
    
    // __i 是循环计数器。
    int __i;

    // 如果只分配了一个对象,就直接返回这个对象的内存。
    if (1 == __nobjs) return(__chunk);
    
    // 计算并获取对应大小的自由列表。
    __my_free_list = _S_free_list + _S_freelist_index(__n);

    // 构建内存块内的自由链表。
    // __result 初始化为指向内存块的起始位置。
    __result = (_Obj*)__chunk;
    // 第一个对象之后的对象地址设置为自由链表的头。
    *__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
    
    // 循环将内存块分割成多个对象,并用 _M_free_list_link 将它们链接起来。
    for (__i = 1; ; __i++) {
        // __current_obj 指向当前正在处理的对象。
        __current_obj = __next_obj;
        
        // 计算下一个对象的地址。
        __next_obj = (_Obj*)((char*)__next_obj + __n);
        
        // 如果这是分配的最后一个对象,将其 _M_free_list_link 设置为 NULL,结束链表。
        if (__nobjs - 1 == __i) {
            __current_obj -> _M_free_list_link = 0;
            break;
        } else {
            // 否则,将当前对象的 _M_free_list_link 设置为指向下一个对象。
            __current_obj -> _M_free_list_link = __next_obj;
        }
    }
    
    // 返回可以立即使用的首个对象的地址。
    return(__result);
}

1723208916

OceanBase怎么做的

  • 先看例子
 ParseNode *key_child_node;
 key_child_node = static_cast<ParseNode*>(allocator.alloc(sizeof(ParseNode))) //
 key_child_node = new(key_child_node) ParseNode;
 

oceanbase/deps/oblib/src/lib/allocator/ob_allocator.h
 class ObAllocator : public ObIAllocator//直接看看发狂,概念太多,还是stl看着舒服//

  • 参考:从0到1 OceanBase原生分布式数据库内核实战进阶版

    从0到1 OceanBase原生分布式数据库内核实战进阶版

1723208681

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

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

相关文章

[240815] GPT-4o 系统安全卡 | AMD 完成对 Silo AI 的收购,加速 AI 模型在硬件上的开发和部署

目录 GPT-4o 系统安全卡AMD 完成对 Silo AI 的收购&#xff0c;加速 AI 模型在硬件上的开发和部署 GPT-4o 系统安全卡 一、概述 GPT-4o 是一个多模态模型&#xff0c;可以处理文本、音频、图像和视频输入&#xff0c;并生成文本、音频和图像输出。本文概述了 GPT-4o 发布前的…

Azure OpenAI Swagger Validation Failure with APIM

题意&#xff1a;Azure OpenAI Swagger 验证失败与 APIM 问题背景&#xff1a; Im converting the Swagger for Azure OpenAI API Version 2023-07-01-preview from json to yaml 我正在将 Azure OpenAI API 版本 2023-07-01-preview 的 Swagger 从 JSON 转换为 YAML。 My S…

快速排序算法详解及Python实现

目录 引言 快速排序算法步骤 快速排序的Python实现 性能分析 注意事项 引言 快速排序&#xff08;Quick Sort&#xff09;是一种高效的排序算法&#xff0c;由C. A. R. Hoare在1960年提出。它的基本思想是&#xff1a;通过一趟排序将待排序的数据分割成独立的两部分&…

数维原动:如何应对亿级规模的数据分析挑战|OceanBase案例

本文作者&#xff1a;贾万利&#xff0c;北京数维原动教育科技 业务背景 作为国内专注于高端教育评估的机构&#xff0c;北京数维原动教育科技有限公司&#xff0c;其核心业务聚焦于中小学学校的诊断、评估与提升工作。自2009年起&#xff0c;公司便踏上了自主研发之路。经过十…

Visual Studio VS 插件之 ReSharper

集成在VS2022上的ReSharper暂无找到汉化方式&#xff0c;如果有大神可以汉化&#xff0c;请指导下。 首先ReSharper 是IDE 下的插件 主要是基于C# 语句优化的这么一个插件。 使用ReSharper可以使开发效率大大提高&#xff0c;但是也是比较吃电脑的配置。所以说如果配置低的小…

移动开发(一):使用.NET MAUI开发第一个安卓APP

目录 一、.NET MAUI官方介绍 二、开发工具安装 三、创建项目 四、Windows Machine模式运行 五、安卓虚拟机方式运行 六、总结 对于工作多年的C#程序员来说,近来想尝试开发一款安卓APP,考虑了很久最终选择使用.NET MAUI这个微软官方的框架来尝试体验开发安卓APP,毕竟是…

C/C++开发---全篇

1、统筹 学习目标&#xff1a; C/C、python精通。 就业匹配方向&#xff1a;专精一个领域&#xff0c;延长职业生涯。 &#xff08;1&#xff09;适配行业&#xff1b; &#xff08;2&#xff09;量化&#xff1b; &#xff08;3&#xff09;安全&#xff1b; &#xff08;4&…

嵌入式系统可靠性设计案例分析

目录 案例 【题目】 【问题 1】(共 9 分) 【问题 2】(共 8 分) 【问题 3】(共 8 分) 【问题 1】解析 【问题 2】解析 【问题 3】解析 相关知识 案例 阅读以下关于嵌入式系统可靠性设计方面的描述&#xff0c;回答问题 1 至问题 3。 【题目】 某宇航公司长期从事宇航装…

H7-TOOL混合脱机烧录以及1拖4不同的通道烧录不同的程序操作说明(2024-08-07)

【应用场景】 原本TOOL的1拖4是用于同时烧录相同程序给目标板&#xff0c;但有时候一个板子上有多个不同的MCU&#xff0c; 客户希望仅通过一个TOOL就可以完成对板子上多个MCU的烧录&#xff0c;也就是1拖4不同的通道烧录不同的程序&#xff0c;此贴为此制作。 【实验目标】…

Android+Jacoco+code-diff全量、增量覆盖率生成实战

背景 主要是记录下Android项目使用jacoco生成代码覆盖率的实战流程&#xff0c;目前已完成全量覆盖方案&#xff0c;仅使用jacoco就能实现&#xff1b; 由于我们的Android端是使用Java和kotlin语言,目前增量的方案code-diff仅针对Java代码&#xff0c;卡在kotlin文件的分析&am…

java 面试 PDF 资料整理

“尊贵的求知者&#xff0c;作者特此献上精心编纂的Java面试宝典PDF&#xff0c;这份资料凝聚了无数面试精华与实战经验&#xff0c;是通往Java技术殿堂的钥匙。若您渴望在Java编程的求职之路上稳健前行&#xff0c;只需轻轻一点&#xff0c;完成这象征支持与认可的一键三联&am…

Dubbo源码深度解析(五)

上一篇博客主要讲服务提供方服务的发布&#xff0c;以及Netty是如何启动的&#xff0c;客户端发过来的请求&#xff0c;会经过哪些处理器&#xff0c;以及补充之前没讲完的SPI机制等等。这篇博客将会接着继续讲&#xff0c;在看这篇博客之前&#xff0c;请先看上一篇博客&#…

VTK—vtkStructuredGrid提取维度面数据

1.在VTK自带的vtkStructuredGrid数据文件combq.bin和combxyz.bin 2.文件读取代码如下&#xff1a; //读取数据文件Create(vtkMultiBlockPLOT3DReader, reader);reader->SetXYZFileName("G:/Temp/vtkTest/combxyz.bin");reader->SetQFileName("G:/Temp/v…

Vitis AI 基本认知(训练过程)

目录 1. 目的 2. TensorBoard 2.1 In TensorFlow 2.2.1 安装 TensorBoard 2.2.2 导入必要的库 2.2.3 初始化 2.2.4 记录数据 2.2.5 启动 TensorBoard 2.2.6 刷新间隔 2.2 In PyTorch 3. 训练周期 Epoch 3.1 Epoch 3.2 Batch 3.3 Iteration 4. 总结 1. 目的 介绍…

传奇游戏发布渠道

传奇游戏发布渠道 回答&#xff1a;游戏发布平台|手机游戏发布平台 传奇游戏发布渠道作为游戏开发商直接控制的信息传播途径&#xff0c;其安全性自然有着较高的保障。首先&#xff0c;渠道通常会采用先进的加密技术和安全协议来保护数据传输过程中的安全&#xff0c;防止信息…

Centos 7 升级GCC时遇到 mirrorlist.centos.org; Unknown error“

问题描述 在执行如下操作的时候&#xff0c; yum install devtoolset-9-gcc devtoolset-9-gcc-c devtoolset-9-binutils 出现&#xff1a; 14: curl#6 - "Could not resolve host: mirrorlist.centos.org; Unknown error" 网上搜索了一下&#xff0c;原因是 mir…

redis集合若干记录

无序集合 redis通常使用字典结构保存集合数据&#xff0c;字典健存储集合元素&#xff0c;字典值为空。如果一个集合全为整数&#xff0c;使用字典就有点浪费了&#xff0c;redis使用intset保存。 插入元素到intset中 获取插入元素编码&#xff0c;如果插入元素编码级别高于int…

Chapter 36 PySpark数据计算

欢迎大家订阅【Python从入门到精通】专栏&#xff0c;一起探索Python的无限可能&#xff01; 文章目录 前言一、map算子二、flatMap算子三、reduceByKey算子四、filter算子五、distinct算子六、sortBy算子七、综合案例 前言 在大数据处理的时代&#xff0c;Apache Spark以其高…

猫头虎 分享:Python库 Pygame 的简介、安装、用法详解入门教程

猫头虎 分享&#xff1a;Python库 Pygame 的简介、安装、用法详解入门教程 &#x1f63a; 摘要&#xff1a;今天&#xff0c;猫头虎将带大家深入了解Python中常用的Pygame库。Pygame是开发2D游戏和多媒体应用的首选工具之一。在本文中&#xff0c;我们将从安装Pygame、了解Pyg…

深入学习零拷贝

在学习中遇到了一个问题就是什么是零拷贝&#xff0c;因此学习之后以此来记录一下。 零拷贝、直接I/O、异步I/O等&#xff0c;优化的目的就是为了提高系统的吞吐量&#xff0c;减少访问磁盘次数。访问磁盘的速度会比读写内存会慢十倍以上。因此就需要提高它的读写速度。 什么…