【 OpenGauss源码学习 —— 列存储(CStoreMemAlloc)】

列存储(CStoreMemAlloc)

  • 概述
  • CStoreMemAlloc 类
    • CStoreMemAlloc::Palloc 函数
    • CStoreMemAlloc::AllocPointerNode 函数
    • CStoreMemAlloc::FreePointerNode 函数
    • CStoreMemAlloc::Repalloc 函数
    • CStoreMemAlloc::Pfree
    • CStoreMemAlloc::Register 函数
    • CStoreMemAlloc::Unregister 函数
    • CStoreMemAlloc::Reset 函数
    • CStoreMemAlloc::Init 函数
  • 总结

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》一书以及OpenGauss社区学习文档和一些参考资料

概述

  学习完了 CUCUStorage 类,我们最后来学习一下 CStoreMemAlloc 类。CStoreMemAlloc 类提供了内存管理的功能,而 CUCUStorage 类负责处理列存储中的具体数据单元和物理存储。在实际的列存储系统中,这些类可能会协同工作以有效地管理内存和存储数据。

CStoreMemAlloc 类

  CStoreMemAlloc 类在列存储中扮演了一个内存管理的角色,帮助管理和释放与列存储操作相关的动态内存。这对于确保系统在处理大量数据时能够高效使用内存防止内存泄漏以及正确处理事务回滚等方面非常重要。类的定义如下:(函数路径:src/include/storage/cstore/cstore_mem_alloc.h

// 类定义:CStoreMemAlloc
// 用于管理从malloc中分配的内存指针
// 在事务出现错误时,可以在abortTransaction中重置malloc的内存
// 在事务提交时,可以重置malloc的内存
class CStoreMemAlloc {
public:
    static void* Palloc(Size size, bool toRegister = true);
    static void Pfree(void* pointer, bool registered = true);
    static void* Repalloc(void* pointer, Size size, Size old_size, bool registered = true);
    static void Register(void* pointer);
    static void Unregister(const void* pointer);
    static void Reset();
    static void Init();

private:
    static void* AllocPointerNode();
    static void FreePointerNode(PointerNode* pointer);

    // 保存所有分配的内存指针数组
    static THR_LOCAL PointerList m_tab[MaxPointersArryLen];
    // 记录分配的内存指针数量
    static THR_LOCAL uint64 m_count;

    // 保存已经分配的PointerNode的缓存
    static THR_LOCAL uint32 m_ptrNodeCacheCount;
    static THR_LOCAL PointerNode* m_ptrNodeCache[MaxPtrNodeCacheLen];
};

  下面我们分别来看看CStoreMemAlloc 类成员函数的作用是什么?

CStoreMemAlloc::Palloc 函数

  CStoreMemAlloc 类中的 Palloc 方法。该方法用于从系统中分配内存,并根据需要将内存指针注册到管理中。具体作用和功能如下:

描述:该方法用于从系统中分配内存,支持动态内存的分配操作。
参数:
    size:需要分配的内存大小。
    toRegister:表示是否需要将该内存指针在事务管理范围内进行注册。如果为 true,则注册;如果为 false,则不注册。
功能:
    确保要分配的内存大小大于0。
    调用内部的 InnerMalloc 方法进行实际的内存分配。
    如果内存分配失败,抛出内存不足的错误。
    如果需要在事务管理范围内注册该内存指针,则进行注册。
    返回分配的内存指针。

  函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp

/*
 * @Description: 从系统中分配内存
 * @Param[IN] size: 需要的内存大小
 * @Param[IN] toRegister: 如果这块内存在线程管理的范围内,则设置为 true;
 *                        如果这块内存在进程管理的范围内,则设置为 false。
 * @See also: 无
 */
void* CStoreMemAlloc::Palloc(Size size, bool toRegister)
{
    // 确保分配的内存大小大于0
    Assert(size > 0);

    // 调用内部的分配方法分配内存
    void* ptr = InnerMalloc(size);

    // 如果分配失败,抛出内存不足的错误
    if (ptr == NULL) {
        ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("malloc fails, out of memory: size %lu", size)));
    }

    // 如果需要注册(在事务管理范围内),则进行注册
    if (toRegister) {
        /* 步骤2:注册指针 */
        Register(ptr);
    }

    // 返回分配的内存指针
    return ptr;
}

CStoreMemAlloc::AllocPointerNode 函数

  这段代码是 CStoreMemAlloc 类中的 AllocPointerNode 方法。该方法用于分配一个指针节点,具体作用和功能如下:

