10 个不稳定版本 (3 个破坏性更新)
0.4.2 | 2018 年 12 月 9 日 |
---|---|
0.4.1 | 2018 年 10 月 26 日 |
0.4.0 | 2018 年 8 月 23 日 |
0.3.4 | 2018 年 8 月 17 日 |
0.1.0 | 2018 年 8 月 14 日 |
#316 在 缓存
34KB
760 行
rust-calc-graph
在值图上进行高效计算。
lib.rs
:
使用此存储库将计算拆分为相关的子计算,称为节点。
您可以将信息从外部推送到一个或多个源节点,并可以从一个或多个输出节点读取结果。值只有在需要时才进行计算,并且只要它们的输入不更改就会进行缓存。这意味着当您仅更改一些输入时,重新计算是高效的,并且如果您没有从输出节点请求值,则其值永远不会进行计算。
示例
let graph = Graph::new(); // create a Graph object
let mut source = graph.source(42); // define one or more nodes for your inputs
let mut output = source.clone().map(|x| x + 1); // build one or more nodes for your outputs
assert_eq!(43, output.get_mut()); // read values from your output nodes
source.set(99); // push new values to the input nodes...
assert_eq!(100, output.get_mut()); // ...and read the output nodes
共享
Func 节点(由 Node::map
、Node::zip
和相关方法创建)拥有自己的输入(前置节点)。当您有一个作为两个或更多 func 节点的输入的节点时,您需要首先使用 shared()
let input_node = calc_graph::const_(42).shared();
let mut output1_node = input_node.clone().map(|x| x + 1);
let mut output2_node = input_node.map(|x| x * x);
assert_eq!(43, output1_node.get_mut());
assert_eq!(1764, output2_node.get_mut());
您可以在同一个程序中拥有多个 Graph
对象,但定义新节点时,其前置节点必须来自同一图。
装箱
Node
对象记得其前置节点的完整类型信息以及用于计算其值的闭包。这意味着 Node
类型的名称可能非常长,或者甚至不可能在源代码中写入。在这种情况下,您可以使用
let func_node: Node<Func1<_, i32, _>> = input_node.map(|x| x + 1);
let output_node: BoxNode<i32> = func_node.boxed();
如果想要一个可以存储一个或另一个节点的变量,也需要调用 boxed()
。这些节点可以有不同的具体类型,对每个节点调用 boxed()
会给您一对具有相同类型的节点。
多线程
Node<Source>
、SharedNode
和 BoxedNode
对象是 Send
和 Sync
,这意味着它们可以在线程之间传递。计算是在调用 node.get()
的线程上执行的。计算不是自动并行化的,尽管您可以读取来自不同线程的单独输出节点,即使它们共享相同图的部分作为输入。
let graph = Graph::new();
let input_node = graph.source(41);
let output_node = input_node.clone().map(|x| x * x).shared();
assert_eq!(1681, output_node.get());
let t = thread::spawn({
let input_node = input_node.clone();
let output_node = output_node.clone();
move || {
input_node.update(|n| {
*n += 1;
true
});
output_node.get()
}
});
assert_eq!(1764, t.join().unwrap());
input_node.update(|n| {
*n += 1;
true
});
assert_eq!(1849, output_node.get());
zip
、zip_update
等
使用 zip()
、map2()
和相关函数创建一个新的节点,该节点从您提供的 FnMut
和一个或多个其他节点的值计算其值。对于大型对象,重新计算这些节点可能效率低下,因为您的 FnMut
每次都会返回一个新的对象,它会在需要的地方被克隆。
为了提高效率,您可以使用 zip_update()
、map2_update()
和相关函数。它们与它们的非 update
等效函数工作相同,除了
- 在创建时您提供新节点的初始值
- 您的
FnMut
将一个&mut T
作为其第一个参数。您将在此处就地更新此值。 - 您的
FnMut
如果在&mut T
中更改了值则返回true
,否则返回false
。反过来,这决定了相关节点是否需要重新计算。
对于大型对象,一种有用的技术是在节点中放置一个 Arc<T>
。当您重新计算节点时,使用 Arc::make_mut
在可能的情况下就地更新对象,并避免分配一个新的 Arc
。
let input_node = graph.source(42);
let mut output_node = input_node.clone().map_update(Arc::new([0; 1024]), |big_array, x| {
let new_value = x * x;
let big_array_ref = Arc::make_mut(big_array);
if big_array_ref[0] == new_value {
false
} else {
big_array_ref[0] = new_value;
true
}
});
assert_eq!(1764, output_node.get_mut()[0]);
input_node.update(|n| {
*n += 1;
true
});
assert_eq!(1849, output_node.get_mut()[0]);
依赖关系
~1.5MB
~24K SLoC