< 返回版块

js2xxx 发表于 2024-03-10 01:20

Tags:内存管理,无锁,并发,no_std,嵌入式

Ferroc v1.0.0-pre.1 现已在crates.io上发布。期待大家的使用和反馈来持续改进,最终完备地发布第一个正式版。

主要的功能和实现改动

  • 减小了allocate_*系列函数接口的Result大小,与之相应的具体Error类型将不能够再直接获得(变为core::alloc::AllocError),转而可以通过开启新增的error-log功能来记录错误日志;
  • TLS变量转而使用专门的缓存结构体,可以在新旧线程交替时复用,减小TLS对应内存的重复分配;
  • 各种代码生成上的优化,比如尾调用、冷热路径分离等等;
  • 暂时移除了数据统计和大于10MB对齐的内存块分配的支持,会在未来的版本中重新加入;
  • 增加了C/C++的使用支持。

TLS缓存介绍

ferroc::heap::ThreadLocal是高度灵活的TLS缓存,用户可以选择手动或自动管理其中缓存的生命周期。其内部使用了指数增长的内存分配策略,并直接通过BaseAlloc分配空间给线程本地的ContextHeap

自动管理缓存生命周期

通过ferroc::heap::ThreadData可以直接从ThreadLocal获得一块自动管理生命周期的线程本地缓存。该缓存可以被分配在栈上(以作为临时的上下文),或者存储在实际的TLS变量中。

栈上使用示例:

#![feature(allocator_api)]

use core::pin::pin;
use ferroc::{
    arena::Arenas,
    base::Mmap,
    heap::{ThreadLocal, ThreadData}
};

let arenas = Arenas::new(Mmap);
let thread_local = pin!(ThreadLocal::new(&arenas));
let thread_data = ThreadData::new(thread_local.as_ref());

let mut vec = Vec::with_capacity_in(5, &thread_data);
vec.extend([1, 2, 3, 4, 5]);
assert_eq!(vec.iter().sum::<i32>(), 15);

存储于TLS的使用示例:

static ARENAS: Arenas<Mmap> = Arenas::new(Mmap);
static THREAD_LOCAL: ThreadLocal<Mmap> = ThreadLocal::new(&ARENAS);

thread_local! {
    static THREAD_DATA: ThreadData<'static, 'static, Mmap>
       = ThreadData::new(Pin::static_ref(&THREAD_LOCAL));
}

THREAD_DATA.with(|td| {
    let mut vec = Vec::with_capacity_in(5, td);
    vec.extend([1, 2, 3, 4, 5]);
    assert_eq!(vec.iter().sum::<i32>(), 15);
})

然而,由于Rust本身TLS变量的存储和销毁并用到了堆内存,因此直接将THREAD_DATA包装成单元结构体来做全局分配器将会造成递归爆栈。这时候,自动管理生命周期的方式已经不再有效。

手动管理生命周期则依靠ThreadLocal直接提供的成员方法assigngetput。其中后二者为unsafe。以下给出两种手动管理的示例,可以根据需要选用(示例中register_thread_dtor是手动注册TLS析构器的函数,提供者有pthread,可查阅相关文档):

  1. 存储线程唯一的缓存ID:
static ARENAS: Arenas<Mmap> = Arenas::new(Mmap);
static THREAD_LOCAL: ThreadLocal<Mmap> = ThreadLocal::new(&ARENAS);

#[thread_local]
static ID: Cell<Option<NonZeroU64>> = Cell::new(None);

fn with_heap<T>(f: impl FnOnce(&Heap) -> T) -> T {
    f(Pin::get_ref(match ID.get() {
        Some(id) => unsafe { Pin::static_ref(&THREAD_LOCALS).get(id) },
        None => {
            let (heap, id) = Pin::static_ref(&THREAD_LOCALS).assign();
            ID.set(id);
            unsafe { register_thread_dtor(id, || Pin::static_ref(&THREAD_LOCALS).put(id)) };
            heap
        }
    }))
}
  1. 直接存储线程本地堆的引用:
#[thread_local]
static HEAP: Cell<Pin<&Heap>> = Cell::new(THREAD_LOCALS.empty_heap());

fn with_heap<T>(f: impl FnOnce(&Heap) -> T) -> T {
    f(&HEAP.get())
}

fn with_heap_fallback<T, F>(f: F) -> T
where
    F: for<'a> FnOnce(&'a Heap<'static, 'static>, fn() -> &'a Heap<'static, 'static>) -> T,
{
    fn fallback<'a>() -> &'a Heap<'static, 'static> {
        let (heap, id) = Pin::static_ref(&THREAD_LOCALS).assign();
        HEAP.set(heap);
        unsafe { register_thread_dtor(id, || Pin::static_ref(&THREAD_LOCALS).put(id)) };
        Pin::get_ref(heap)
    }
    f(Pin::get_ref(HEAP.get()), fallback)
}

其中,前者的优势是简单;而后者的优势则是优化代码路径——deallocate不需要当前的Heap初始化,而allocate可能才需要。后者的fallback可以作为一个参数传入新加入的Heap::allocate_with成员函数用来提供当前堆未初始化时的替代,用来优化代码路径。

最后,当然还是直接使用ferroc::config!,或者直接使用默认的ferroc::Ferroc最为方便:

// 内部默认使用了`ferroc::heap::ThreadLocal`。
ferroc::config!(pub Custom => ferroc::base::Mmap; pthread);

C/C++用户使用示例

  1. 下载Rust工具链:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs |\
    sh -- -y --toolchain nightly --profile minimal -c rust-src
  1. cmake 然后 make来自动化生成:
mkdir target && cd target # 注意不要改成build等其他目录
cmake .. && make

生成的文件有(相对路径):target/libferroc.sotarget/libferroc.aferroc.h

  1. 安装动态库、静态库和头文件到本地:
sudo make install

CMake的通用配置选项比如-DCMAKE_BUILD_TYPE--install-prefix等均支持。 并且有其他的自定义选项(cmake -D):

  • FE_TRACK_VALGRIND: 使用valgrind追踪内存UB;
  • FE_FINER_GRAINED: 提升内存效率,但是可能会导致使用SIMD的程序发生内存对齐错误;
  • FE_PGO_GATHER: 启用 PGO (Profile-Guided Optimization) 的数据收集(至项目目录下target/pgo)。
  • FE_PGO_USE: 使用PGO的数据优化生成目标(需要与Rust工具链版本匹配的llvm-profdata,可在第一步安装Rust工具链时在命令尾部增加llvm-tools来顺带安装)。

基准测试结果更新

时间消耗:

Time #1

Time #2

内存消耗:

Memory #1

Memory #2

欢迎大家试用并提供反馈!


Ext Link: https://github.com/js2xxx/ferroc/tree/v1.0.0-pre.1

评论区

写评论

还没有评论

1 共 0 条评论, 1 页