描述:该方法用于分配一个指针节点,该节点可能被用于管理 CStoreMemAlloc 类中的内存指针。
返回:返回分配得到的指针节点。
功能:
    如果指针节点缓存中有可用节点,从缓存中取出一个节点。
    如果指针节点缓存中没有可用节点,直接使用 malloc 分配一个新的节点。
    如果分配失败,抛出内存不足的错误。
    返回分配得到的指针节点。

  函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp

/*
 * @Description: 分配一个指针节点
 * @Return: 分配得到的指针节点
 * @See also: 无
 */
void* CStoreMemAlloc::AllocPointerNode()
{
    PointerNode* ptr = NULL;

    // 如果缓存中有可用节点,从缓存中取出
    if (m_ptrNodeCacheCount > 0) {
        ptr = m_ptrNodeCache[--m_ptrNodeCacheCount];
    } else {
        // 如果缓存中没有可用节点,直接使用malloc分配新节点
        ptr = (PointerNode*)malloc(sizeof(PointerNode));
        
        // 分配失败则抛出内存不足的错误
        if (ptr == NULL) {
            ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("malloc fails, out of memory")));
        }
    }

    // 返回分配得到的指针节点
    return ptr;
}

CStoreMemAlloc::FreePointerNode 函数

  CStoreMemAlloc 类中的 FreePointerNode 方法用于释放一个指针节点,具体作用和功能如下:

描述:该方法用于释放一个指针节点,该节点可能被用于管理 CStoreMemAlloc 类中的内存指针。
参数:ptr 为要释放的指针节点。
功能:
    如果指针节点缓存未满,将节点放入缓存中以便后续复用。
    如果指针节点缓存已满,直接使用 free 释放节点。

  函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp

/*
 * @Description: 释放一个指针节点
 * @Param[IN] ptr: 要释放的指针节点
 * @See also: 无
 */
void CStoreMemAlloc::FreePointerNode(PointerNode* ptr)
{
    // 如果指针节点缓存未满,将节点放入缓存中
    if (m_ptrNodeCacheCount < MaxPtrNodeCacheLen) {
        m_ptrNodeCache[m_ptrNodeCacheCount++] = ptr;
    } else {
        // 如果缓存已满,直接使用free释放节点
        free(ptr);
    }
}

CStoreMemAlloc::Repalloc 函数

  CStoreMemAlloc 类中的 Repalloc 方法用于重新分配内存(相当于 remalloc 操作),并根据需要注册新分配的内存取消注册旧内存。具体作用和功能如下:

描述:该方法用于重新分配内存,类似于系统中的 remalloc 操作,同时支持内存的注册和取消注册。
参数:
    old_size:旧内存的大小。
    size:新内存的大小。
    pointer:要释放的旧内存指针。
    registered:如果在调用 Palloc() 时传递 true,则为 true;如果传递 false,则为 false。
功能:
    断言,确保内存指针和大小的合法性。
    调用内部的 InnerMalloc 方法重新分配内存。
    如果分配失败,抛出内存不足的错误。
    将旧内存中的数据拷贝到新分配的内存中。
    如果在 Palloc() 中传递 true,则注册新分配的内存,取消注册旧内存。
    断言,确保计数器大于0。
    释放旧内存。
    返回新分配的内存指针。

  函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp

/*
 * @Description: 重新分配内存(remalloc)从系统中
 * @Param[IN] old_size: 旧内存大小
 * @Param[IN] size:     新内存大小
 * @Param[IN] pointer:  要释放的内存指针
 * @Param[IN] registered: 如果在 Palloc() 中传递 true,则为 true;
 *                        如果在 Palloc() 中传递 false,则为 false。
 * @See also: 无
 */
void* CStoreMemAlloc::Repalloc(void* pointer, Size size, Size old_size, bool registered)
{
    // 断言,确保内存指针和大小的合法性
    Assert(pointer != NULL);
    Assert(size > 0);

    // 调用内部的 InnerMalloc 方法重新分配内存
    void* ptr = InnerMalloc(size);

    // 如果分配失败,抛出内存不足的错误
    if (ptr == NULL) {
        ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory")));
    }

    // 将旧内存中的数据拷贝到新分配的内存中
    errno_t rc = memcpy_s(ptr, size, pointer, old_size);
    securec_check_c(rc, "\0", "\0");

    // 如果在 Palloc() 中传递 true,则注册新分配的内存,取消注册旧内存
    if (registered) {
        Register(ptr);
        Unregister(pointer);
        // 断言,确保计数器大于0
        Assert(m_count > 0);
    }

    // 释放旧内存
    free(pointer);

    // 返回新分配的内存指针
    return ptr;
}

CStoreMemAlloc::Pfree

  CStoreMemAlloc 类中的 Pfree 方法用于将内存释放到系统,同时根据需要取消注册内存指针。具体作用和功能如下:

