Page 509 - 《软件学报》2024年第4期
P. 509

欧阳湘臻 等: 榫卯: 一种可组合的定制化内存分配框架                                                     2087


                    在可移植性方面, 榫卯框架除系统接口层外, 整体框架实现不依赖于任何外部代码. 相比只支持在操作系统环
                 境下定制内存分配器的        HeapLayer, 榫卯框架还允许在无操作系统的裸机上定制内存分配器. 不过, 榫卯框架依赖
                 C/C++预处理器的可变参数宏机制          (C99  标准) 以及递归宏机制      (C++20  标准). 考虑到一些嵌入式     C  编译器仅支
                 持  C89  标准, 榫卯框架构建的内存分配器可能存在无法通过编译的问题. 然而, 由于榫卯框架依赖的是预处理器
                 而非编译器, 该问题可以通过混合使用编译工具链来解决: 使用支持目前最新                        C2x/C++20  标准的  GCC/Clang  编译
                 器产生预处理过的.c 文件, 然后再交给嵌入式            C  编译器编译.

                  3   榫卯内存分配框架实例

                    在实际应用的系统中, 定制专用内存分配器需要根据应用场景的内存分配统计数据进行分析, 判断系统瓶颈
                 所在, 然后修改造成瓶颈的部分. 在传统的内存分配器基础上开发定制专用内存分配器, 需要对整体代码有深入了
                 解, 因此定制的时间成本较高且代码复用性也较差. 相比之下, 使用内存分配框架定制专用内存分配器有显著的开
                 发效率优势. 由于榫卯内存分配框架的层级和策略都是可替换的, 定制专用内存分配器可以通过组合现有层级, 更
                 换或编写新的层级和策略函数来实现. 而这些重新编写的函数也能够得到复用. 本节主要以实例说明如何根据应
                 用场景和优化目标, 使用榫卯内存分配框架定制               3  种不同类型的专用内存分配器.
                    (1) 通过定制同步策略优化多核性能: tlsfcc 分配器. TLSF         是嵌入式实时操作系统中常用的内存分配算法, 它
                 具有内存碎片率低, 内存请求延迟低的优点. 然而在多核环境下, TLSF                  需要一个全局锁来保证线程安全, 因此该全
                 局锁是   TLSF  分配器的并发性能瓶颈所在. 如今多核嵌入式系统已得到广泛应用, 为了优化                        TLSF  在多核以及异
                 构多核系统上的性能和内存请求延迟, 本文使用榫卯框架实现了一种改进的                         tlsfcc 分配器. 该分配器主要将     TLSF
                 全局锁抽象为同步策略, 并将同步策略指定为公平排队的合并同步                       ccsynch [11] . 在存在多个等待的竞争线程时,
                 ccsynch  同步算法的性能更好, 同时, 其同步算法的公平性也能确保最差情况执行时间有界. 整个定制过程无需改
                 动  TLSF  层函数的实现, 只需要通过      USE_SYNC  宏指定同步策略. 此外, 由于        TLSF  使用分割合并式的内存块管
                 理方式, 如果地址空间不连续可能会造成较多的内存碎片, 因此本文使用                      brk_heap  作为  tlsfcc 的系统接口层, 该层
                 使用  brk  系统调用确保申请的地址空间是连续的. tlsfcc 内存分配器结构使用抽象模型可描述为:
                                 brk_heap::tlsf_glk(sync=ccsynch, size_handle=[0, SIZE_MAX))::malloc_socket.
                    (2) 通过核心感知的分配缓存优化性能: hslab         分配器. 传统的    slab  式分配器通常假设所有      CPU  核心是相同的,
                 因此在分配缓存层的配置上采用大小相同的设计. 如今异构多核                     CPU  已经在许多功耗敏感的系统上得到应用, 而
                 这些异构处理器核心的        cache 大小并不是相同的. 例如, 8     核异构处理器     Snapdragon 888  集成的  3  种异构核心拥有
                 不同的   L1/L2 cache 大小. 为了能够充分利用异构处理器的硬件           cache, 本文依据异构    CPU  不同核心的   cache 大小
                 对线程分配缓存进行定制, 实现了一种异构核心感知的                  hslab  内存分配器. hslab  内存分配器整体结构与       tcmalloc
                 相似, 它采用一个全局的伙伴系统管理大于             32 KB  的内存, 使用  slab 式分配算法管理小内存, 使用一个        thread_cache
                 附加层作为线程本地分配缓存. hslab         在  thread_cache 层启用了核心感知的动态缓存. 该层函数会定期查询当前线
                 程所在的   CPU  核心  ID, 并根据预先配置的核心信息动态调整缓存大小. 此外, 为了方便对比, hslab                 分配器采用与
                 tcmalloc 相同的大小阶划分策略. 相比于只使用自旋锁同步的              tcmalloc, hslab  还为各层精细化定制同步方式. 其中,
                 全局伙伴系统是集中式竞争, 采用          flat-combine [10] 同步策略能够最大化该层并发吞吐率. slab     层是每大小阶的竞争,
                 由于  hslab  的大小阶划分较多, 并发竞争较为分散, 因此使用            POSIX  自旋锁较为合适. POSIX     自旋锁的获取开销
                 较低, 同时可以确保长时间等不到锁的线程会主动出让                 CPU  给其他线程. hslab 分配器结构使用抽象模型可表示为:
                    mmap_heap(faa_heap_growth=1, threadlocal_heap_size=2MB)::buddy_glk(sync=flatcombine, sizeclass=tc,
                 size_handle=[32768, SIZE_MAX))::slab_lk(sync=posix_spin, slab_size=64KB, sizeclass=tc, size_handle=[0,
                 32768))::thread_cache(sync, sizeclass=tc, size_handle=[0, 32768), cache_size=2MB, coreaware_cachesize=
                 1)::malloc_socket.
                    (3) 通过系统设计降低内存请求延迟: wfslab         分配器. 在时间敏感的系统中, 大量的并发内存请求需要在尽可
                 能短的时间内完成. 为了尽可能降低内存请求延迟, 每个层级都应该有最小的内存请求延迟, 同时也应该将内存分
   504   505   506   507   508   509   510   511   512   513   514