11 个版本

0.2.0 2021 年 12 月 11 日
0.1.9 2021 年 10 月 23 日
0.1.8 2021 年 8 月 23 日
0.1.3 2021 年 7 月 27 日

#118 in 性能分析

Download history 8/week @ 2024-04-01 300/week @ 2024-06-10

每月 300 次下载

自定义许可

130KB
1.5K SLoC

swarm_pool

Rust 的一个优化性能的对象池系统。

内容

  1. 变更日志
  2. 使用 swarm pool
  3. 基准测试
  4. 使用 Swam for ECS

变更日志

  • 版本 0.2.0:
    • 为工厂回调添加了属性。这允许在通过工厂创建时使用属性值。
  • 版本 0.1.9:
    • 实现了创建具有特定值的池对象的工厂。
  • 版本 0.1.8:
    • Swarm 添加了 kill_all() 方法。一次性删除所有已创建的对象,只能从 Swarm 本身调用,不能从 SwarmControl 调用。
    • 将基准测试和 ECS 文档添加到本 README。
  • 版本 0.1.7:
    • 移除了 Copy 依赖,PoolObjects 不再依赖于 Copy 特性的实现。
    • Spawn 实现了 CloneDefault 特性。Spawn.clone() 的行为与 Spawn.mirror() 相同。这些特性是为了使 PoolObjects 能够将 Spawn 引用对象作为字段属性。不应使用 Spawn 的 Default,它只存在于使上述操作成为可能。
  • 版本 0.1.4:
    • SwarmComtrol 添加了迭代器 find()for_while()for_each()for_all()enumerate(),这使得在 update() 循环中遍历所有创建的对象成为可能。

使用 swarm pool

该池系统管理自定义类型的对象实例,并提供更新循环以遍历它们。

为了创建一个新的群池,您需要定义您的 pool 对象swarm 属性 类型将如何看起来。您的 pool 对象 至少需要实现标准库中的 Default、Copy 和 Clone 特性。另一方面,swarm 属性 不依赖于任何特性。

基本群设置示例

extern crate swarm_pool;
use swarm_pool::Swarm;

// create an object you want to pool
#[derive(Default, Clone)]     
pub struct MyPoolObject {           
    pub name: &static str,
    pub value: usize,
}

// create properties you want to share with pooled objects
pub struct MySwarmProperties;

fn main() {
    let swarm = Swarm::<MyPoolObject, MySwarmProperties>::new(10, MySwarmProperties);
    assert!(swarm.capacity() == 10);
}

现在群已经准备好使用。首先,我们需要创建新的池实例。实际上,池中的所有对象都已经创建并等待使用。这意味着可以通过 fetch() 方法访问所有对象(从 0 到最大容量但不包括最大容量)。已生成对象和非生成对象之间的区别在于,已生成对象包含在所有 Swarm 群的迭代方法中,而非生成对象则不包括。

生成和循环

let mut swarm = Swarm::<MyPoolObject, _>::new(10, ());
let spawn1 = swarm.spawn().unwrap();
let spawn2 = swarm.spawn().unwrap();
  
assert_eq!(swarm.fetch_ref(&spawn1).value, 0);
assert_eq!(swarm.fetch_ref(&spawn2).value, 0);

swarm.for_each(|obj| {
    obj.value = 42;
});

assert_eq!(swarm.fetch_ref(&spawn1).value, 42);
assert_eq!(swarm.fetch_ref(&spawn2).value, 42);

这个库真正的强大之处不仅在于循环几个对象实例,还在于控制和交叉引用它们。有两个强大的方法可以用来做到这一点:Swarm.for_all()Swarm.update()。两者都有其优缺点,for_all 循环速度快(等同于标准的 vec 循环),但不能生成或销毁池对象,而 update 易于使用,提供完全控制,但速度较慢(速度低于一半)。

使用 for_all & update 进行交叉引用

// change properties to contain references to our spawned pool objects
pub struct MySwarmProperties { 
    john: Option<Spawn>, 
    cristy: Option<Spawn>,
}

let properties = MySwarmProperties { john: None, cristy: None };

let mut swarm = Swarm::<MyPoolObject, MySwarmProperties>::new(10, properties);
let s_john = swarm.spawn().unwrap();
let s_cristy = swarm.spawn().unwrap();

