6 个版本 (breaking)

0.6.0 2021年2月27日
0.5.0 2020年11月28日
0.4.0 2020年9月26日
0.3.0 2020年9月26日
0.1.0 2020年2月18日

数据结构 中排名第 832

每月下载量 39

MIT 许可证

110KB
2.5K SLoC

Anchors: Self-Adjusting Computitons in Rust
Crates.io Package Docs

功能

  • 混合图允许同时进行 Adapton 风格和 Incremental 风格的推送更新。有关内部机制的更多信息,您可以查看相关的博客文章
  • 在图中克隆值几乎是可选的。 mapthen 闭包接收不可变引用,并返回拥有的值。或者,refmap 闭包接收不可变引用,并返回不可变引用。
  • 仍在开发中,但应该功能正常(lol)并且速度尚可。但是,仍预期未来几年将会有重大的 API 变更。

示例

// example
use crate::{singlethread::Engine, AnchorExt, Var};
let mut engine = Engine::new();

// create a couple `Var`s
let (my_name, my_name_updater) = Var::new("Bob".to_string());
let (my_unread, my_unread_updater) = Var::new(999usize);

// `my_name` is a `Var`, our first type of `Anchor`. we can pull an `Anchor`'s value out with our `engine`:
assert_eq!(&engine.get(&my_name), "Bob");
assert_eq!(engine.get(&my_unread), 999);

// we can create a new `Anchor` from another one using `map`. The function won't actually run until absolutely necessary.
// also feel free to clone an `Anchor` — the clones will all refer to the same inner state
let my_greeting = my_name.clone().map(|name| {
    println!("calculating name!");
    format!("Hello, {}!", name)
});
assert_eq!(engine.get(&my_greeting), "Hello, Bob!"); // prints "calculating name!"

// we can update a `Var` with its updater. values are cached unless one of its dependencies changes
assert_eq!(engine.get(&my_greeting), "Hello, Bob!"); // doesn't print anything
my_name_updater.set("Robo".to_string());
assert_eq!(engine.get(&my_greeting), "Hello, Robo!"); // prints "calculating name!"

// a `map` can take values from multiple `Anchor`s. just use tuples:
let header = (&my_greeting, &my_unread)
    .map(|greeting, unread| format!("{} You have {} new messages.", greeting, unread));
assert_eq!(
    engine.get(&header),
    "Hello, Robo! You have 999 new messages."
);

// just like a future, you can dynamically decide which `Anchor` to use with `then`:
let (insulting_name, _) = Var::new("Lazybum".to_string());
let dynamic_name = my_unread.then(move |unread| {
    // only use the user's real name if the have less than 100 messages in their inbox
    if *unread < 100 {
        my_name.clone()
    } else {
        insulting_name.clone()
    }
});
assert_eq!(engine.get(&dynamic_name), "Lazybum");
my_unread_updater.set(50);
assert_eq!(engine.get(&dynamic_name), "Robo");

观察到的节点

您可以向引擎指示希望观察某个节点

engine.mark_observed(&dynamic_name);

现在当您请求它时,它将 减少频繁遍历整个图,这对于具有大型 Anchor 依赖树的场景非常有用。但是,也有一些缺点

  • 每次您 get 任何 Anchor 时,所有观察到的节点都将更新。
  • 如果观察到的依赖项之一是 then,则由它请求的节点可能会被重新计算,即使它们在严格意义上并不是必要的。

它有多快?

您可以在 bench 文件夹中查看一些微基准测试。这些是运行 stabilize_linear_nodes_simple 的结果,这是一个由许多 map 节点组成的线性链,每个节点将 1 添加到某个变化的输入数字。基准测试在我的 Macbook Air(Intel,2020)上针对 Anchors 0.5.0 8c9801c 运行,使用 lto = true

节点数量 是否使用 `mark_observed`? 获取链尾的总时间 总时间 / 节点数量
10 观察到的 [485.48 ns 491.85 ns 498.49 ns] 49.185 ns
100 观察到的 [4.1734 微秒 4.2525 微秒 4.3345 微秒] 42.525 纳秒
1000 观察到的 [42.720 微秒 43.456 微秒 44.200 微秒] 43.456 纳秒
10 未观察 [738.02 纳秒 752.40 纳秒 767.86 纳秒] 75.240 纳秒
100 未观察 [6.5952 微秒 6.7178 微秒 6.8499 微秒] 67.178 纳秒
1000 未观察 [74.256 微秒 75.360 微秒 76.502 微秒] 75.360 纳秒

大致看来,观察到的节点大约有 ~42-50ns 的开销,而未观察到的节点大约有 74-76ns 的开销。这可能是相当大的改进;理想情况下,我们可以将这些数字降低到每个观察节点大约 ~15ns,这是 Incremental 实现的

对于任何增量程序,减慢的可能来自于框架的其他方面,这些方面并没有用这个非常简单的微基准测试来衡量。

在 M1 mac 上有多快?

可能快两倍?

节点数量 是否使用 `mark_observed`? 获取链尾的总时间 总时间 / 节点数量
10 观察到的 [242.68 纳秒 242.98 纳秒 243.37 纳秒] 24.30 纳秒
100 观察到的 [1.9225 微秒 1.9232 微秒 1.9239 微秒] 19.232 纳秒
1000 观察到的 [20.421 微秒 20.455 微秒 20.489 微秒] 20.46 纳秒
10 未观察 [354.05 纳秒 354.21 纳秒 354.37 纳秒] 35.42
100 未观察 [3.3810 微秒 3.3825 微秒 3.3841 微秒] 33.83 纳秒
1000 未观察 [41.429 微秒 41.536 微秒 41.642 微秒] 41.54 纳秒

参见

依赖

~1MB
~25K SLoC