#memory-safety #heap-memory #object-pool #memory #lock-free #memory-pool #safety

syncpool

一个线程友好的库,用于回收和重用重和堆对象,以减少分配和内存压力

7个版本

0.1.6 2021年2月2日
0.1.5 2019年9月14日
0.1.2 2019年8月29日

#216 in 内存管理

Download history 15/week @ 2024-03-11 10/week @ 2024-03-18 6/week @ 2024-03-25 31/week @ 2024-04-01 8/week @ 2024-04-08 5/week @ 2024-04-15 13/week @ 2024-04-22 9/week @ 2024-04-29 6/week @ 2024-05-06 17/week @ 2024-05-13 77/week @ 2024-05-20 24/week @ 2024-05-27 22/week @ 2024-06-03 37/week @ 2024-06-10 11/week @ 2024-06-17 65/week @ 2024-06-24

137 每月下载量
3 crates 中使用

MIT 许可证

71KB
1K SLoC

SyncPool

SyncPool on crates.io SyncPool on docs.rs

一个简单且线程安全的对象池,用于在堆中重用重对象。

本crate的用途

受Go的sync.Pool模块的启发,此crate提供了一个多线程友好的库来回收和重用重和基于堆的对象,从而减少整体分配和内存压力,从而提高性能。

本crate不适用于

在多线程项目中设计时,并没有银弹,程序员需要根据具体情况判断。

正如我们运行的一些(数百个)基准测试所示,在以下情况下,该库可以可靠地击败分配器

  • 对象足够大,使其在堆中生存是有意义的。
  • 在将元素放回池中之前清理写入数据所需的操作简单且运行速度快。
  • 对程序运行期间同时检查出的最大元素数量的估计足够好,即并行性是确定的;否则,当池饥饿(即它没有足够的元素留下以提供)时,性能将受到影响,因为我们需要创建(并在堆中分配)新元素。

如果你的struct足够小,可以在栈上生存而不崩溃,或者它不在最热的代码路径中,你很可能不需要这个库来为你工作,现在的分配器在栈上工作得相当神奇。

示例

extern crate syncpool;

use std::collections::HashMap;
use std::sync::mpsc::{self, SyncSender};
use std::thread;
use std::time::Duration;
use syncpool::prelude::*;

/// For simplicity and illustration, here we use the most simple but unsafe way to 
/// define the shared pool: make it static mut. Other safer implementation exists 
/// but may require some detour depending on the business logic and project structure.
static mut POOL: Option<SyncPool<ComplexStruct>> = None;

/// Number of producers that runs in this test
const COUNT: usize = 128;

/// The complex data struct for illustration. Usually such a heavy element could also
/// contain other nested struct, and should almost always be placed in the heap. If 
/// your struct is *not* heavy enough to be living in the heap, you most likely won't
/// need this library -- the allocator will work better on the stack. The only requirement
/// for the struct is that it has to implement the `Default` trait, which can be derived
/// in most cases, or implemented easily.
#[derive(Default, Debug)]
struct ComplexStruct {
    id: usize,
    name: String,
    body: Vec<String>,
    flags: Vec<usize>,
    children: Vec<usize>,
    index: HashMap<usize, String>,
    rev_index: HashMap<String, usize>,
}

fn main() {
    // Must initialize the pool first
    unsafe { POOL.replace(SyncPool::with_size(COUNT / 2)); }

    // use the channel that create a concurrent pipeline.
    let (tx, rx) = mpsc::sync_channel(64);

    // data producer loop
    thread::spawn(move || {
        let mut producer = unsafe { POOL.as_mut().unwrap() };

        for i in 0..COUNT {
            // take a pre-init element from the pool, we won't allocate in this 
            // call since the boxed element is already placed in the heap, and 
            // here we only reuse the one. 
            let mut content: Box<ComplexStruct> = producer.get();
            content.id = i;
        
            // simulating busy/heavy calculations we're doing in this time period, 
            // usually involving the `content` object.
            thread::sleep(Duration::from_nanos(32));
        
            // done with the stuff, send the result out.
            tx.send(content).unwrap_or_default();
        }
    });

    // data consumer logic
    let handler = thread::spawn(move || {
        let mut consumer = unsafe { POOL.as_mut().unwrap() };
    
        // `content` has the type `Box<ComplexStruct>`
        for content in rx {
            println!("Receiving struct with id: {}", content.id);
            consumer.put(content);
        }
    });

    // wait for the receiver to finish and print the result.
    handler.join().unwrap_or_default();

    println!("All done...");

}

你可以在示例文件夹中找到更复杂(即实用)的使用案例。

无运行时依赖