swarm.properties.john = Some(s_john.mirror());
swarm.properties.cristy = Some(s_cristy.mirror());

swarm.fetch(&s_john).name = "John";
swarm.fetch(&s_cristy).name = "Cristy";

// using the for_all methode
swarm.for_all(|target, list, props| {

    // john tells critsy to have a value of 2
    if list[*target].name == "John" { 
        if let Some(cristy) = &props.cristy {
            list[cristy.pos()].value = 2; 
        }
    }
    // cristy tells john to have a value of 1
    if list[*target].name == "Cristy" { 
        if let Some(john) = &props.john {
            list[john.pos()].value = 1; 
        }
    }
});

assert_eq!(swarm.fetch_ref(&s_john).value, 1);
assert_eq!(swarm.fetch_ref(&s_cristy).value, 2);

// using the update methode
swarm.update(|ctl| {
    let name = ctl.target().name;
    let cristy = ctl.properties.cristy.as_ref().unwrap().mirror();
    let john = ctl.properties.john.as_ref().unwrap().mirror();

    // john tells critsy to have a value of 4
    if name == "John" { 
        ctl.fetch(&cristy).value = 4; 
    }
    // cristy tells john to have a value of 5
    if name == "Cristy" { 
        ctl.fetch(&john).value = 5; 
    }
});

assert_eq!(swarm.fetch_ref(&s_john).value, 5);
assert_eq!(swarm.fetch_ref(&s_cristy).value, 4);

Swarm 和 SwarmControl 类型还包括许多其他功能。上述示例或其他库提供的功能的文档更为深入,应该阅读,因为编写它们是项大量工作 ;)

基准测试

基准性能测试使用标准 Vector 作为基线。该标准 Vector 由相同的对象类型填充,并使用标准循环遍历元素。每次调用对象时,都会将其值属性增加 1。

0.1.8 - 我 i7 笔记本上的基准测试结果

1. Standard Vec bench for 1 object(s).. 814M calls/s @ 814M upd/s
2. Swarm.for_each() bench with 1 object(s).. 825M calls/s(101%) @ 825M upd/s
3. Swarm.for_all() bench with 1 object(s).. 677M calls/s(83%) @ 677M upd/s
4. Swarm.update() bench with 1 object(s).. 530M calls/s(65%) @ 530M upd/s
5. Standard Vec bench for 10 object(s).. 1568M calls/s @ 156.8M upd/s
6. Swarm.for_each() bench with 10 object(s).. 1596M calls/s(102%) @ 159.6M upd/s
7. Swarm.for_all() bench with 10 object(s).. 1049M calls/s(67%) @ 104.9M upd/s
8. Swarm.update() bench with 10 object(s).. 695M calls/s(44%) @ 69.5M upd/s
9. Standard Vec bench for 100 object(s).. 1550M calls/s @ 15.5M upd/s
10. Swarm.for_each() bench with 100 object(s).. 1419M calls/s(92%) @ 14.19M upd/s
11. Swarm.for_all() bench with 100 object(s).. 1051M calls/s(68%) @ 10.51M upd/s
12. Swarm.update() bench with 100 object(s).. 676M calls/s(44%) @ 6.76M upd/s
13. Standard Vec bench for 1000 object(s).. 1234M calls/s @ 1.234M upd/s
14. Swarm.for_each() bench with 1000 object(s).. 1231M calls/s(100%) @ 1.231M upd/s
15. Swarm.for_all() bench with 1000 object(s).. 1058M calls/s(86%) @ 1.058M upd/s
16. Swarm.update() bench with 1000 object(s).. 678M calls/s(55%) @ 0.678M upd/s