描述:该方法用于释放内存到系统,可以选择是否取消注册内存指针。
参数:
    pointer:要释放的内存指针。
    registered:如果在调用 Palloc() 时传递 true,则为 true;如果传递 false,则为 false。
功能:
    断言,确保内存指针的合法性。
    如果在 Palloc() 中传递 true,则取消注册内存指针。
    释放内存到系统。

  函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp

/*
 * @Description: 释放内存到系统
 * @Param[IN] pointer: 要释放的内存指针
 * @Param[IN] registered: 如果在 Palloc() 中传递 true,则为 true;
 *                        如果在 Palloc() 中传递 false,则为 false。
 * @See also: 无
 */
void CStoreMemAlloc::Pfree(void* pointer, bool registered)
{
    // 断言,确保内存指针的合法性
    Assert(pointer);

    // 如果在 Palloc() 中传递 true,则取消注册内存指针
    if (registered) {
        Unregister(pointer);
    }

    // 释放内存到系统
    free(pointer);
}

CStoreMemAlloc::Register 函数

  CStoreMemAlloc 类中的 Register 方法用于注册内存指针,将其添加到内存指针管理表中。具体作用和功能如下:

描述:该方法用于将内存指针注册,将其添加到内存指针管理表中,以便在事务结束时进行统一的处理。
参数:
    pointer:要注册的内存指针。
功能:
    断言,确保 MaxPointersArryLen 是2的幂。
    计算内存指针在管理表中的索引位置。
    如果链表为空,头尾节点都指向新分配的节点。
    如果链表不为空,在链表尾部添加新节点。
    增加内存指针计数器。

  函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp

/*
 * @Description: 注册内存指针
 * @Param[IN] pointer: 要注册的内存指针
 * @See also: AllocPointerNode()
 */
void CStoreMemAlloc::Register(void* pointer)
{
    // 断言,确保 MaxPointersArryLen 是2的幂
    Assert((MaxPointersArryLen & (MaxPointersArryLen - 1)) == 0);

    // 计算索引位置
    int idx = PointerGetDatum(pointer) & (MaxPointersArryLen - 1);
    PointerNode* nodePtr = m_tab[idx].tail;

    // 如果尾节点为空,说明链表为空
    if (nodePtr == NULL) {
        // 头尾节点都指向新分配的节点
        Assert(m_tab[idx].header == NULL);
        m_tab[idx].header = m_tab[idx].tail = (PointerNode*)AllocPointerNode();
        m_tab[idx].header->ptr = pointer;
        m_tab[idx].header->next = NULL;
    } else {
        // 否则在链表尾部添加新节点
        nodePtr->next = (PointerNode*)AllocPointerNode();
        nodePtr->next->ptr = pointer;
        nodePtr->next->next = NULL;
        m_tab[idx].tail = nodePtr->next;
    }

    // 增加计数器
    ++m_count;
}

CStoreMemAlloc::Unregister 函数

  CStoreMemAlloc 类中的 Unregister 方法用于取消注册内存指针,将其从内存指针管理表中移除。具体作用和功能如下:

描述:该方法用于取消注册内存指针,将其从内存指针管理表中移除,以便在事务结束时进行统一的处理。
参数:
    pointer:要取消注册的内存指针。
功能:
    断言,确保内存指针和计数器的合法性。
    确定包含该指针的节点列表。
    在节点列表中查找指针,如果找到则移除该节点。
    如果取消注册的是尾节点,需要修改尾指针。
    重置并释放节点。
    减少计数器。
    断言,确保找到了要取消注册的指针节点。

  函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp

/*
 * @Description: 取消注册内存指针
 * @Param[IN] pointer: 要取消注册的内存指针
 * @See also: FreePointerNode()
 */
void CStoreMemAlloc::Unregister(const void* pointer)
{
    // 断言,确保内存指针和计数器的合法性
    Assert(pointer && m_count > 0);

    // Step 1: 确定包含该指针的节点列表
    Assert((MaxPointersArryLen & (MaxPointersArryLen - 1)) == 0);
    int idx = PointerGetDatum(pointer) & (MaxPointersArryLen - 1);

    // Step 2: 在节点列表中查找指针
    PointerNode* nodePtr = m_tab[idx].header;
    Assert(nodePtr);

    PointerNode* prePtr = NULL;
    while (nodePtr != NULL) {
        if (nodePtr->ptr == pointer) {
            // 如果前一个节点为空,说明要取消注册的是头节点
            if (prePtr == NULL) {
                m_tab[idx].header = nodePtr->next;

                // 如果这个列表只有一个节点
                if (m_tab[idx].tail == nodePtr) {
                    Assert(m_tab[idx].header == NULL);
                    m_tab[idx].tail = NULL;
                }
            } else {
                // 否则,修改前一个节点的 next 指针
                prePtr->next = nodePtr->next;

                // 如果取消注册的是尾节点,需要修改尾指针
                if (m_tab[idx].tail == nodePtr) {
                    m_tab[idx].tail = prePtr;
                    Assert(m_tab[idx].tail->next == NULL);
                }
            }

            // 重置并释放节点
            nodePtr->Reset();
            FreePointerNode(nodePtr);

            break;
        }
        prePtr = nodePtr;
        nodePtr = nodePtr->next;
    }

    // 减少计数器
    --m_count;
    // 断言,确保找到了要取消注册的指针节点
    Assert(nodePtr != NULL);
}

