Chisle
The coding-agent plugin that bills 52% of a bare model across 20 live tasks. Terse persona + tool-output compressor + context diet.
翻訳待ち
An implementation of differential dataflow using timely dataflow on Rust.
翻訳待ち
開発ツールRust · PythonGitHub @TimelyDataflow自分でインストール所有者未確認
Paste into Claude or ChatGPT for installation instructions suited to your environment.
Have you tried it? One tap is enough. No sign-in needed.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗An implementation of differential dataflow over timely dataflow on Rust.
Differential dataflow is a data-parallel programming framework designed to efficiently process large volumes of data and to quickly respond to arbitrary changes in input collections. You can read more in the differential dataflow mdbook and in the differential dataflow documentation.
Differential dataflow programs are written as functional transformations of collections of data, using familiar operators like map, filter, join, and reduce. Differential dataflow also includes more exotic operators such as iterate, which repeatedly applies a differential dataflow fragment to a collection. The programs are compiled down to timely dataflow computations.
For example, here is a differential dataflow fragment to compute the out-degree distribution of a directed graph (for each degree, the number of nodes with that many outgoing edges):
let out_degr_dist =
edges.map(|(src, _dst)| src) // extract source
.count() // count occurrences of source
.map(|(_src, deg)| deg) // extract degree
.count(); // count occurrences of degree
Alternately, here is a fragment that computes the set of nodes reachable from a set roots of starting nodes:
let reachable =
roots.iterate(|scope, reach|
edges.enter(scope)
.semijoin(reach)
.map(|(src, dst)| dst)
.concat(reach)
.distinct()
)
Once written, a differential dataflow responds to arbitrary changes to its initially empty input collections, reporting the corresponding changes to each of its output collections. Differential dataflow can react quickly because it only acts where changes in collections occur, and does no work elsewhere.
In the examples above, we can add to and remove from edges, dynamically altering the graph, and get immediate feedback on how the results change: if the degree distribution shifts we'll see the changes, and if nodes are now (or no longer) reachable we'll hear about that too. We could also add to and remove from roots, more fundamentally altering the reachability query itself.
Be sure to check out the differential dataflow documentation, which is continually improving.
Let's check out that out-degree distribution computation, to get a sense for how differential dataflow actually works. This example is examples/hello.rs in this repository, if you'd like to follow along.
A graph is a collection of pairs (Node, Node), and one standard analysis is to determine the number of times each Node occurs in the first position, its "degree". The number of nodes with each degree is a helpful graph statistic.
To determine the out-degree distribution, we create a new timely dataflow scope in which we describe our computation and how we plan to interact with it.
// create a degree counting differential dataflow
let (mut input, probe) = worker.dataflow(|scope| {
// create edge input, count a few ways.
let (input, edges) = scope.new_collection();
let out_degr_distr =
edges.map(|(src, _dst)| src) // extract source
.count() // count occurrences of source
.map(|(_src, deg)| deg) // extract degree
.count(); // count occurrences of degree
// show us something about the collection, notice when done.
let probe =
out_degr_distr
.inspect(|x| println!("observed: {:?}", x))
.probe();
(input, probe)
});
The input and probe we return are how we get data into the dataflow, and how we notice when some amount of computation is complete. These are timely dataflow idioms, and we won't get in to them in more detail here (check out the timely dataflow repository).
If we feed this computation with some random graph data, say fifty random edges among ten nodes, we get output like
Echidnatron% cargo run --release --example hello -- 10 50 1 inspect
Finished release [optimized + debuginfo] target(s) in 0.05s
Running `target/release/examples/hello 10 50 1 inspect`
observed: ((3, 1), 0, 1)
observed: ((4, 2), 0, 1)
observed: ((5, 4), 0, 1)
observed: ((6, 2), 0, 1)
observed: ((7, 1), 0, 1)
round 0 finished after 772.464µs (loading)
This shows us the records that passed the inspect operator, revealing the contents of the collection: there are five distinct degrees, three through seven. The records have the form ((degree, count), time, delta) where the time field says this is the first round of data, and the delta field tells us that each record is coming into existence. If the corresponding record were departing the collection, it would be a negative number.
Let's update the input by removing one edge and adding a new random edge:
observed: ((2, 1), 1, 1)
observed: ((3, 1), 1, -1)
observed: ((7, 1), 1, -1)
observed: ((8, 1), 1, 1)
round 1 finished after 149.701µs
We see here some changes! Those degree three and seven nodes have been replaced by degree two and eight nodes; looks like one node lost an edge and gave it to the other!
How about a few more changes?
This is the beginning of the README. Read the rest at the original. Read the full README ↗