#node #graph #value #calculations #calc #input #source

bin+lib calc-graph

在值图上进行高效计算

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缓存

MIT 许可证

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::mapNode::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>SharedNodeBoxedNode 对象是 SendSync,这意味着它们可以在线程之间传递。计算是在调用 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());

zipzip_update

使用 zip()map2() 和相关函数创建一个新的节点,该节点从您提供的 FnMut 和一个或多个其他节点的值计算其值。对于大型对象,重新计算这些节点可能效率低下,因为您的 FnMut 每次都会返回一个新的对象,它会在需要的地方被克隆。

为了提高效率,您可以使用 zip_update()map2_update() 和相关函数。它们与它们的非 update 等效函数工作相同,除了

  1. 在创建时您提供新节点的初始值
  2. 您的 FnMut 将一个 &mut T 作为其第一个参数。您将在此处就地更新此值。
  3. 您的 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