6. Mempool Library
Memory pool是一个分配对象大小固定的内存分配器。 在DPDK中,它通过名称唯一标识,并使用mempool句柄存储空闲对象空间。 默认的mempool句柄是基于ring的。 mempool 提供了一些其他可选服务,例如核本地缓存和内存对齐辅助器,内存对齐辅助器确保对象被均匀地填充到所有DRAM或DDR3通道上(对象被均匀的放在通道上,会提高存取速度)。
Mbuf 库基于memory库实现的。
6.1. Cookies(mempool添加的特定成员以保存内存分配信息)
在调试模式时(使能CONFIG_RTE_LIBRTE_MEMPOOL_DEBUG ), 分配块的前后将添加cookies。 于是分配对象中将包含覆盖保护字段,以帮助调试缓冲区溢出。
6.2. Stats
在调试模式时(使能CONFIG_RTE_LIBRTE_MEMPOOL_DEBUG ),分配统计信息保存在mempool结构中,每个核有自己的统计信息,以避免同步问题。
6.3. 内存对齐
根据硬件的存储结构,在分配对象之间补零可以大大的提高性能。这样做的目的是确保每个分配对象的开始地址和内存中的channel和rank首地址对齐,以便均衡的加载到所有channel(对齐的目的是减少未命中的发生概率,channel和rank是内存的组成结构,详情需要了解内存的构造,不然这节有点难懂)。
内存对齐的包缓存在三层转发和流分类时特别有用。仅仅访问头64自己的话,可以通过将对象的首地址均衡的分摊存储到不同的channels中来提高访存性能。
DIMM中的ranks是一个个独立的装置存在于DRAMS中,ranks可以以DIMM的满带宽速度访问,但ranks不能同时被访问,因为它们共享相同的数据通路。DIMM中的DRAM芯片的物理布局本身并不一定与ranks的数量有关。
在运行dpdk应用时,可以通过EAL命令行配置内存的channels和ranks。
note:命令配置的参数必须和指定的cpu的内存channels数匹配。
图6.1和6.2展示了不同DIMM架构中对齐的例子
在这个例子中,假定数据包的大小是64字节,占用16个块。
Intel ®5520 芯片集有3个channels,在大多数情况,在分配对象之间不用填充(除了对象的大小是nx3x64字节的块)
当创建一个新内存池时,用户可以指定是否使用这个特性。
注:上面提到的“内存对齐“不同于仅仅将数据放在一个合适的内存地址上,而是根据内存通道(channel)和rank的结构,将数据包拆分成块,均匀分布到不同的channel的rank上,,当存取一个数据报文时可以同时访问多个channel,获得性能提升。
6.4. 本地缓存
多核cpu访问同一个内存池的ring中的空闲缓冲区时,代价会很大,因为每次存取都要进行compare-and-set (CAS)操作。为了避免对于一个内存池ring的太多存取请求,内存池分配器支持单核本地缓存,支持批量请求操作机制(在对ring的实际结构操作中较少使用有锁的缓存)。这样,每个核都可以满速访问自己的缓存中(带有锁)的空闲对象,只有当缓存填充时,核才需要将一些空闲对象拖回内存池中,或者在缓存为空时获得更多对象。
虽然这可能意味着在某些核的缓存中许多分配对象可能会闲置,但是核通过访问没有锁的本地缓存来得到性能的提升。
本地缓存由一个小的、核本地指针表和长度(用作堆栈)组成。这个内部缓存可以在创建内存池时启用或禁用。
本地缓存的最大值在编译阶段就静态设置了(CONFIG_RTE_MEMPOOL_CACHE_MAX_SIZE)。
另外内部的默认是核本地缓存,应用程序可以通过调用以下函数创建和管理外部缓存:
rte_mempool_cache_create()
rte_mempool_cache_free()
rte_mempool_cache_flush()
通过以下函数可以显示的操作用户的缓存:
rte_mempool_generic_put()
rte_mempool_generic_get()
调用以下函数返回默认的内部缓存,如果存在:
rte_mempool_default_cache()
与默认缓存相比,用户拥有的缓存也可以被非EAL线程使用。
6.5. 内存池句柄
mempool允许外部内存子系统,例如外部硬件内存管理系统和基于软件的内存分配程序,用于DPDK。
mempool句柄包括两个方面:
*为新的mempool操作(ops)添加代码。这是通过添加一个新的mempool操作码来实现的,并使用MEMPOOL_REGISTER_OPS宏来设置
*使用新的API调用rte_mempool_create_empty()和rte_mempool_set_ops_byname()来创建一个新的mempool并指定要使用的操作。
可以在同一个应用程序中使用几个不同的mempool 句柄。可以使用rte_mempool_create_empty()函数创建新的mempool,然后使用rte_mempool_set_ops_byname()将mempool指向相关的mempool句柄回调结构。
旧的应用程序可能会继续使用旧的rte_mempool_create()API调用,默认情况下使用一个基于mempool 句柄的ring。需要对这些应用程序进行修改,以使用新的mempool处句柄。
对于使用rte_pktmbuf_create()的应用程序,有一个配置设置(RTE_MBUF_DEFAULT_MEMPOOL_OPS),它允许应用程序使用另一个mempool句柄。
6.6. 用例
所有需要高性能的内存分配都应该使用基于内存池的分配器。以下是一些例子:
*Mbuf Library
*Environment Abstraction Layer(环境抽象层),日志服务
*任何需要在数据平面中分配固定大小,不断使用的内存对象的应用程序