为eRDMA注册超大内存

简介: 本文介绍如何在eRDMA环境下注册大量内存。

弹性RDMA是阿里云8代ECS/EGS的标配RDMA加速能力,可以为用户提供高性能的RDMA网络的加速能力。在实际使用中,部分应用存在注册大量内存(如:将实例的全部内存都注册成RDMA的MR(Memory Region))的需求。但是由于设备规格约束,导致无法成功注册(提示-EIO,Input/Output Error)。本文描述一种可行的办法,以便实现大量内存的注册。

eRDMA支持的单个MR大小可以通过ibv_devinfo -v | grep mr_size查询得到,如下图所示:
image.png

上图表明单个MR的最大大小为64GB。由于硬件存在MR页表总数的规格约束,默认PageSize(4096)的情况下,整个实例可以注册的总的MR大小是有上限的。如果内存是页对齐,同时以4096B的PageSize注册,可以注册的总内存量是整个内存的25%左右(非内存型实例的经验值)。

对于绝大多数应用,实际是不需要注册大量内存的。但是对于某些特定应用的大内存注册场景,我们需要突破如上限制。eRDMA限制的是注册内存的总页表数,Linux提供了大页机制,通过大页分配的内存可以做到2MB/1GB的物理连续。将通过大页分配的内存注册成MR,MR就可以使用更大的Page Size来下发页表,进而减少消耗的页表数量,达到成功注册大内存的目的。

大页的配置方法有很多,本文采用echo <HugePageCnt> | sudo tee /proc/sys/vm/nr_hugepages的方式让操作系统预留一定数目的大页。其他方案不在本文讨论范围,可以自行查阅相关材料。

我们使用ecs.r8a.32xlarge 的实例规格来进行实验,操作系统搭配ubuntu 22.04。该实例提供了约1TB的内存。常规的注册方案,仅能注册不到128G。我们按照以下步骤进行操作。

注册超过4G内存时,由于内核代码存在缺陷,有概率会导致物理连续大页所计算出的page size过小,详情参考:https://lore.kernel.org/all/20250217141623.12428-1-mrgolin@amazon.com/。eRDMA驱动的新版本(驱动程序包大包版本号1.5.2)进行了规避,请先手动更新至该版本的驱动。可以使用如下命令进行更新:

sudo wget http://mirrors.cloud.aliyuncs.com/erdma/env_setup.sh
bash env_setup.sh --url http://mirrors.cloud.aliyuncs.com/erdma/erdma_installer-1.5.2.tar.gz
  1. 配置OS预留足够数量的大页。在实验中,我们预留896G的2M大页,总共页数是458752。因此输入shell命令:
    echo 458752 | sudo tee /proc/sys/vm/nr_hugepages,并等待执行完毕。
  2. 检查当前系统大页状态:cat /proc/meminfo | grep HugeHugePages_Total以及HugePages_Free应当符合预期。
    image.png
  3. 使用我们的demo程序进行验证:
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <infiniband/verbs.h>

#define TOTAL_SIZE (896UL * 1024 * 1024 * 1024)
#define REG_SIZE (64ULL * 1024 * 1024 * 1024)
#define NUM_BUFS 14