CStoreMemAlloc::Reset 函数

  CStoreMemAlloc 类中的 Reset 方法用于重置内存指针管理器的状态释放所有注册的内存。具体作用和功能如下:

描述:该方法用于在事务结束时,重置内存指针管理器的状态,释放所有注册的内存。
功能:
    如果存在注册的内存指针:
        Step 1: 释放 m_tab 中的每个列表中的内存指针。
            遍历 m_tab 数组,释放每个列表中的内存指针。
            注意,必须将列表的头尾节点重置为 NULL,因为线程可能被重用。
            断言,确保释放的内存数和计数器一致。
        Step 2: 释放缓存的节点(如果有的话)。
            释放缓存的节点,以便在下一次使用时重新分配。

  这个方法的目的是确保在事务结束时,所有注册的内存都被正确释放,以避免内存泄漏。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp

/*
 * @Description: 重置内存指针管理器状态,释放所有注册的内存
 */
void CStoreMemAlloc::Reset()
{
    uint32 i = 0;
    uint32 freeNum = 0;

    // 如果存在注册的内存指针
    if (m_count) {
        // Step 1: 释放 m_tab 中的每个列表
        for (i = 0; i < MaxPointersArryLen; ++i) {
            PointerNode* nodePtr = m_tab[i].header;
            PointerNode* tmpPtr = NULL;

            // 释放每个列表中的内存指针
            while ((nodePtr != NULL) && (nodePtr->ptr != NULL)) {
                free(nodePtr->ptr);
                tmpPtr = nodePtr->next;
                free(nodePtr);
                nodePtr = tmpPtr;
                ++freeNum;
            }

            // 注意,必须将 header 和 tail 重置为 NULL
            // 因为线程可能被重用
            m_tab[i].header = NULL;
            m_tab[i].tail = NULL;
        }

        // 断言,确保释放的内存数和计数器一致
        Assert(m_count == freeNum);
        m_count = 0;
    }

    // Step 2: 释放缓存的节点(如果有的话)
    for (i = 0; i < m_ptrNodeCacheCount; ++i) {
        free(m_ptrNodeCache[i]);
    }
    m_ptrNodeCacheCount = 0;
}

CStoreMemAlloc::Init 函数

  这段代码是 CStoreMemAlloc 类中的 Init 方法。该方法用于初始化内存指针管理器的状态。具体作用和功能如下:

描述:该方法用于在开始新的事务时,初始化内存指针管理器的状态。
功能:
    重置计数器。
    断言,确保 MaxPointersArryLen 是2的幂。
    初始化 m_tab 数组,将每个列表的头尾节点都设置为 NULL。
    初始化缓存节点计数器。

  这个方法的目的是确保在每次开始新的事务时,内存指针管理器都处于初始状态。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_mem_alloc.cpp

/*
 * @Description: 初始化内存指针管理器
 */
void CStoreMemAlloc::Init()
{
    // 重置计数器
    m_count = 0;

    // 断言,确保 MaxPointersArryLen 是2的幂
    Assert((MaxPointersArryLen & (MaxPointersArryLen - 1)) == 0);

    // 初始化 m_tab 数组
    for (uint32 i = 0; i < MaxPointersArryLen; ++i) {
        m_tab[i].header = NULL;
        m_tab[i].tail = NULL;
    }

    // 初始化缓存节点计数器
    m_ptrNodeCacheCount = 0;
}

总结

CStoreMemAlloc
作用: CStoreMemAlloc 类用于管理列存储中的内存分配和释放
功能:

  • Palloc 方法: 分配内存,并根据需要注册到管理器。
  • FreePointerNode 方法: 释放缓存的节点。
  • Repalloc 方法: 重新分配内存,同时处理注册和取消注册。
  • Pfree 方法: 释放内存,同时处理取消注册。
  • Register 方法: 注册内存指针,将其添加到管理表。
  • Unregister 方法: 取消注册内存指针,将其从管理表中移除。
  • Reset 方法: 重置管理器状态,释放所有注册的内存。
  • Init 方法: 初始化管理器状态。

你可能感兴趣的:(OpenGauss,数据库,gaussdb)