8 个版本 (重大变更)
0.7.0 | 2023年11月8日 |
---|---|
0.6.0 | 2023年5月30日 |
0.5.0 | 2022年8月15日 |
0.4.2 | 2022年6月7日 |
0.1.0 | 2022年4月8日 |
#123 在 并发 中
167 每月下载量
在 4 个 Crates 中使用 (3 个直接使用)
37KB
760 行
合唱团
合唱团是一个任务编排框架。它帮助您根据任务来组织所有 CPU 工作流程。
示例
let mut choir = choir::Choir::new();
let _worker = choir.add_worker("worker");
let task1 = choir.spawn("foo").init_dummy().run();
let mut task2 = choir.spawn("bar").init(|_| { println!("bar"); });
task2.depend_on(&task1);
task2.run().join();
销售要点
是什么让合唱团如此优雅?通常当我们需要编码“等待依赖”的语义时,我们会想到某种计数器。可能是一个原子的,用于依赖项数量。当它达到零(或一)时,我们安排任务执行。在 合唱团 中,任务的内部数据(即自身运算符!)被放置在一个 Arc
中。每次我们能够从 Arc
中提取它(这意味着没有其他依赖项),我们就将它移动到调度队列中。我认为 Rust 类型系统在这里表现最好。
注意:实际上 Arc
并不完全支持这里所需的此类“线性”使用,而且无法控制最后一个引用在何处被销毁(没有 drop()
中的逻辑)。因此,我们引入了自己的 Linearc
来内部使用。
您还可以随时添加或删除工作器,以平衡系统负载,这可能同时在运行其他应用程序。
API
一般工作流程是创建任务并在它们之间设置依赖关系。有多种不同类型的任务
- 单次运行任务,使用
init()
初始化,表示为FnOnce()
- 空任务,使用
init_dummy()
初始化,没有函数体 - 多次运行任务,在范围中的每个索引上执行,表示为
Fn(SubIndex)
,并使用init_multi()
初始化 - 迭代任务,为迭代器产生的每个项目执行,表示为
Fn(T)
,并使用init_iter()
初始化。
如果没有显式调用,则在 IdleTask::drop()
上自动调用 run()
。此对象还允许在调度任务之前添加依赖项。运行中的任务也可以用作其他任务的依赖项。
请注意,所有任务都在 Fn()
执行边界处被抢占。因此,例如,一个长时间运行的多任务将被任何传入的单次运行任务抢占。
用户
Blade 在并行化资源加载方面严重依赖于 Choir。有关详细信息,请参阅 Rust Gamedev Meetup 上的 blade-asset talk。
TODO
- 检测依赖项设置不正确的情况
- 使用 Loom 进行测试:由 https://github.com/crossbeam-rs/crossbeam/pull/849 阻塞
开销
机器:2016年MBP,3.3 GHz 双核英特尔酷睿i7
- 函数
spawn()+init()
(优化):237纳秒 - "窃取"任务:61纳秒
- 空"执行":37纳秒
- 空任务"解除阻塞":78纳秒
执行100k个空任务
- 单独执行:28毫秒
- 作为多任务:6毫秒
性能分析工作流程示例
使用 Tracy:将此行添加到基准测试的开头
let _ = profiling::tracy_client::Client::start();
然后在命令提示符下运行
cargo bench --features "profiling/profile-with-tracy"
依赖项
~440KB