# RESULTS TOTAL
* Plain vec results:
  - average of '1292M' calls/s
  - lowest of '814M' calls/s (bench #1)
  - highest of '1568M' calls/s (becnh #5)
* swarm.for_each() results:
  - average of '1268M' calls/s
  - average speed was '98.168%' of plain vector speed
  - lowest of '825M' calls/s (becnh #2)
  - highest of '1596M' calls/s (becnh #6)
* swarm.for_all() results:
  - average of '959M' calls/s
  - average speed was '74.242%' of plain vector speed
  - lowest of '677M' calls/s (becnh #3)
  - highest of '1058M' calls/s (becnh #15)
* swarm.update() results:
  - average of '645M' calls/s
  - average speed was '49.916%' of plain vector speed
  - lowest of '530M' calls/s (becnh #4)
  - highest of '695M' calls/s (becnh #8)

结果因测试运行而异,但平均速度百分比似乎相对稳定。换句话说,基准测试结果可以大致了解使用 swarm_pool 库时的性能范围。

使用 Swam for ECS

对于那些想要深入了解 ECS 引擎开发的人来说,问题是:“能否将 swarm_pool 转换为 ECS 引擎?”,答案是:“是的,可以”。

我已经尝试了不同的方法来使用 Swarm 池创建 ECS 引擎。结果表明,在使用 Swarm 池时,使用 Option<> 方法似乎比经典的 bitflag 策略更快。换句话说,将您的组件包装在标准的 Option 中,并在遍历群中的对象时对每个对象进行测试,以确定该对象是否具有 Some(component)。

ECS 示例

#[derive(Default, Clone, Debug, PartialEq)] struct Image(bool);
#[derive(Default, Clone, Debug, PartialEq)] struct Position(f32, f32);
#[derive(Default, Clone, Debug, PartialEq)] struct Speed(f32);

#[derive(Default, Clone, Debug)]
pub struct Entity {
    name: &'static str,
    
    image_component: Option<Image>,
    position_component: Option<Position>,
    speed_component: Option<Speed>,
}

fn main() {
    let mut swarm = Swarm::<Entity, _>::new(10, ());
    
    let building = swarm.spawn().unwrap();
    {
        let entity = swarm.fetch(&building);
            entity.name = "Sixteenth Chapel";
            entity.image_component = Some(Image(false));
            entity.position_component = Some(Position(3.0, 5.0));
            entity.speed_component = None;
    }

    let truck = swarm.spawn().unwrap();
    {
        let entity = swarm.fetch(&truck);
            entity.name = "Cargo truck";
            entity.image_component = Some(Image(false));
            entity.position_component = Some(Position(8.0, 6.0));
            entity.speed_component = Some(Speed(1.0));
    }

    assert_eq!(swarm.fetch_ref(&building).position_component, Some(Position(3.0, 5.0)));
    assert_eq!(swarm.fetch_ref(&building).image_component, Some(Image(false)));

    assert_eq!(swarm.fetch_ref(&truck).position_component, Some(Position(8.0, 6.0)));
    assert_eq!(swarm.fetch_ref(&truck).image_component, Some(Image(false)));

    // # MOVE SYSTEM
    swarm.for_all(|tar, pool, props|{
        if let ( 
            Some(position_component), 
            Some(speed_component)
        ) = (
            &mut pool[*tar].position_component, 
            &mut pool[*tar].speed_component 
        ){
            position_component.0 += speed_component.0;
        }
    });

    // Only the truck moves from position 8.0 to 9.0, the building does not 
    // move because it does not have a Speed component which is required 
    // by the move system.
    assert_eq!(swarm.fetch_ref(&building).position_component, Some(Position(3.0, 5.0)));
    assert_eq!(swarm.fetch_ref(&truck).position_component, Some(Position(9.0, 6.0)));

    // # DRAW SYSTEM
    swarm.for_all(|tar, pool, props|{
        if let ( 
            Some(position_component), 
            Some(image_component)
        ) = (
            &mut pool[*tar].position_component, 
            &mut pool[*tar].image_component 
        ){
            image_component.0 = true;
        }
    });

    // Both building and truck should be updated because both have a Position 
    // and an Image component, which are required by the draw system.
    assert_eq!(swarm.fetch_ref(&building).image_component, Some(Image(true)));
    assert_eq!(swarm.fetch_ref(&truck).image_component, Some(Image(true)));
}

您可能已经注意到使用了 for_all() 而不是 update()。Update 较慢,因此,在构建自己的引擎时,建议只使用 for_all。如果您需要在更新期间进行生成或销毁,或者需要在遍历所有对象的同时遍历所有对象,则建议仅使用 update。

无运行时依赖