int main(void)
{
   
    struct ibv_device **dev_list;
    struct ibv_mr *mrs[NUM_BUFS];
    struct ibv_context *ib_ctx;
    struct ibv_device *ib_dev;
    struct ibv_pd *pd;
    void *ptr;
    int i;

    // 1. 获取 RDMA 设备列表
    dev_list = ibv_get_device_list(NULL);
    if (!dev_list) {
   
        fprintf(stderr, "Failed to get IB devices list\n");
        return -1;
    }

    // 2. 打开 RDMA 设备
    ib_dev = dev_list[0]; // 选择第一个设备
    ib_ctx = ibv_open_device(ib_dev);
    if (!ib_ctx) {
   
        fprintf(stderr, "Failed to open device %s\n",
            ibv_get_device_name(ib_dev));
        ibv_free_device_list(dev_list);
        return -1;
    }

    // 3. 创建保护域(Protection Domain)
    pd = ibv_alloc_pd(ib_ctx);
    if (!pd) {
   
        fprintf(stderr, "Failed to allocate protection domain\n");
        ibv_close_device(ib_ctx);
        ibv_free_device_list(dev_list);
        return -1;
    }

    ptr = mmap(NULL, TOTAL_SIZE, PROT_READ | PROT_WRITE,
           MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
    if (ptr == MAP_FAILED) {
   
        perror("mmap");
        exit(1);
    }

    for (i = 0; i < NUM_BUFS; i++) {
   
        mrs[i] = ibv_reg_mr(pd, ptr + i * REG_SIZE, REG_SIZE,
                    IBV_ACCESS_LOCAL_WRITE |
                        IBV_ACCESS_REMOTE_READ |
                        IBV_ACCESS_REMOTE_WRITE);
        if (!mrs[i]) {
   
            fprintf(stderr, "Failed to register memory: %s\n",
                strerror(errno));
            exit(1);
        }
    }

    printf("All memory registered!\n");

    for (i = 0; i < NUM_BUFS; i++)
        ibv_dereg_mr(mrs[i]);

    munmap(ptr, TOTAL_SIZE);
    ibv_dealloc_pd(pd);
    ibv_close_device(ib_ctx);
    ibv_free_device_list(dev_list);

    return 0;
}

将上述代码保存为test.c,使用gcc -o rdma_example test.c -libverbs -lrdmacm进行编译。然后执行./rdma_example,如果正常执行完毕,则可以看到All memory registered! 的输出:
image.png

其他一些需要注意的事项:

  1. 对于较新的内核的操作系统(如alinux3、ubuntu 22.04+),都是可以让eRDMA使用更大PageSize来注册内存。较旧的内核缺少对应的API,因此只能使用默认的PageSize(4096)进行内存注册。
  2. 可以在测试过程中开启eRDMA内核驱动日志,显示注册所使用的PageSize。输入命令echo 'file erdma* +p'>/sys/kernel/debug/dynamic_debug/control来开启日志(关闭时,使用echo 'file erdma* -p'>/sys/kernel/debug/dynamic_debug/control),然后使用dmesg观察输出当中的page_size打印来确认:
    image.png
相关文章
|
存储 Java 数据安全/隐私保护
java基础学习_IO流04_用户登录注册案例(IO版)、数据操作流(操作基本数据类型的流)、内存操作流、打印流、标准输入输出流、随机访问流、合并流、序列化流(对象操作流)、Properties属性集合类、NIO(新IO)_day22总结
java基础学习_IO流04_用户登录注册案例(IO版)、数据操作流(操作基本数据类型的流)、内存操作流、打印流、标准输入输出流、随机访问流、合并流、序列化流(对象操作流)、Properties属性集合类、NIO(新IO)_day22总结 ===========================...
1829 0
|
存储 Java API
Java基础-22总结登录注册IO版,数据操作流,内存操作流,打印流,标准输入输出流,转换流,随机访问流,合并流,序列化流,Properties
你需要的是什么,直接评论留言。 获取更多资源加微信公众号“Java帮帮” (是公众号,不是微信好友哦) 还有“Java帮帮”今日头条号,技术文章与新闻,每日更新,欢迎阅读 学习交流请加Java帮帮交流QQ群553841695 分享是一种美德,分享更快乐! 1:登录注册IO版本案例(掌握) 要求,对着写一遍。 cn.i
2105 0
Dev 显式注册的EvenHandler要显式注销以避免内存泄漏
        将一个成员方法注册到某个对象的事件会造成后者持有前者的引用。在事件注销之前,前者不会被垃圾回收。 private void Form1_Load() { …… //注册事件 CommandRemotingContext.CmdChanged += new ReciverCmdStateChangedEventHandler(this.CommandRemotingContex
1180 0
Dev 显式注册的EvenHandler要显式注销以避免内存泄漏
        将一个成员方法注册到某个对象的事件会造成后者持有前者的引用。在事件注销之前,前者不会被垃圾回收。 private void Form1_Load() { …… //注册事件 CommandRemotingContext.CmdChanged += new ReciverCmdStateChangedEventHandler(this.CommandRemotingCo
901 0
|
10月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
3111 0
|
10月前
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
1016 1
|
10月前
|
存储 弹性计算 固态存储
阿里云服务器配置费用整理,支持一万人CPU内存、公网带宽和存储IO性能全解析
要支撑1万人在线流量,需选择阿里云企业级ECS服务器,如通用型g系列、高主频型hf系列或通用算力型u1实例,配置如16核64G及以上,搭配高带宽与SSD/ESSD云盘,费用约数千元每月。
1294 0
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
1084 0
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。