<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Frank McSherry</title>
    <description>Some notes on doing less of what I used to do, and more of what I&apos;d rather be doing.</description>
    <link>http://www.frankmcsherry.org/</link>
    <atom:link href="http://www.frankmcsherry.org/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 15 Jul 2026 18:02:31 +0000</pubDate>
    <lastBuildDate>Wed, 15 Jul 2026 18:02:31 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>The impact of fast networks on graph analytics, part 2</title>
        <description>&lt;p&gt;This is a joint post with &lt;a href=&quot;http://www.cl.cam.ac.uk/~ms705/&quot;&gt;Malte Schwarzkopf&lt;/a&gt;, cross-blogged here and at &lt;a href=&quot;http://www.cl.cam.ac.uk/research/srg/netos/camsas/blog/2015-07-31-timely-pagerank-part2.html&quot;&gt;the CamSaS blog&lt;/a&gt;. It follows up a &lt;a href=&quot;http://www.frankmcsherry.org/pagerank/distributed/performance/2015/07/08/pagerank.html&quot;&gt;previous post&lt;/a&gt; evaluating the impact of fast networks on graph processing systems.&lt;/p&gt;

&lt;p&gt;In &lt;a href=&quot;http://www.frankmcsherry.org/pagerank/distributed/performance/2015/07/08/pagerank.html&quot;&gt;the first part&lt;/a&gt; of this series, we have shown that some computations see large speedups when running on a faster network, illustrated by a PageRank implementation in &lt;a href=&quot;https://github.com/frankmcsherry/timely-dataflow&quot;&gt;timely dataflow&lt;/a&gt; in Rust. This seemed to run counter to the findings in a &lt;a href=&quot;https://www.usenix.org/system/files/conference/nsdi15/nsdi15-paper-ousterhout.pdf&quot;&gt;recent NSDI paper&lt;/a&gt; that many “big data” computations can only gain a 2-10% speedup from a faster network: although we found this to be true for the Spark/GraphX stack investigated in the paper, it did not generalize to timely dataflow.&lt;/p&gt;

&lt;p&gt;We are going to try to explain a bit more about &lt;em&gt;why&lt;/em&gt; it doesn’t generalize: how are the systems different, and what should we learn?&lt;/p&gt;

&lt;p&gt;We also profile both systems to explain the differences that we observe, address some of our readers’ questions from part one, and show some further improvements we have since made.&lt;/p&gt;

&lt;p&gt;Finally, we return to our initial question of whether having a 10G network is beneficial. We compare process-level aggregation and worker-level aggregation at 10G: with a fast network, reducing computation and increasing communication actually helps. However, maybe a faster aggregation strategy could bring 1G to performance parity with 10G? It turns out it cannot. We show that even if aggregation was &lt;em&gt;infinitely&lt;/em&gt; fast, the amount of aggregated data is still sufficiently large that sending it at 1G line rate would take longer than it takes for our 10G worker-level aggregation implementation to complete an entire PageRank iteration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tl;dr #1&lt;/strong&gt;: &lt;em&gt;GraphX spends significantly more time computing and stalled on memory accesses than it could possibly spend using the network. Timely dataflow, by comparison, let us write PageRank as a “balanced system”, bottlenecked evenly on each of its resources.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tl;dr #2&lt;/strong&gt;: &lt;em&gt;worker-level aggregation on a 10G network is 10-20% faster than the more aggressive process-level aggregation, even though it sends significantly more data.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tl;dr #3&lt;/strong&gt;: &lt;em&gt;a lower-bound analysis shows that even the best possible 1G implementation of process-level aggregation will be at least 2-3x slower than our 10G implementation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus:&lt;/strong&gt; timely dataflow improved since we published part one, bringing the PageRank per-iteration times down to about 0.5s (from 0.7s). The PageRank code didn’t need to change to benefit. We have some of the improved numbers in paretheses, but unless otherwise mentioned the data are from the weeks-older version.&lt;/p&gt;

&lt;h2 id=&quot;performance-comparison&quot;&gt;Performance comparison&lt;/h2&gt;

&lt;p&gt;To better understand the difference between timely dataflow and GraphX, we collected some utilization data from the cluster machines while running our computations. The timelines presented in the following were recorded using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;collectl&lt;/code&gt; utility, with samples taken every 100ms on each machine.&lt;/p&gt;

&lt;h3 id=&quot;graphx&quot;&gt;GraphX&lt;/h3&gt;

&lt;p&gt;Lets look at a trace of GraphX on one machine over its execution on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uk_2007_05&lt;/code&gt; graph. Different machines’ timelines can differ significantly with GraphX, so we also supply the &lt;a href=&quot;http://www.frankmcsherry.org/assets/timeseries/pagerank/graphx_uk_16x8_10g/&quot;&gt;graphs for all machines&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.frankmcsherry.org/assets/timeseries/pagerank/graphx_uk_16x8_10g/caelum-401.png&quot; alt=&quot;Cluster utilization over time, GraphX 10G&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is a bit hard to read; let’s zoom in to the GraphX plot a bit. We pick a 30 second range in the iteration phase (210-240s; &lt;a href=&quot;http://www.frankmcsherry.org/assets/timeseries/pagerank/graphx_uk_16x8_10g_zoom/&quot;&gt;graphs for all machines&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.frankmcsherry.org/assets/timeseries/pagerank/graphx_uk_16x8_10g_zoom/caelum-401.png&quot; alt=&quot;Cluster utilization over time, GraphX 10G, zoomed&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That’s a bit more readable; there are three distinct iterations clearly visible here.&lt;/p&gt;

&lt;p&gt;The plots tell us a bit about where GraphX’s time goes:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;There is some idle time during each iteration, where the Spark workers wait on a few slower (or more burdened) machines (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;caelum-302&lt;/code&gt;). It’s also evident from the gradual drop in CPU utilization that some tasks on each machine take longer than others. This corroborates results from the NSDI paper, that Java GC can be a cause of stragglers (§5.3).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;GraphX uses the disk, but not very much. In the initial phase (before iteration 1 completes), the disk I/O is composed of local reads of the input data set (each of 23 read bursts reads 1.5 GB of data). Although each worker’s data are held in memory,&lt;a name=&quot;fn0l&quot; href=&quot;#fn0&quot;&gt;&lt;sup&gt;0&lt;/sup&gt;&lt;/a&gt; in each iteration GraphX has two write bursts to the root filesystem SSD,&lt;sup&gt;&lt;a name=&quot;fn1l&quot; href=&quot;#fn1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; which (as we understand it) is a consequence of Spark writing all shuffled data to disk (see §3.2 in the &lt;a href=&quot;http://www.eecs.berkeley.edu/~keo/publications/nsdi15-final147.pdf&quot;&gt;NSDI paper&lt;/a&gt;).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;GraphX’s memory usage is truly epic. The graph’s input edge list in ASCII is 63 total GB on disk&lt;sup&gt;&lt;a name=&quot;fn2l&quot; href=&quot;#fn2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; and 15 GB in binary, which when partitioned should amount to less than one GB per machine across 16 machines.&lt;sup&gt;&lt;a name=&quot;fn3l&quot; href=&quot;#fn3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; GraphX uses substantially more memory than this: its memory footprint grows to 42 GB in the load phase, and further to 50 GB during the iterations. This is possibly due to the overheads of tracking many small Java objects (a known issue, &lt;a href=&quot;https://databricks.com/blog/2015/04/28/project-tungsten-bringing-spark-closer-to-bare-metal.html&quot;&gt;being addressed&lt;/a&gt; in Spark 1.5). As a result, GraphX doesn’t run on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uk_2007_05&lt;/code&gt; graph with fewer than eight of our 64 GB machines, and it doesn’t run on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;twitter_rv&lt;/code&gt; graph with fewer than 16 machines.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;There is some brief network traffic in each iteration, with two spikes that send and receive between 1 and 1.5 GB together. The spikes occur early in the iteration, once at the start of the first period of intense CPU use, and then again at the beginning of the second one. The computation appears to rarely be blocked on them (as observed in the NSDI paper).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Most of the time is spent with the allocated CPUs (8 out of 12) fully saturated, corroborating the NSDI paper’s finding that Spark/GraphX are mostly CPU-bound. It isn’t entirely clear to us what the GraphX computation is exactly doing with all this compute, although serialization overheads are proposed in the NSDI paper. Our later profiling results indicate that serialization is about 20% of the time, and that in addition to compute GraphX may be waiting on memory for a large fraction of time.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, let’s look at the timely dataflow implementation for comparison!&lt;/p&gt;

&lt;h3 id=&quot;timely-dataflow&quot;&gt;Timely dataflow&lt;/h3&gt;

&lt;p&gt;Here is the timely dataflow trace from one machine (it is fairly representative of &lt;a href=&quot;http://www.frankmcsherry.org/assets/timeseries/pagerank/timely_uk_16x8_10g/&quot;&gt;all 16 timely dataflow traces&lt;/a&gt;) across its execution on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uk_2007_05&lt;/code&gt; graph:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.frankmcsherry.org/assets/timeseries/pagerank/timely_uk_16x8_10g/caelum-401.png&quot; alt=&quot;Cluster utilization over time, timely dataflow, 10G&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Notice the timescale: in the time it takes GraphX to do three iterations above the timely dataflow implementation has &lt;strong&gt;started and completed the entire computation&lt;/strong&gt;, including loading, partitioning, and sorting the edge data and running 20 iterations.&lt;a name=&quot;fn5l&quot; href=&quot;#fn5&quot;&gt;&lt;sup&gt;5&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The compute is much leaner here, there is very little skew (despite no cleverness in partitioning the graph, something that many papers claim is critical!), and memory utilization is just a few gigabytes (the line is hard to see as it remains below 2 GB, under 4% of the machine’s total 64 GB RAM).&lt;/p&gt;

&lt;p&gt;What is timely dataflow doing differently? Well, the short version is that there isn’t really all that much to do, and timely dataflow doesn’t do much more than that.&lt;/p&gt;

&lt;p&gt;Let’s go through the moving parts and see what has to get done.&lt;/p&gt;

&lt;h4 id=&quot;initially-loading-up-data&quot;&gt;Initially: loading up data&lt;/h4&gt;

&lt;p&gt;The computation first needs to load up and exchange the edge data, so that each worker has the edges associated with the vertices it is responsible for. There is little disk I/O in this phase as we have a hot buffer cache. Each worker then “exchanges” and sorts the edges, and gets ready to start computing.&lt;/p&gt;

&lt;p&gt;We put “exchanges” in quotes because each worker only loads the edges it will eventually require (and the whole dataset is on the machine already), so although they go through timely’s exchange channels, none actually reach the network in practice. If we were to send them, the 3.7B edges would require 29.7 GB on the wire, i.e., 1.86 GB on each of the 16 computers. This would take about 1.5s to exchange on a 10G link, or 15s on a 1G link.&lt;/p&gt;

&lt;p&gt;As the number of processes grows, the number of edges each process must exchange and sort decreases. For 1.4 billion and 3.7 billion edges, each of 16x8 = 128 workers must process roughly 11 million (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;twitter_rv&lt;/code&gt;) and 29 million edges (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uk_2007_05&lt;/code&gt;). Exchanging and sorting these data takes each worker just a few seconds (we use a &lt;a href=&quot;https://github.com/frankmcsherry/pagerank/blob/master/src/sorting.rs&quot;&gt;radix sort&lt;/a&gt;), and it could be even faster with more effort.&lt;/p&gt;

&lt;h4 id=&quot;each-iteration-producing-sending-and-receiving-updates&quot;&gt;Each iteration: producing, sending, and receiving updates&lt;/h4&gt;

&lt;p&gt;Once each worker receives the signal that there are no more edges due to arrive (a “notification” in Naiad terms), it is able to start producing rank updates for each of its edge destinations. This involves first preparing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt; vector, by dividing each entry by its degree, scaling down by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alpha&lt;/code&gt;, etc.; this is all just a linear scan through a vector, doing a few operations on each element.&lt;/p&gt;

&lt;h4 id=&quot;producing-updates&quot;&gt;Producing updates&lt;/h4&gt;

&lt;p&gt;The worker then walks sequentially through the list of edges, ordered by destination, and for each edge performs one random access into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt; vector. These vectors are relatively compact, and get smaller proportional to the number of workers involved, hence improving locality with scale. For 61M (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;twitter_rv&lt;/code&gt;) and 105M (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uk_2007_05&lt;/code&gt;) vertices, each worker indexes into roughly 476,000 and 820,000 elements, respectively. These aren’t huge sets, but they don’t all fit in the CPU caches. Waiting on reads from memory is one of the places that our implementation spends its time (as does GraphX, as we’ll see).&lt;/p&gt;

&lt;h4 id=&quot;sending-updates&quot;&gt;Sending updates&lt;/h4&gt;

&lt;p&gt;For each destination, the worker must also send a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(u32, f32)&lt;/code&gt; pair, indicating a rank update for the relevant vertex. These are handed to the timely dataflow system, which maintains buffers for each destination. Once sufficiently many updates are at hand (or the worker finishes), the data are serialized: this is essentially a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;memcpy&lt;/code&gt;, as it is in many other languages that are not JVM-based (e.g., C, C++, C#), which may explain some of Spark and GraphX’s overhead. Serialized data are then put on the network by a separate thread for each socket, without blocking the worker.&lt;sup&gt;&lt;a name=&quot;fn6l&quot; href=&quot;#fn6&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h4 id=&quot;receiving-updates&quot;&gt;Receiving updates&lt;/h4&gt;

&lt;p&gt;Having sent all of the necessary updates, a worker repeatedly receives updates from other workers. Each worker removes binary data from a queue shared with the networking threads, who each demultiplex data received from the network into said queue. Each worker thread deserializes the data (again, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;memcpy&lt;/code&gt;), and the applies the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+=&lt;/code&gt; operation indicated by the update to its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt; vector. Although the data could be arriving in any order, destinations are ordered small to large, and we might hope for good locality here.&lt;/p&gt;

&lt;h4 id=&quot;recap&quot;&gt;Recap&lt;/h4&gt;

&lt;p&gt;A distributed PageRank implementation doesn’t need to do much more than read and accumulate values for each destination, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;memcpy&lt;/code&gt; the results a few times, and apply the updates. Timely dataflow doesn’t really do much more than help out with this, managing the partitioning of the updates (more on this in a bit), putting bits on the wire, and coordinating the workers (telling them once they’ve received all the messages for an iteration).&lt;/p&gt;

&lt;p&gt;In a perfect world, timely dataflow computations are either running user code as written, or waiting on data from the network. If you get the “user code” part sorted out properly, communication is really the only thing left to improve.&lt;/p&gt;

&lt;p&gt;GraphX is integrated with a larger data processing stack, and expresses graph computations by iteratively repeating higher-level dataflow operations (“join” and “group by” stages, forming a triplets view; §3.2 in the &lt;a href=&quot;https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-gonzalez.pdf&quot;&gt;paper&lt;/a&gt;) and the graph as a set of parallel collections (§3.1). This approach requires a number of auxiliary data structures and makes additional passes over the data, with several optimizations aiming to reduce communication (§4.1-4.4). This, and implementation-level overheads owed to the JVM’s data representation, is a likely explanation for the memory and computation overhead it sees over timely dataflow.&lt;/p&gt;

&lt;h3 id=&quot;profiling&quot;&gt;Profiling&lt;/h3&gt;

&lt;p&gt;After that detour into how the implementations work on a conceptual level, let’s answer some more questions about the differences between GraphX and our implementation with the aid of some profiling.&lt;/p&gt;

&lt;p&gt;One reasonable question to ask is where the time goes in GraphX, and what could be done to reduce this overhead. To find out, we attached the &lt;a href=&quot;https://github.com/patric-r/jvmtop&quot;&gt;JvmTop profiler&lt;/a&gt; to the running GraphX version of PageRank. The following snapshot is fairly representative for what we saw during the compute phase:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;JvmTop 0.8.0 alpha - 09:16:33,  amd64, 12 cpus, Linux 3.13.0-24, load avg 4.59
http://code.google.com/p/jvmtop

Profiling PID 15963: org.apache.spark.executor.CoarseGrainedE

57.15% (    13.14s) scala.collection.Iterator$$anon$13.hasNext()
11.07% (     2.55s) scala.collection.Iterator$class.foreach()
 3.65% (     0.84s) ....esotericsoftware.kryo.serializers.FieldSerializer.ne()
 2.97% (     0.68s) org.xerial.snappy.SnappyNative.arrayCopy()
 2.80% (     0.64s) scala.collection.Iterator$$anon$11.next()
 2.59% (     0.59s) io.netty.channel.nio.NioEventLoop.select()
 2.38% (     0.55s) org.xerial.snappy.SnappyNative.rawCompress()
 2.18% (     0.50s) com.esotericsoftware.kryo.Kryo.readClassAndObject()
 1.82% (     0.42s) ....esotericsoftware.kryo.serializers.FieldSerializer.re()
 1.25% (     0.29s) ....esotericsoftware.kryo.util.DefaultClassResolver.read()
 0.95% (     0.22s) com.twitter.chill.KryoBase$$anonfun$1.apply()
 0.81% (     0.19s) org.xerial.snappy.SnappyNative.rawUncompress()
 0.80% (     0.18s) com.twitter.chill.ScalaCollectionsRegistrar.apply()
 0.78% (     0.18s) ....esotericsoftware.kryo.util.DefaultClassResolver.writ()
 0.74% (     0.17s) ....esotericsoftware.kryo.serializers.DefaultSerializers()
 0.73% (     0.17s) com.esotericsoftware.kryo.Kryo.newSerializer()
 0.66% (     0.15s) scala.collection.IndexedSeqOptimized$class.foreach()
 0.57% (     0.13s) ....esotericsoftware.reflectasm.AccessClassLoader.loadCl()
 0.46% (     0.11s) scala.collection.Iterator$$anon$11.hasNext()
 0.40% (     0.09s) scala.collection.immutable.Range.foreach$mVc$sp()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Serialization overhead &lt;a href=&quot;http://www.cl.cam.ac.uk/research/srg/netos/camsas/blog/2015-07-08-timely-pagerank-part1.html#comment-2127472445&quot;&gt;has been suspected&lt;/a&gt; as the main culprit, and is certainly responsible for some of the overhead. If, as a crude approximation, we sum everything with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kryo&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xerial&lt;/code&gt; in the name above, we get 19.63% of time being devoted to serialization. (Note that garbage collection, another important factor in Spark performance, does not show up here, as JvmTop only profiles when the JVM runs user code.)&lt;/p&gt;

&lt;p&gt;However, a much larger fraction of time (72.54%) is spent in various Scala collection methods, with 57% of time in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hasNext()&lt;/code&gt; method of some iterator. This may be user logic (GAS vertex implementations), or edge iterators – who really knows?&lt;a name=&quot;fn7l&quot; href=&quot;#fn7&quot;&gt;&lt;sup&gt;7&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, one of our readers &lt;a href=&quot;https://news.ycombinator.com/item?id=9854895&quot;&gt;suggested&lt;/a&gt; taking average cycle-per-instruction (CPI) measurements for GraphX and timely dataflow in order to see which system spends more time waiting on memory. We thought that was a rather nice idea, so we went and did it using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf&lt;/code&gt; profiling tool for Linux.&lt;/p&gt;

&lt;center&gt;
&lt;table border=&quot;1&quot; style=&quot;width:75%&quot;&gt;
&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;GraphX, 10G&lt;/th&gt;
&lt;th&gt;Timely dataflow, 10G&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;Cycles per instruction (CPI)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1.95&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1.09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;Stalled cycles per instruction&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1.54&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;0.74&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;Instructions per LLC miss&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;252&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;522&lt;/td&gt;
&lt;/tr&gt;
&lt;caption&gt;Performance counter profiling results.&lt;/caption&gt;
&lt;/table&gt;
&lt;/center&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;The table shows what we already suspected: GraphX’s CPI is almost 2x higher than timely dataflow’s, meaning that the processor spends more time waiting on other parts of the system than actually doing work. Likewise, GraphX has a higher average number of stalled cycles per instruction, and misses in the LLC more often (about every 252 instructions &lt;em&gt;vs.&lt;/em&gt; every 522 instructions in timely dataflow).&lt;/p&gt;

&lt;p&gt;In essence, these numbers tell us something that the CPU timeline above did not: both timely dataflow and GraphX are memory-bound on this computation (as we expected), but timely dataflow has better cache locality and thus (potentially) spends less time waiting on memory accesses.&lt;/p&gt;

&lt;h3 id=&quot;good-news-everyone-timely-dataflow-has-got-even-faster&quot;&gt;Good news everyone! Timely dataflow has got even faster.&lt;/h3&gt;

&lt;p&gt;Since we reported the results in part 1 of this series, timely dataflow has improved. Primarily, this has been about streamlining data movement, cutting out several redundant memory copies and simplifying the exchange channel logic, resulting in higher network utilization.&lt;/p&gt;

&lt;p&gt;Here are the utilization timelines before and after the improvements:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.frankmcsherry.org/assets/timeseries/pagerank/timely_uk_16x8_10g/caelum-401.png&quot; alt=&quot;Cluster utilization over time, timely dataflow, 10G (old)&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.frankmcsherry.org/assets/timeseries/pagerank/timely_uk_16x8_10g_workeragg/caelum-401.png&quot; alt=&quot;Cluster utilization over time, timely dataflow, 10G (improved)&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The second series is effectively just a compressed version of the first: the network and compute utilizations spike higher as we need less time to process the same amount of data.&lt;/p&gt;

&lt;p&gt;Numerically, the total and per-iteration times for 16 workers improved to 14.5s (from 19.3s) and 0.53s (from 0.75s) on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;twitter_rv&lt;/code&gt; graph, and 19.1s (from 23.7s) and 0.59s (from 0.76s) on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uk_2007_05&lt;/code&gt; graph. The per-iteration times improved by 1.37x and 1.28x for the two graphs (~30%), which bump our best-case improvement over GraphX from 17x to 23x.&lt;/p&gt;

&lt;p&gt;We are pointing this out because we had to get some new measurements we hadn’t previous taken, and needed to re-calibrate for them. Specifically, …&lt;/p&gt;

&lt;h3 id=&quot;process-level-vs-worker-level-aggregation-on-10g&quot;&gt;Process-level vs. worker-level aggregation on 10G&lt;/h3&gt;

&lt;p&gt;As &lt;a href=&quot;http://www.cl.cam.ac.uk/research/srg/netos/camsas/blog/2015-07-08-timely-pagerank-part1.html#comment-2153198798&quot;&gt;one astute reader observed&lt;/a&gt;, while we pointed out that worker-level aggregation outperforms process-level aggregation on a 10G network, we did not report the detailed numbers in part one.&lt;/p&gt;

&lt;p&gt;For bonus credit, we got utilization timelines for worker-level and process-level aggregation on 10G to visually present the trade-offs involved. These measurements are on the improved timely dataflow code.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.frankmcsherry.org/assets/timeseries/pagerank/timely_uk_16x8_10g_workeragg/caelum-401.png&quot; alt=&quot;Cluster utilization over time, timely dataflow, 10G (worker-level aggregation)&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.frankmcsherry.org/assets/timeseries/pagerank/timely_uk_16x8_10g_processagg/caelum-401.png&quot; alt=&quot;Cluster utilization over time, timely dataflow, 10G+ (process-level aggregation)&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The process-level aggregation experiment shown in the time series shown here uses the network noticeably less than the worker-level time series does, but also takes longer because it keeps the workers busy for a bit longer on each iteration (due to the extra aggregation work). As you may remember from the first part, the worker-level aggregation strategy allows us to start sending updates as we produce them, while process-level aggregation must wait until all updates are produced.&lt;/p&gt;

&lt;p&gt;When we applied our trusted &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perf&lt;/code&gt; profiler, we found that both aggregation strategies consume about the same number of cycles, but the worker-level aggregation spends these cycles managing the additional network data on separate cores, rather than occupying the worker threads with additional aggregation. Moreover, we found that worker-level aggregation hits the last-level cache twice as often (probably due to the network threads accessing a larger working set), but it has a lower LLC miss ratio (39.6% &lt;em&gt;vs.&lt;/em&gt; 47.1% for process-level aggregation). Finally, worker-level aggregation has a higher CPI (1.18) than process-level aggregation (0.99) – again, likely because of its larger working set.&lt;/p&gt;

&lt;p&gt;All in all, worker-level aggregation is 10-20% faster than process-level aggregation on the 10G network, but up to 4x slower on 1G (as discussed in part one). This comes despite sending more data over the network – sometimes, being more chatty actually helps!&lt;/p&gt;

&lt;h2 id=&quot;lower-bound-analysis&quot;&gt;Lower bound analysis&lt;/h2&gt;

&lt;p&gt;That process-level aggregation stuff sure seems to transmit a lot less data. Maybe if only we had worked harder to optimize the CPU use we would see a 1G network overtake the worker-level 10G implementation. Could a better implementation get closer to the 10G performance with more engineering? Yes, but not too much closer.&lt;/p&gt;

&lt;p&gt;Let’s reason about how fast the best possible 1G implementation could go. Due to the way we have arranged the computation, each machine must communicate at least some number of updates. Using worker-level aggregation, each process must transmit a number of updates equal to the sum of the numbers of distinct destinations for &lt;em&gt;each worker&lt;/em&gt;. Using process-level aggregation, by contrast, each process must transmit a number of updates equal to the number of distinct destinations across &lt;em&gt;all of its workers&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This does reduce the number of required updates, but it only reduces it so much.&lt;/p&gt;

&lt;p&gt;Each process must transmit &lt;em&gt;at least&lt;/em&gt; this smaller number of updates. The time it takes to put this data onto a 1G network is the &lt;em&gt;lower&lt;/em&gt; limit on how fast it can go, even if the aggregation takes no time at all. If this lower limit ends up taking longer to send the data than a 10G implementation (spoiler: it will), we have learned something.&lt;/p&gt;

&lt;p&gt;Let’s look at the minimum number of updates that one specific process&lt;sup&gt;&lt;a name=&quot;fn8l&quot; href=&quot;#fn8&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; must transmit when using process-level aggregation. Each update is at least 8 bytes (a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u32&lt;/code&gt; and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f32&lt;/code&gt;); divided by 1 Gb/s, we get a &lt;em&gt;lower bound&lt;/em&gt; on how quickly this approach can iterate on a 1G network (assuming infinitely fast aggregation). Depending on the number of machines, this time is between 1 and 5 seconds (third column). This best-case bound is still often slower than our &lt;em&gt;measured&lt;/em&gt;, real-world 10G times. If we compare the two, we get a lower bound on the speed-up from using our 10G implementation as compared to the most efficient 1G implementation possible (fourth column).&lt;a name=&quot;fn9l&quot; href=&quot;#fn9&quot;&gt;&lt;sup&gt;9&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;
&lt;table border=&quot;1&quot; style=&quot;width:75%&quot;&gt;
&lt;tr&gt;&lt;th&gt;cores&lt;/th&gt;
&lt;!-- &lt;th&gt;worker-level&lt;/th&gt;  --&gt;
&lt;th&gt;updates&lt;/th&gt;
&lt;th&gt;1G lower bound&lt;/th&gt;
&lt;th&gt;1G bound / 10G measured&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;2x8&lt;/td&gt;
&lt;!-- &lt;td align=&quot;right&quot;&gt;126,689,200&lt;/td&gt; --&gt;
&lt;td align=&quot;right&quot;&gt;31,215,505&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;2.00s&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;0.85x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;4x8&lt;/td&gt;
&lt;!-- &lt;td align=&quot;right&quot;&gt;83,591,288&lt;/td&gt; --&gt;
&lt;td align=&quot;right&quot;&gt;26,835,975&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1.72s&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1.15x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;8x8&lt;/td&gt;
&lt;!-- &lt;td align=&quot;right&quot;&gt;52,899,088&lt;/td&gt; --&gt;
&lt;td align=&quot;right&quot;&gt;22,809,014&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1.46s&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1.39x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;16x8&lt;/td&gt;
&lt;!-- &lt;td align=&quot;right&quot;&gt;38,026,392&lt;/td&gt; --&gt;
&lt;td align=&quot;right&quot;&gt;15,836,150&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1.01s&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1.35x&lt;/td&gt;
&lt;/tr&gt;
&lt;caption&gt; Minimum numbers of 8-byte updates process zero must send each iteration for the twitter_rv graph.&lt;/caption&gt;
&lt;/table&gt;
&lt;/center&gt;

&lt;p&gt;&lt;/p&gt;

&lt;center&gt;
&lt;table border=&quot;1&quot; style=&quot;width:75%&quot;&gt;
&lt;tr&gt;&lt;th&gt;cores&lt;/th&gt;
&lt;!-- &lt;th&gt;worker-level&lt;/th&gt;  --&gt;
&lt;th&gt;updates&lt;/th&gt;
&lt;th&gt;1G lower bound&lt;/th&gt;
&lt;th&gt;1G bound / 10G measured&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;2x8&lt;/td&gt;
&lt;!-- &lt;td align=&quot;right&quot;&gt;214,935,720&lt;/td&gt; --&gt;
&lt;td align=&quot;right&quot;&gt;75,220,268&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;4.81s&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;2.08x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;4x8&lt;/td&gt;
&lt;!-- &lt;td align=&quot;right&quot;&gt;140,918,216&lt;/td&gt; --&gt;
&lt;td align=&quot;right&quot;&gt;55,465,829&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;3.55s&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;2.30x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;8x8&lt;/td&gt;
&lt;!-- &lt;td align=&quot;right&quot;&gt;88,755,160&lt;/td&gt; --&gt;
&lt;td align=&quot;right&quot;&gt;39,443,730&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;2.52s&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;2.36x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;right&quot;&gt;16x8&lt;/td&gt;
&lt;!-- &lt;td align=&quot;right&quot;&gt;55,243,248&lt;/td&gt; --&gt;
&lt;td align=&quot;right&quot;&gt;26,866,965&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt; 1.72s &lt;/td&gt;
&lt;td align=&quot;right&quot;&gt; 2.26x &lt;/td&gt;
&lt;/tr&gt;
&lt;caption&gt; Minimum numbers of 8-byte updates process zero must send each iteration for the uk-2007-05 graph.&lt;/caption&gt;
&lt;/table&gt;
&lt;/center&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;These numbers indicate that one cannot hope to get the observed 10G performance on a 1G network just by optimizing the computation. Of course, there are other options – such as laying out the graph data along the “smarter” Hilbert-curve mentioned earlier – which we leave for another post.&lt;/p&gt;

&lt;h2 id=&quot;how-close-to-the-metal-are-we&quot;&gt;How “close to the metal” are we?&lt;/h2&gt;

&lt;p&gt;With graph processing systems, hardly a systems conference seems to go by without another paper announcing mani-fold speedups over some precursor system. How much more room is there for improvement in our code?&lt;/p&gt;

&lt;p&gt;First, we could change the program to look more like the &lt;a href=&quot;http://www.frankmcsherry.org/graph/scalability/cost/2015/01/15/COST.html&quot;&gt;smarter single-threaded code&lt;/a&gt;, which uses a Hilbert space-filling curve to lay out the edge data. This can improve cache locality and reduce time blocked on memory reads, and can also reduce the data transmitted across the network. That would be a great thing to try out, and we will probably do that, but let’s look at some anecdotal evidence of how “close to the metal” what we already are.&lt;/p&gt;

&lt;p&gt;Perhaps surprisingly, one of the more expensive parts of our implementation is the division by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workers&lt;/code&gt;, which each worker needs to do for each received &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(y, update)&lt;/code&gt; pair:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// repeatedly receive data from other workers&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input2&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.pull&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;notificator&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.notify_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.drain_temp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;workers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If we hard-wire &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workers&lt;/code&gt; to a compile-time constant power of two (specifically, 16x8 = 128), the per-iteration runtime drops by about 10%, as the compiler turns the division into a shift.
The fact that replacing arbitrary divisions with constant shifts yields such speedups likely suggests that we’re getting pretty “close to the metal”, and that relatively few other system overheads exist in our implementation.&lt;/p&gt;

&lt;h2 id=&quot;caveats--discussion&quot;&gt;Caveats &amp;amp; discussion&lt;/h2&gt;

&lt;p&gt;This blog post would not be complete without some discussion of the caveats of what we’ve discussed – be they perceived or real. Here are some objections to our analysis that you might have.&lt;/p&gt;

&lt;h3 id=&quot;your-approach-seems-to-require-a-lot-of-hand-coding-and-tuning-is-it-user-friendly&quot;&gt;Your approach seems to require a lot of hand-coding and tuning; is it user-friendly?&lt;/h3&gt;

&lt;p&gt;If you look at &lt;a href=&quot;https://github.com/frankmcsherry/pagerank&quot;&gt;our code&lt;/a&gt; one thing becomes clear: the implementation indeed looks more like a “program” than implementations for other data processing systems might. Timely dataflow mandates relatively little program structure (compared to, say, MapReduce’s or Spark’s paradigms), which gives us the flexibility to write the program as we see fit.&lt;/p&gt;

&lt;p&gt;This flexibility is helpful, but it comes with greater responsibility. While a programmer can more easily write performant code in timely dataflow, she can also more easily stumble into anti-patterns. GraphX’s higher-level interfaces may be the better option for writing the simplest possible programs; however, timely dataflow still maintains the core benefit of MapReduce, Spark and other parallel programming frameworks: it alleviates the user of having to implement tricky distributed coordination code, and the computational core of a PageRank implementation is &lt;a href=&quot;https://github.com/frankmcsherry/pagerank/blob/e5ea254b1f2cb944bfc13e0b638a13c8a648662c/src/main.rs#L91-L104&quot;&gt;pretty concise&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One could certainly write a “library” of PageRank and other graph computations atop timely dataflow, providing an appealingly simple interface to the more casual user. Timely dataflow allows the inter-operation of pre-fabricated parts (similar to Naiad vertices) with hand-rolled operators as appropriate for the computation.&lt;/p&gt;

&lt;h3 id=&quot;doesnt-the-lack-of-fault-tolerance-mean-that-you-avoid-overhead&quot;&gt;Doesn’t the lack of fault tolerance mean that you avoid overhead?&lt;/h3&gt;

&lt;p&gt;One major difference between timely dataflow and Spark/GraphX is that the latter can tolerate worker failures.&lt;a name=&quot;fn10l&quot; href=&quot;#fn10&quot;&gt;&lt;sup&gt;10&lt;/sup&gt;&lt;/a&gt; Fault-tolerance absolutely introduces overheads, both directly and indirectly: GraphX writes data to disk, but also needs to be structured as a stateless dataflow which prevents (or at least complicates) the maintenance of indexed in-memory state, while timely dataflow is stateful.&lt;/p&gt;

&lt;p&gt;A different question is whether fault-tolerance is serving its purpose here. In batch processing, fault-tolerance is fundamentally a performance optimization, meant to get you the result faster than re-running the computation from scratch. Arguably, this investment makes sense when dealing with jobs over many hours and hundreds of machines, but if the entire computation takes on the order of 20s over a handful of machines, it may not be worth it. Of course, this trade-off may chance for larger graphs and more PageRank iterations.&lt;/p&gt;

&lt;p&gt;That said, timely dataflow could be extended with support for fault tolerance. A blunt approach would simply check-point intermediate results (e.g., every 100 iterations). There is also &lt;a href=&quot;https://users.soe.ucsc.edu/~abadi/Papers/many-short-revised.pdf&quot;&gt;some existing work&lt;/a&gt; on cleverer fault-tolerant abstractions of Naiad’s version of the timely dataflow model, and this could be applied to our implementation.&lt;/p&gt;

&lt;h3 id=&quot;graphx-is-part-of-a-general-purpose-stack-not-a-special-purpose-graph-processing-system-isnt-this-is-a-flawed-comparison&quot;&gt;GraphX is part of a general-purpose stack, not a special-purpose graph processing system; isn’t this is a flawed comparison?&lt;/h3&gt;

&lt;p&gt;We weren’t specifically trying to do a bake-off between Spark/GraphX and our code, though the observed results are a happy coincidence. Rather, we were trying to understand what is it about systems that prevent them from benefiting from modern networking kit.&lt;/p&gt;

&lt;p&gt;Spark and timely dataflow are both general-purpose data-parallel systems, and you can build graph processing infrastructure on either (GraphX and Naiad’s GraphLINQ, respectively). However, their relative performance reveals a bit about the properties of the underlying systems. In this work, we wrote code that fills GraphLINQ’s role atop timely dataflow in Rust (although we didn’t implement everything GraphLINQ supports).&lt;/p&gt;

&lt;h2 id=&quot;what-about-other-state-of-the-art-systems-are-you-as-fast-as-graphlab-or-naiad&quot;&gt;What about other state-of-the-art systems; are you as fast as GraphLab or Naiad?&lt;/h2&gt;

&lt;p&gt;Performance-wise, the closest competitor (and to our knowledge, the previous-best system) is Naiad, which is based on the same timely dataflow paradigm as the code presented here. Naiad runs one iteration of PageRank on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;twitter_rv&lt;/code&gt; graph in &amp;amp;approx;2s using 16 dedicated eight-core machines, and in &amp;amp;approx;1s using 49 machines (Fig. 7(a), §6.1 in &lt;a href=&quot;http://dl.acm.org/citation.cfm?id=2522738&quot;&gt;the paper&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The Naiad numbers are on a 1G network, and were totally communication bound: there was about 0.15s of computation each iteration, and the rest waiting on communication. Naiad would likely see substantial improvement from a 10G network, too. We have established above that our implementation shown here cannot beat 1s on a 1G network without fundamentally changing the algorithm (Naiad used a Hilbert space-filling curve to partition the edges); on a 10G network, our best result for the same computation is 0.5s per iteration using 16 machines.&lt;/p&gt;

&lt;p&gt;The GraphX paper authors found its performance equal to GraphLab and Giraph; our results also beat the published numbers from these systems, though we haven’t done a deep-dive comparison against them. Our understanding is that the GraphLab numbers &lt;a href=&quot;https://twitter.com/mejoeyg/status/623563038096949248&quot;&gt;have improved substantially&lt;/a&gt; from their &lt;a href=&quot;https://www.usenix.org/system/files/conference/osdi12/osdi12-final-167.pdf&quot;&gt;published paper&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;the-nsdi-paper-isnt-about-pagerank-how-does-your-analysis-apply-to-their-conclusions&quot;&gt;The NSDI paper isn’t about PageRank; how does your analysis apply to their conclusions?&lt;/h3&gt;

&lt;p&gt;The NSDI paper made some specific observations, and suggested that they may generalize. Our point is that they do not generalize &lt;em&gt;too much&lt;/em&gt;, and that the outcome depends on the workload, the data processing system implementation and the available hardware.&lt;/p&gt;

&lt;p&gt;The majority of the workloads in the NSDI paper are TPC-DS and BDBench queries, whose Spark implementations shuffle less data than they read as input. This property is one of the reasons invoked to explain why improving the exchange performance does not improve overall performance.&lt;a name=&quot;fn11l&quot; href=&quot;#fn11&quot;&gt;&lt;sup&gt;11&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The NSDI paper’s conclusions &lt;em&gt;may&lt;/em&gt; generalize for other types of computations, but we won’t know without implementing them as well.&lt;a name=&quot;fn12l&quot; href=&quot;#fn12&quot;&gt;&lt;sup&gt;12&lt;/sup&gt;&lt;/a&gt; At least one &lt;a href=&quot;http://dl.acm.org/citation.cfm?id=2750547&quot;&gt;other paper&lt;/a&gt; reports that for well-tuned implementations, the JOIN processing common in analytics workloads &lt;em&gt;should&lt;/em&gt; be network bound, even on an Infiniband network.&lt;/p&gt;

&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons learned&lt;/h2&gt;

&lt;p&gt;To finish up, let’s recap what we’ve learned.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Computers are surprisingly fast!&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Modern hardware can do quite a lot of work in a short time – for example, it can iterate over 3 billion edges and exchange hundreds of millions of updates in less than a second. However, to get this performance, it is necessarily to carefully think about how every layer of the system works. Our ability to speed up PageRank by up to 16x (or 23x, with the new numbers) suggests that there’s still a fair bit of this kind of optimization to be had in distributed data processing systems.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Faster networks &lt;em&gt;can&lt;/em&gt; help, and they influence implementations.&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Strategies that are helpful on a (slow) 1G network, such as process-level aggregation, can actually be &lt;em&gt;harmful&lt;/em&gt; on a faster 10G network.
 Spark and GraphX are examples of systems designed with the assumption that I/O is slow, and that doing extra computing to reduce the I/O required is always beneficial. As we’ve shown with the aggregation strategies, this is not necessarily the case. This motivates thinking about whether we need systems capable of dynamically adapting their strategies to the hardware they’re running on.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Distributed processing &lt;em&gt;does&lt;/em&gt; help even communication-heavy graph workloads.&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Our best distributed PageRank is 11x faster than even the smart, Hilbert-curve-based single-threaded implementation (now 13.4x), and 5x faster than our best multi-threaded, single machine implementation. In other words, even communication-heavy workloads on data sets that fit into a single machine can get significant benefit from a distributed implementation, provided it makes good use of available system resources.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Balanced system design is key.&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Our implementation overlaps communication and computation, stressing CPU and network resources roughly equally. In general, when adding to one resource failed to deliver speedups, we could realize them by improving on another: when we found that network utilization was uneven, we improved it by re-structuring the computation to overlap communication more, and when computation became a bottleneck, we reduced it by communicating more data. The goal should be a system in which improving any single resource won’t yield a speedup on its own. Spark/GraphX, by comparison, looks highly CPU-bound, and needs some heavy optimization before becoming network-bound.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Timely dataflow is quite a neat paradigm.&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;A lot of our freedom to optimize things is owed to the flexibility of timely dataflow, which only mandates some very light progress tracking, and that workers respect the promise to send no more data for a timestamp after it has been “notified”. Since this paradigm does not mandate many &lt;em&gt;implementation choices&lt;/em&gt;, we are able to implement things such that all system resources are best utilized at all times.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While our timely dataflow implementation surely isn’t the last word in graph processing, it really is pretty simple, it gets the job done, and it goes pretty fast. You could certainly improve many of the implementation choices we made, or do something totally new, different and better! It really isn’t that hard to write this stuff, and we’d love to see more people doing inventive work rather than doing minor tweaks to existing monolithic stacks.&lt;/p&gt;

&lt;p&gt;Maybe let’s all agree that we can stop talking so much about numbers that are slower than those above, and focus our attention on how to build things that are better.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;a href=&quot;#fn0l&quot; name=&quot;fn0&quot;&gt;&lt;sup&gt;0&lt;/sup&gt;&lt;/a&gt; – GraphX’s default “storage level” for the edge and vertex RDDs is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MEMORY_ONLY&lt;/code&gt; (see &lt;a href=&quot;https://github.com/apache/spark/blob/branch-1.4/graphx/src/main/scala/org/apache/spark/graphx/Graph.scala#L525&quot;&gt;here&lt;/a&gt;), and we use the default settings.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn1l&quot; name=&quot;fn1&quot;&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; – This is because Spark by default uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp&lt;/code&gt; for its local intermediate storage (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPARK_LOCAL_DIRS&lt;/code&gt; environment variable); &lt;a href=&quot;https://spark.apache.org/docs/1.4.0/configuration.html&quot;&gt;as recommended&lt;/a&gt;, this is located on the SSD in our setup. The input data set, however, is on a spinning disk, as the SSD is not large enough to hold it (hence the “low” utilization in the load phase).&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn2l&quot; name=&quot;fn2&quot;&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; – The ASCII edge list representation is actually a tremendously wasteful representation of the graph. For timely dataflow, we use a binary adjacency list representation that stores the same graph in 15 GB (4.2x more efficient). GraphX could, of course, use the same binary representation (but the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GraphLoader&lt;/code&gt; class used by the canonical PageRank example assumes an ASCII edge list). This difference – and any one-off transformation into binary – only affects the one-off startup cost, however, and has no impact on the per-iteration runtime (as by then, the graph is stored as binary integers in memory).&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn3l&quot; name=&quot;fn3&quot;&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; – Our binary representation uses 32-bit unsigned values, which are sufficient for the graphs we study (just). Larger graphs require 64-bit values, but should still remain within 4 GB of working set per machine. Moreover, using 64-bit values potentially increases the amount of network traffic required, which would exacerbate our findings.&lt;sup&gt;&lt;a name=&quot;fn4l&quot; href=&quot;#fn4&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn4l&quot; name=&quot;fn4&quot;&gt;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt; – Frank doesn’t like footnotes. Malte did this to you.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn5l&quot; name=&quot;fn5&quot;&gt;&lt;sup&gt;5&lt;/sup&gt;&lt;/a&gt; – You might wonder why our load phase is much shorter, and why there’s little disk activity: this is because we ran all of our experiments on a hot buffer cache (i.e., having run the computation before to warm it up). GraphX does not seem to benefit much from this, which may be because its large runtime memory consumption ends up evicting most of cached data before the computation completes. However, even paging the data from a cold buffer cache ends up taking only tens of seconds, since each worker only reads less than 1 GB.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn6l&quot; name=&quot;fn6&quot;&gt;&lt;sup&gt;6&lt;/sup&gt;&lt;/a&gt; – The network threads are the reason why we allocated only eight cores per machine to the compute workers; however, as the timeline shows, we actually still have some CPU headroom.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn7l&quot; name=&quot;fn7&quot;&gt;&lt;sup&gt;7&lt;/sup&gt;&lt;/a&gt; – Footnote 7 was removed, but renumbering in HTML is too painful.&lt;sup&gt;&lt;a name=&quot;fn7l&quot; href=&quot;#fn7&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn8l&quot; name=&quot;fn8&quot;&gt;&lt;sup&gt;8&lt;/sup&gt;&lt;/a&gt; – We show numbers from process 0 here, but the numbers for others are similar.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn9l&quot; name=&quot;fn9&quot;&gt;&lt;sup&gt;9&lt;/sup&gt;&lt;/a&gt; – We use our older, slower numbers to avoid re-running everything with the new code; the ratios would only improve with our newer numbers.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn10l&quot; name=&quot;fn10&quot;&gt;&lt;sup&gt;10&lt;/sup&gt;&lt;/a&gt; – Although when Spark workers failed due to running out of memory in our experiments, the PageRank computation did not seem to recover: it spun on the same execution stage for 30 minutes before we terminated the job.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn11l&quot; name=&quot;fn11&quot;&gt;&lt;sup&gt;11&lt;/sup&gt;&lt;/a&gt; – Incidentally, this property is true of the GraphX PageRank too for up to three iterations: for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uk_2007_05&lt;/code&gt; graph, it reads 63 GB of ASCII text, and then shuffles 23.8 GB of data per iteration. This does, however, illustrate a potential pitfall with the use of “input bytes” as a metric: the same input data can be represent at very different levels of efficiency, and may yet still result in the same runtime shuffle size. As we discussed in footnote 3, the input size can be reduced significantly by using a binary representation, without affecting the shuffle size.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn12l&quot; name=&quot;fn12&quot;&gt;&lt;sup&gt;12&lt;/sup&gt;&lt;/a&gt; – We implemented &lt;a href=&quot;https://github.com/frankmcsherry/differential-dataflow/blob/cde516fd27255dac13ce7a76c2dc1e93b6041b00/examples/tpch17.rs#L40-L80&quot;&gt;TPC-H query 17&lt;/a&gt; in timely dataflow to show that it can be done, and it went quite fast when we tested it. However, we haven’t done a detailed evaluation of it.&lt;/p&gt;
</description>
        <pubDate>Fri, 31 Jul 2015 17:00:00 +0000</pubDate>
        <link>http://www.frankmcsherry.org/pagerank/distributed/performance/2015/07/31/pagerank.html</link>
        <guid isPermaLink="true">http://www.frankmcsherry.org/pagerank/distributed/performance/2015/07/31/pagerank.html</guid>
        
        
        <category>pagerank</category>
        
        <category>distributed</category>
        
        <category>performance</category>
        
      </item>
    
      <item>
        <title>The impact of fast networks on graph analytics, part 1</title>
        <description>&lt;p&gt;This is a joint post with &lt;a href=&quot;http://www.cl.cam.ac.uk/~ms705/&quot;&gt;Malte Schwarzkopf&lt;/a&gt;, cross-blogged here and at &lt;a href=&quot;http://www.cl.cam.ac.uk/research/srg/netos/camsas/blog/2015-07-08-timely-pagerank-part1.html&quot;&gt;the CamSaS blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;tl;dr:&lt;/em&gt; A recent NSDI paper argued that data analytics stacks don’t get much faster at tasks like PageRank when given better networking, but this is likely just a property of the stack they evaluated (Spark and GraphX) rather than generally true. A different framework (timely dataflow) goes 6x faster than GraphX on a 1G network, which improves by 3x to 15-17x faster than GraphX on a 10G network.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I spent the past few weeks visiting the &lt;a href=&quot;http://camsas.org&quot;&gt;CamSaS folks&lt;/a&gt; at the &lt;a href=&quot;http://www.cl.cam.ac.uk&quot;&gt;University of Cambridge Computer Lab&lt;/a&gt;. Together, we did some interesting work, which we – &lt;a href=&quot;http://www.cl.cam.ac.uk/~ms705/&quot;&gt;Malte Schwarzkopf&lt;/a&gt; and I – are now going to tell you about.&lt;/p&gt;

&lt;p&gt;Recently, a paper entitled “&lt;a href=&quot;https://www.usenix.org/system/files/conference/nsdi15/nsdi15-paper-ousterhout.pdf&quot;&gt;Making Sense of Performance in Data Analytics Frameworks&lt;/a&gt;” appeared at &lt;a href=&quot;https://www.usenix.org/conference/nsdi15/&quot;&gt;NSDI 2015&lt;/a&gt;. This paper contains some surprising results: in particular, it argues that data analytics stacks are limited more by CPU than they are by network or disk IO. Specifically,&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“Network optimizations can only reduce job completion time by a median of at most 2%. The network is not a bottleneck because much less data is sent over the network than is transferred to and from disk. As a result, network I/O is mostly irrelevant to overall performance, even on 1Gbps networks.”&lt;/em&gt; (§1)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The measurements were done using Spark, but the authors argue that they generalize to other systems. We thought that this was surprising, as it doesn’t match our experience with other data processing systems. In this blog post, we will look into whether these observations do indeed generalize.&lt;/p&gt;

&lt;p&gt;One of the three workloads in the paper is the &lt;a href=&quot;https://amplab.cs.berkeley.edu/benchmark/&quot;&gt;BDBench query set&lt;/a&gt; from Berkeley, which includes a “page-rank-like computation”. Moreover, PageRank also appears as an extra example in the &lt;a href=&quot;http://www.eecs.berkeley.edu/~keo/talks/2015_05_05_NSDI_MakingSense.pdf&quot;&gt;NSDI slide deck&lt;/a&gt; (slide 38-39), used there to illustrate that at most a 10% improvement in job completion time can be had even for a network-intensive workload.&lt;/p&gt;

&lt;p&gt;This was especially surprising to us because of the recent discussion around whether graph computations require distributed data processing systems at all. Several distributed systems &lt;a href=&quot;https://www.usenix.org/conference/hotos15/workshop-program/presentation/mcsherry&quot;&gt;get beat by a simple, single-threaded implementation&lt;/a&gt; on a laptop for various graph computations. The common interpretation is that graph computations are communication-limited; the network gets in the way, and you are better off with one machine if the computation fits.&lt;sup&gt;&lt;a name=&quot;fn0l&quot; href=&quot;#fn0&quot;&gt;0&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;These two positions – &lt;em&gt;(i)&lt;/em&gt; “PageRank cannot be improved by more than 10% via a faster network”, and &lt;em&gt;(ii)&lt;/em&gt; “graph computations such as PageRank are communication-bound, and benefit from very fast local communication” – didn’t jive with each other, and raised a bunch of questions: are these computations CPU-bound, or communication-bound, or is it perhaps more complicated than that? And shouldn’t it still be possible to make multiple computers connected by a decent network go faster than one computer?&lt;/p&gt;

&lt;p&gt;We are going to look in to the performance of distributed PageRank using both GraphX (a graph processing framework on top of Spark) and &lt;a href=&quot;https://github.com/frankmcsherry/timely-dataflow&quot;&gt;timely dataflow&lt;/a&gt;, on a cluster with both 1G and 10G network interfaces.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;tl;dr #1&lt;/strong&gt;: Network speed may not matter with a Spark-based stack, but it &lt;em&gt;does&lt;/em&gt; matter to higher-performance analytics stacks, and for graph processing especially. By moving from a 1G to a 10G network, we see a 2-3x improvement in performance for timely dataflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;tl;dr #2&lt;/strong&gt;: A well-balanced distributed system offers performance improvements even for graph processing problems that fit into a single machine; running things locally isn’t always the best strategy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;tl;dr #3&lt;/strong&gt;: PageRank performance on GraphX is primarily &lt;em&gt;system&lt;/em&gt;-bound. We see a 4x-16x performance increase when using timely dataflow on the same hardware, which suggests that GraphX (and other graph processing systems) leave an alarming amount of performance on the table.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Obviously, take all these conclusions with a grain of salt: we will discuss some caveats and design decisions that some may disagree with. However, our code is &lt;a href=&quot;http://github.com/frankmcsherry/pagerank&quot;&gt;available&lt;/a&gt; alongside &lt;a href=&quot;https://github.com/frankmcsherry/pagerank/blob/master/README.md&quot;&gt;instructions&lt;/a&gt;, so you may try it for yourself!&lt;/p&gt;

&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;p&gt;We set out to understand the bottlenecks in a non-trivial computation that we understand pretty well: PageRank. To be clear, PageRank is not some brilliant computation, but it is more interesting than a distributed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep&lt;/code&gt;. It is a good example of computations that exchange data, aggregate data, and benefit from maintaining indexed data resident in memory. It can also be implemented in different ways, and thus helps tease out how well (or badly) a particular approach fits with a particular system.&lt;/p&gt;

&lt;p&gt;This was also a good opportunity to try out &lt;a href=&quot;https://github.com/frankmcsherry/timely-dataflow&quot;&gt;timely dataflow in Rust&lt;/a&gt;, which is both a port to &lt;a href=&quot;http://www.rust-lang.org&quot;&gt;Rust&lt;/a&gt; and an extension of the timely dataflow paradigm in &lt;a href=&quot;http://research.microsoft.com/Naiad&quot;&gt;Naiad&lt;/a&gt;. Timely dataflow in Rust had only been run on a laptop so far, so this was a good chance to shake some bugs out.&lt;/p&gt;

&lt;p&gt;Additionally, the Cambridge Computer Lab has a new “model data centre”, which is a modern cluster equipped with 10G networking kit, and we wanted to see how fast this Rust code can go. As it turns out, it now moves pretty briskly. You’ll see.&lt;/p&gt;

&lt;h2 id=&quot;pagerank&quot;&gt;PageRank&lt;/h2&gt;

&lt;p&gt;PageRank is a not-wildly-complicated graph computation: the idea is that each vertex starts with some amount of real-valued “rank”, which it repeatedly shares along the directed edges to its neighbors. If one keeps doing this for long enough, the real-valued ranks start to stabilize.&lt;/p&gt;

&lt;p&gt;Here is a straightforward, serial PageRank implementation in Rust:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pagerank&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// mutable per-vertex state&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0f32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dst&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0f32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0f32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// determine vertex degrees&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1f32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// perform 20 iterations&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_iteration&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// prepare src ranks&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vertex&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vertices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vertex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vertex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vertex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;dst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vertex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1f32&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// do the expensive part&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If we look at the code, the computation manipulates per-vertex state (preparing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src[vertex]&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dst[vertex]&lt;/code&gt;, using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deg[vertex]&lt;/code&gt;), and then swings through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;graph&lt;/code&gt;, increasing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dst[y]&lt;/code&gt; by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src[x]&lt;/code&gt; for each graph edge &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(x,y)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The only part of this computation that makes parallelization difficult is updating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dst[y]&lt;/code&gt;: because the edges may link any pairs of vertices, we don’t have an &lt;em&gt;a priori&lt;/em&gt; partitioning of responsibility for these updates. So we’ll have to do that.&lt;/p&gt;

&lt;h2 id=&quot;a-distributed-pagerank&quot;&gt;A Distributed PageRank&lt;/h2&gt;

&lt;p&gt;We want to map this computation across multiple workers (threads, processes, or computers), and fortunately there are several fairly simple ways to do this. The most common approach is to partition responsibility for each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertex&lt;/code&gt; across the workers, so that each worker is responsible for a roughly equal number of vertices.&lt;sup&gt;&lt;a name=&quot;fn1l&quot; href=&quot;#fn1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; We will assign the responsibility for processing vertex &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v&lt;/code&gt; to worker &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v % workers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To partition the computation among workers, we also need to partition the relevant state (inputs and intermediate data) as well. The input to the computation is just a set of edges, so we must make sure that edge &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(x, y)&lt;/code&gt; makes its way to worker &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x % workers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once we have distributed all the edges to the workers, the PageRank computation is just a matter of repeatedly applying the per-vertex updates, which each worker can do independently, and then determining and communicating the updates to the ranks of &lt;em&gt;other&lt;/em&gt; vertices.&lt;/p&gt;

&lt;p&gt;Each worker prepares a message of the form &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(y, src[x])&lt;/code&gt;, indicating the intended update, rather than directly applying &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+=&lt;/code&gt; to the ranks. These updates are then exchanged between the workers, i.e., updates to a vertex &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; are sent to worker &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y % workers&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;implementation-1-send-everything&quot;&gt;Implementation #1: Send everything&lt;/h3&gt;

&lt;p&gt;This will be our first implementation, which we will quickly discard as being hilariously inefficient. For every edge &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(x, y)&lt;/code&gt; at a vertex, we prepare a message &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(y, rank)&lt;/code&gt; indicating interest in performing a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+=&lt;/code&gt; operation for the edge destination &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt;. We exchange all of these messages, sending each to the worker in charge of the recipient vertex.&lt;/p&gt;

&lt;p&gt;The figure below illustrates how this would proceed in a setting with four workers (&lt;em&gt;w&lt;sub&gt;0&lt;/sub&gt;&lt;/em&gt; to &lt;em&gt;w&lt;sub&gt;3&lt;/sub&gt;&lt;/em&gt;) in two processes (&lt;strong&gt;P&lt;sub&gt;0&lt;/sub&gt;&lt;/strong&gt; and &lt;strong&gt;P&lt;sub&gt;1&lt;/sub&gt;&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.frankmcsherry.org/assets/pr-naive.png&quot; alt=&quot;Na&amp;iuml;ve no-aggregation implementation of distributed PageRank.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;However, this approach needs to send some data across the network &lt;em&gt;for every edge in the graph&lt;/em&gt;. That would certainly result in some serious communication,&lt;sup&gt;&lt;a name=&quot;fn2l&quot; href=&quot;#fn2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; and 10G would look pretty good compared to 1G. However, we would only be able to conclude that a fairly naïve implementation is communication-bound. Let’s see if we can do better.&lt;/p&gt;

&lt;h3 id=&quot;implementation-2-worker-level-aggregation&quot;&gt;Implementation #2: Worker-level aggregation&lt;/h3&gt;

&lt;p&gt;Recall that each worker manages multiple vertices. Their edges may well have destinations in common, so each worker can accumulate the messages for each destination, and send just one message for each distinct destination. The worker can accumulate these updates into a hash table, or a large sparse vector (proportional to the number of vertices in the entire graph), but there is a much simpler way.&lt;/p&gt;

&lt;p&gt;Each worker groups its edges by &lt;em&gt;destination&lt;/em&gt;, rather than by source. By so doing, a worker can iterate through its destinations, accumulate its updates from each source rank, and then issue one update for the destination.&lt;/p&gt;

&lt;p&gt;The single-threaded interpretation of this code might look like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// replaces &quot;for (x, y) in graph.edges() { dst[y] += src[x]; }&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;graph_transpose&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Because each worker only has edges from vertices &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; equal modulo &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workers&lt;/code&gt;, each value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x / workers&lt;/code&gt; is distinct. We can compact &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt; by a factor of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workers&lt;/code&gt;, accessing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src[x / workers]&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src[x]&lt;/code&gt;. Compared to the source-grouped approach, the code does random access into a small vector &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt; rather than a large vector &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dst&lt;/code&gt;, which benefits locality.&lt;/p&gt;

&lt;p&gt;The figure below illustrates this: each worker merges updates for the same destination (bold number) into a single update. As a result, this example four-worker setup ends up exchanging only 12 messages, instead of 16 in the naïve version.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.frankmcsherry.org/assets/pr-worker-agg.png&quot; alt=&quot;Worker-level aggregation implementation of distributed PageRank.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Moreover, this approach also produces outputs as it goes, which allows workers to overlap communication with communication: a worker can start telling other workers about its updates right away, using the network at the same time as the rest of the computer.&lt;/p&gt;

&lt;h3 id=&quot;implementation-3-process-level-aggregation&quot;&gt;Implementation #3: Process-level aggregation&lt;/h3&gt;

&lt;p&gt;The worker-level aggregation implementation (#2, above) is our best-performing implementation for a 10G network, but it still sends quite a bit of data.&lt;sup&gt;&lt;a name=&quot;fn3l&quot; href=&quot;#fn3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; To more fairly compare 1G and 10G networks, we can aggregate a bit more aggressively and move aggregation to the &lt;em&gt;process level&lt;/em&gt; to further reduce the amount of data transmitted. This, however, comes at the expense of more computation and more synchronization, since process-level aggregates across multiple workers must await all data from each worker. This reduces the overlapping of computation and communication that we can do, but we send less data overall.&lt;/p&gt;

&lt;p&gt;Again, the figure illustrates this: after we have aggregated updates at the worker level, we also aggregate them at the process level. In the example, this reduces the number of messages from 12 to 9.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.frankmcsherry.org/assets/pr-process-agg.png&quot; alt=&quot;Process-level aggregationo implementation of distributed PageRank.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We could potentially try and make this implementation even smarter, cutting computation and overlapping communication. However, as we will show in part two of our investigation, we cannot expect a 1G network to win out over 10G even with infinitely fast aggregation.&lt;/p&gt;

&lt;h2 id=&quot;evaluation&quot;&gt;Evaluation&lt;/h2&gt;

&lt;p&gt;Let’s see how well our implementations work!&lt;/p&gt;

&lt;p&gt;We evaluate the time to do twenty iterations of PageRank on the CamSaS cluster,&lt;sup&gt;&lt;a name=&quot;fn4l&quot; href=&quot;#fn4&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; using Spark/GraphX 1.4 and our timely dataflow implementation.&lt;/p&gt;

&lt;p&gt;We use two graphs, those used by &lt;a href=&quot;https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-gonzalez.pdf&quot;&gt;the GraphX paper&lt;/a&gt;: a 1.4B edge &lt;a href=&quot;http://an.kaist.ac.kr/traces/WWW2010.html&quot;&gt;graph of Twitter followers&lt;/a&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;twitter_rv&lt;/code&gt;), and a 3.7B edge &lt;a href=&quot;http://law.di.unimi.it/webdata/uk-2007-05/&quot;&gt;graph of web links&lt;/a&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uk_2007_05&lt;/code&gt;). The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uk_2007_05&lt;/code&gt; graph is also the one used for the results &lt;a href=&quot;https://twitter.com/kayousterhout/status/596034007063638016&quot;&gt;shown in the NSDI slide deck&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;some-baseline-measurements&quot;&gt;Some baseline measurements&lt;/h3&gt;

&lt;p&gt;Before we start, let’s think about existing baselines that should give some context.&lt;/p&gt;

&lt;p&gt;Below, we show previously reported measurements from Spark and GraphX, as well as the runtime of GraphX on our cluster, and the runtime of two single-threaded implementations (from &lt;a href=&quot;https://www.usenix.org/conference/hotos15/workshop-program/presentation/mcsherry&quot;&gt;the COST paper&lt;/a&gt;).&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
&lt;img src=&quot;http://www.frankmcsherry.org/plots/pr-baselines.png&quot; /&gt;
&lt;/p&gt;

&lt;center&gt;
&lt;table border=&quot;1&quot; style=&quot;width:75%&quot;&gt;
&lt;tr&gt; &lt;th&gt;System&lt;/th&gt; &lt;th&gt;source&lt;/th&gt; &lt;th&gt;cores&lt;/th&gt; &lt;th&gt;twitter_rv&lt;/th&gt; &lt;th&gt;uk_2007_05&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Spark&lt;/td&gt; &lt;td&gt;&lt;a href=&quot;https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-gonzalez.pdf&quot;&gt;GraphX paper&lt;/a&gt;&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;16x8&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;857s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1759s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;GraphX&lt;/td&gt; &lt;td&gt;&lt;a href=&quot;https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-gonzalez.pdf&quot;&gt;GraphX paper&lt;/a&gt;&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;16x8&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;419s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;462s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;GraphX&lt;/td&gt; &lt;td&gt;measured on our cluster&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;16x8&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;334s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;362s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Single thread (simpler)&lt;/td&gt; &lt;td&gt;&lt;a href=&quot;https://www.usenix.org/conference/hotos15/workshop-program/presentation/mcsherry&quot;&gt;COST paper&lt;/a&gt;&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;300s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;651s&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Single thread (smarter)&lt;/td&gt; &lt;td&gt;&lt;a href=&quot;https://www.usenix.org/conference/hotos15/workshop-program/presentation/mcsherry&quot;&gt;COST paper&lt;/a&gt;&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;110s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;256s&lt;/td&gt; &lt;/tr&gt;
&lt;caption&gt;Twenty pagerank iterations, baseline measurements.&lt;/caption&gt;
&lt;/table&gt;
&lt;/center&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;So far, nothing new: the measurement on our cluster confirms that the numbers from the GraphX paper can be reproduced,&lt;sup&gt;&lt;a name=&quot;fn5l&quot; href=&quot;#fn5&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;. Moreover, the laptop performs pretty well even though it’s using only one CPU core, rather than 128. On the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;twitter_rv&lt;/code&gt; graph, the single-threaded implementation &lt;em&gt;always&lt;/em&gt; beats the distributed implementations, and on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uk_2007_05&lt;/code&gt; graph, the simple single-threaded implementation is only ~50% slower than GraphX (at 128x less resources!). The smarter single-threaded implementation with a Hilbert space-filling curve graph layout always beats the distributed systems by between 30% and 3x.&lt;/p&gt;

&lt;p&gt;Is that bad news for distributed graph processing generally? Let’s see.&lt;/p&gt;

&lt;h3 id=&quot;a-timely-dataflow-implementation&quot;&gt;A timely dataflow implementation&lt;/h3&gt;

&lt;p&gt;Let’s take our data-parallel implementation out for a spin. We’ll start with just a single machine, and move from one core to multiple cores. We measure the total elapsed time (first graph), and the average per-iteration time of the last ten iterations (second graph). For reference, we also show the results for GraphX, and the simple single-threaded implementations (as horizontal bars).&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
&lt;img src=&quot;http://www.frankmcsherry.org/plots/pr-multithreaded.png&quot; /&gt;
&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
&lt;img src=&quot;http://www.frankmcsherry.org/plots/pr-multithreaded-periter.png&quot; /&gt;
&lt;/p&gt;

&lt;!-- ![End-to-end runtimes for multi-threaded timely dataflow PageRank](http://www.frankmcsherry.org/plots/pr-multithreaded.png)
![Per-iteration runtimes for multi-threaded timely dataflow PageRank](http://www.frankmcsherry.org/plots/pr-multithreaded-periter.png) --&gt;

&lt;center&gt;
&lt;table border=&quot;1&quot; style=&quot;width:75%&quot;&gt;
&lt;tr&gt; &lt;th&gt;System&lt;/th&gt; &lt;th&gt;cores&lt;/th&gt; &lt;th&gt;twitter_rv&lt;/th&gt; &lt;th&gt;uk_2007_05&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;350.7s (11.33s)&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;442.2s (8.90s)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;2&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;196.5s (6.39s)&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;297.3s (5.67s)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;4&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;182.4s (6.12s)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;192.0s (3.78s)&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;8&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;107.6s (3.70s)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;137.1s (3.29s)&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;12&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;95.0s (3.32s)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;114.5s (2.65s)&lt;/td&gt; &lt;/tr&gt;
&lt;caption&gt;Twenty pagerank iterations on one machine, multiple threads.&lt;/caption&gt;
&lt;/table&gt;
&lt;/center&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Well, this is good: with one thread, we still perform similarly as GraphX at 128, and we outperform the simple single-threaded measurement with just two threads&lt;sup&gt;&lt;a name=&quot;fn6l&quot; href=&quot;#fn6&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;, and we outperform the smart single-threaded measurement with eight threads.&lt;sup&gt;&lt;a name=&quot;fn7l&quot; href=&quot;#fn7&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;This suggests that parallelism inside a single machine, at least, helps make this computation go faster. Maybe multiple computers, with all that networking in between, end up being slower though?&lt;/p&gt;

&lt;h3 id=&quot;multiple-computers-1g-vs-10g&quot;&gt;Multiple computers: 1G vs. 10G&lt;/h3&gt;

&lt;p&gt;Let’s now see what happens when we distribute the computation over multiple computers. Here we have the choice of using either a 1G network interface or a 10G network interface; we will measure both, revealing the performance gains that 10G brings (if any).&lt;/p&gt;

&lt;p&gt;In addition to the worker-level aggregation implementation from above, we also include measurements for process-level aggregation on a 1G network (labelled “1G+”). This is more representative of a 1G-optimized implementation, and matches what GraphX does.&lt;/p&gt;

&lt;p&gt;For each configuration, we again report the elapsed time to perform twenty iterations (first graph), and the average of the final ten iterations (second graph). Because GraphX and our implementation have different one-off start-up costs, the average iteration time when the computation is running is probably the fairest metric for comparison.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
&lt;img src=&quot;http://www.frankmcsherry.org/plots/pr-distributed.png&quot; /&gt;
&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
&lt;img src=&quot;http://www.frankmcsherry.org/plots/pr-distributed-periter.png&quot; /&gt;
&lt;/p&gt;

&lt;!-- ![End-to-end runtimes for distributed timely dataflow PageRank](http://www.frankmcsherry.org/plots/pr-distributed.png)
![Per-iteration runtimes for distributed timely dataflow PageRank](http://www.frankmcsherry.org/plots/pr-distributed-periter.png) --&gt;

&lt;center&gt;
&lt;table border=&quot;1&quot; style=&quot;width:75%&quot;&gt;
&lt;tr&gt; &lt;th&gt;System&lt;/th&gt;
&lt;th&gt;cores&lt;/th&gt;
&lt;th&gt;1G&lt;/th&gt;
&lt;th&gt;1G+&lt;/th&gt;
&lt;th&gt;10G&lt;/th&gt;
&lt;th colspan=&quot;2&quot;&gt;10G speedup over 1G+&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt; &lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;total&lt;/th&gt;
&lt;th&gt;per-iteration&lt;/th&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1x8&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;107.6s (3.70s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;107.6s (3.70s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;107.6s (3.70s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&amp;ndash;&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&amp;ndash;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;2x8&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;115.2s (4.66s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;89.0s (3.51s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;65.6s (2.34s)&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;1.36x&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;1.50x&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;4x8&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;149.4s (6.77s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;80.9s (3.33s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;40.6s (1.49s)&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;1.99x&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;2.23x&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;8x8&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;145.4s (6.60s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;66.5s (2.86s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;27.6s (1.05s)&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;2.41x&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;2.72x&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;16x8&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;169.3s (7.51s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;51.8s (2.30s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;19.3s (0.75s)&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;2.68x&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;3.07x&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;GraphX&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;16x8&lt;/td&gt; &lt;td&gt;&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;354.8s (13.4s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;333.7s (12.2s)&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;1.06x&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;1.10x&lt;/td&gt; &lt;/tr&gt;
&lt;caption&gt;Elapsed and (per-iteration) times for twenty PageRank iterations on multiple machines using the &lt;code&gt;twitter_rv graph&lt;/code&gt;, comparing 1G and 10G networks&lt;/caption&gt;
&lt;/table&gt;
&lt;/center&gt;

&lt;p&gt;&lt;/p&gt;

&lt;center&gt;
&lt;table border=&quot;1&quot; style=&quot;width:75%&quot;&gt;
&lt;tr&gt; &lt;th&gt;System&lt;/th&gt;
&lt;th&gt;cores&lt;/th&gt;
&lt;th&gt;1G&lt;/th&gt;
&lt;th&gt;1G+&lt;/th&gt;
&lt;th&gt;10G&lt;/th&gt;
&lt;th colspan=&quot;2&quot;&gt;10G speedup over 1G+&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt; &lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;total&lt;/th&gt;
&lt;th&gt;per-iteration&lt;/th&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;1x8&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;137.1s (3.29s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;137.1s (3.29s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;137.1s (3.29s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&amp;ndash;&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&amp;ndash;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;2x8&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;173.3s (6.82s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;135.8s (4.82s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;80.7s (2.31s)&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;1.68x&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;2.09x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;4x8&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;231.9s (9.06s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;119.1s (4.67s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;51.4s (1.54s)&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;2.32x&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;3.03x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;8x8&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;196.4s (8.87s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;80.1s (3.18s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;34.1s (1.07s)&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;2.35x&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;2.97x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Timely dataflow&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;16x8&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;231.2 (10.25s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;53.9s (2.13s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;23.7s (0.76s)&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;2.27x&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;2.80x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;GraphX&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;8x8&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;666.8s (14.40s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;682.6s (15.00s)&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;0.98x&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;0.96x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;GraphX&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;16x8&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;361.8s (9.30s)&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;357.9s (8.30s)&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;1.01x&lt;/td&gt;
&lt;td align=&quot;right&quot; style=&quot;font-weight: bold;&quot;&gt;1.12x&lt;/td&gt;&lt;/tr&gt;
&lt;caption&gt;Elapsed and (per-iteration) times for twenty PageRank iterations on multiple machines using the &lt;code&gt;uk_2007_05&lt;/code&gt; graph, comparing 1G and 10G networks&lt;/caption&gt;
&lt;/table&gt;
&lt;/center&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Phew, that’s a lot of data! There are a few important observations that we can draw from them, though:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Making the network faster &lt;strong&gt;does not&lt;/strong&gt; improve GraphX’s performance much (at most 10-12%), which confirms the observations of the NSDI paper.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Making the network faster &lt;strong&gt;does&lt;/strong&gt; improve timely dataflow’s performance (by 2-3x), which limits the generality of the conclusions of the NSDI paper.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Communication cost &lt;strong&gt;does not&lt;/strong&gt; dominate GraphX’s performance, but it &lt;strong&gt;does&lt;/strong&gt; dominate the timely dataflow performance, meaning graph computations can be communication-bound on a good implementation.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Nevertheless, performance strictly improves as we add machines, invalidating the claim that distributed systems will not be faster than a single machine when running a communication-bound graph computation on a graph that fits into the machine.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Worker-level aggregation has the &lt;em&gt;opposite&lt;/em&gt; impact on a 10G network and a 1G network: on 10G, it yields improvements, but on 1G, it makes things slower. Moreover, at 10G, worker-level aggregation outperforms process-level aggregation (not shown in graphs), despite sending more data.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;GraphX &lt;em&gt;scales&lt;/em&gt; well from eight to sixteen machines on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uk_2007_05&lt;/code&gt; graph, but &lt;a href=&quot;https://www.usenix.org/system/files/conference/hotos15/hotos15-paper-mcsherry.pdf&quot;&gt;as the COST paper has suggests&lt;/a&gt;, this scalability could just come from parallelizable overheads.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Timely dataflow on a 10G network outperforms GraphX &lt;strong&gt;by up to 16x&lt;/strong&gt; on the per-iteration runtime using 16 machines (16.27x for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;twitter_rv&lt;/code&gt;, and 10.92x for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uk_2007_05&lt;/code&gt;).&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those results allow us to answer our questions from the beginning, as we will see now.&lt;/p&gt;

&lt;h2 id=&quot;conclusion-for-the-moment&quot;&gt;Conclusion (for the moment)&lt;/h2&gt;

&lt;p&gt;This concludes the first part of our investigation. Apparently both 10G networks and distributed data processing systems &lt;em&gt;are&lt;/em&gt; useful for PageRank. Phew. Existential crisis averted!&lt;/p&gt;

&lt;p&gt;As we have seen, the three implementations (GraphX and the two timely dataflow ones) have &lt;em&gt;different bottleneck resources&lt;/em&gt;. GraphX does more compute and is CPU-bound even on the 1G network, whereas the leaner timely dataflow implementations become CPU-bound only on the 10G network. Drawing conclusions about the scalability or limitations of either system based on the performance of the other is likely misguided.&lt;/p&gt;

&lt;p&gt;Fast 10G networks &lt;em&gt;do&lt;/em&gt; help reduce reduce the runtime of parallel computations by significantly more than 2-10%: we’ve seen &lt;strong&gt;speedups up to 3x going from 1G to 10G&lt;/strong&gt;. However, the structure of the computation and the implementation of the data processing system must be suited to fast networks, and different strategies are appropriate for 1G and 10G networks. For the latter, being less clever and communicating more sometimes actually helps.&lt;/p&gt;

&lt;p&gt;Distributed data processing makes sense even for graph computations where the graph fits into one machine. When computation and communication are overlapped sufficiently, &lt;strong&gt;using multiple machines yields speedups up to 5x&lt;/strong&gt; (e.g., on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;twitter_rv&lt;/code&gt;, 1x8 vs. 16x8). Running everything locally isn’t necessarily faster.&lt;/p&gt;

&lt;p&gt;Moreover, we’ve shown that we can make PageRank run &lt;strong&gt;16x faster per iteration using distributed timely dataflow&lt;/strong&gt; than using GraphX (from 12.2s to 0.75s per iteration). This tells us something about how much scope for improvement there is even over numbers currently considered state-of-the-art in research!&lt;/p&gt;

&lt;p&gt;In part two, we will follow up with a more in-depth analysis of &lt;em&gt;why&lt;/em&gt; timely dataflow is so much faster than GraphX. We will look at their respective resource usage over the runtime of the computation, and investigate via a lower-bound analysis whether it is at all possible to make a 1G network outperform a 10G one by doing cunning aggregation (spoiler: it’s not!).&lt;/p&gt;

&lt;p&gt;Come back for more in a few days!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;a href=&quot;#fn0l&quot; name=&quot;fn0&quot;&gt;&lt;sup&gt;0&lt;/sup&gt;&lt;/a&gt; – This view is even common with those who build distributed graph processing systems: a few months ago, Reynold Xin (GraphX co-author, now at Databricks) &lt;a href=&quot;https://twitter.com/rxin/status/556365235032821760&quot;&gt;indicated that&lt;/a&gt;, for communication-bound graph workloads that fit into a machine, there is no point in going distributed. That’s a somewhat surprising position: surely, we would expect a distributed graph processing system (such as GraphX) and a decent network to outperform a laptop on graph problems? Maybe Reynold is saying that “systems like GraphX are designed for graphs that are too large to fit into a single machine; there is no benefit to using them on ones that fit into a machine”. That’s a reasonable view to take, although the fact that all known GraphX results are on such graphs suggests that &lt;a href=&quot;http://www.frankmcsherry.org/graph/scalability/cost/2015/02/04/COST2.html&quot;&gt;systems need to be evaluated using larger graphs&lt;/a&gt;. In either case, we later show that a distributed system does outperform a single machine even for graphs that do fit into the machine.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn1l&quot; name=&quot;fn1&quot;&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; – Some graph processing systems (e.g., &lt;a href=&quot;https://www.usenix.org/system/files/conference/osdi12/osdi12-final-167.pdf&quot;&gt;PowerGraph&lt;/a&gt;) use far more complex and cunning schemes in order to balance the amount of work each worker needs to do. In practice, this doesn’t seem necessary, as we will see.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn2l&quot; name=&quot;fn2&quot;&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; – If we represent the edge destination by a 32-bit integer and the update value by a single-precision float, we’d end up with each machine sending and receiving ~15 GB of messages per iteration when processing the widely-used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;twitter_rv&lt;/code&gt; graph. Clearly not a good plan.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn3l&quot; name=&quot;fn3&quot;&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; – About 3 GB per iteration for each machine on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;twitter_rv&lt;/code&gt; graph.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn4l&quot; name=&quot;fn4&quot;&gt;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt; – The CamSaS cluster consists of 16 machines, each equipped with an Intel Xeon E5-2430Lv2 CPU (12 hyperthreads, 2.4 GHz), 64 GB DDR3-1600 RAM, a Micron P400m-MTF SSD for the root file system and a Toshiba MG03ACA1 7,200rpm harddrive for the data, and both an onboard 1G NIC and an Intel X520 10G NIC. We run the default Ubuntu 14.04 (trusty) Linux kernel 3.13.0-24, and the harddisk is partitioned using the ext4 file system. We use the standard Spark 1.4 distribution, which includes GraphX.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn5l&quot; name=&quot;fn5&quot;&gt;&lt;sup&gt;5&lt;/sup&gt;&lt;/a&gt; – We suspect that our results are slightly better than those in the GraphX paper because we use dedicated machines (as opposed to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;m2.4xlarge&lt;/code&gt; EC2 instances).&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn6l&quot; name=&quot;fn6&quot;&gt;&lt;sup&gt;6&lt;/sup&gt;&lt;/a&gt; – You might wonder why this single-threaded measurement is slower than the simple single-threaded one from before: it’s because structuring the program as dataflow adds some overhead.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#fn7l&quot; name=&quot;fn7&quot;&gt;&lt;sup&gt;7&lt;/sup&gt;&lt;/a&gt; – The smarter single-threaded implementation uses an “exotic” graph layout (based on a Hilbert space-filling curve); although this technique could be applied to timely dataflow, we won’t use it in the distributed implementation in this post.&lt;/p&gt;
</description>
        <pubDate>Wed, 08 Jul 2015 17:00:00 +0000</pubDate>
        <link>http://www.frankmcsherry.org/pagerank/distributed/performance/2015/07/08/pagerank.html</link>
        <guid isPermaLink="true">http://www.frankmcsherry.org/pagerank/distributed/performance/2015/07/08/pagerank.html</guid>
        
        
        <category>pagerank</category>
        
        <category>distributed</category>
        
        <category>performance</category>
        
      </item>
    
      <item>
        <title>Differential graph computation</title>
        <description>&lt;p&gt;I recently received an email asking if &lt;a href=&quot;http://research.microsoft.com/apps/pubs/default.aspx?id=176693&quot;&gt;differential dataflow&lt;/a&gt; might be a good fit for &lt;a href=&quot;http://en.wikipedia.org/wiki/Breadth-first_search&quot;&gt;breadth-first search&lt;/a&gt; in evolving graphs. I think it is a great fit, and I pushed a &lt;a href=&quot;https://github.com/frankmcsherry/differential-dataflow/blob/master/examples/bfs.rs&quot;&gt;breadth-first search example&lt;/a&gt; to the &lt;a href=&quot;https://github.com/frankmcsherry/differential-dataflow&quot;&gt;differential dataflow repository&lt;/a&gt; in my delight and satisfaction.&lt;/p&gt;

&lt;p&gt;Around the same time I got a different email, asking if the recent &lt;a href=&quot;https://www.usenix.org/system/files/conference/nsdi15/nsdi15-paper-iyer.pdf&quot;&gt;CellIQ paper&lt;/a&gt; from NSDI, which does incremental connected components, is fundamentally new or might their “GStream” API be easily cast in something like &lt;a href=&quot;https://github.com/frankmcsherry/timely-dataflow&quot;&gt;timely dataflow&lt;/a&gt; or &lt;a href=&quot;https://github.com/frankmcsherry/differential-dataflow&quot;&gt;differential dataflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, I suspect this person was just a disgruntled trouble-maker. The GStream API (Listing 2) is clearly covered by differential dataflow, which allows arbitrary incremental updates to iterative computations containing operators like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group_by&lt;/code&gt;, enough for many data-parallel graph computations. Whether it can be done is different from whether it can be done &lt;em&gt;well&lt;/em&gt;, though.&lt;/p&gt;

&lt;p&gt;The CellIQ paper is one step ahead of us, and observes that although graph computation can be expressed &lt;em&gt;“using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group_by&lt;/code&gt;, this can be very inefficient”&lt;/em&gt;, because &lt;em&gt;“join operators are not optimized for graph processing and can be very expensive.”&lt;/em&gt; They then cite some papers which, perhaps coincidentally, try to sell you their special-purpose graph processors.&lt;/p&gt;

&lt;p&gt;These authors are not wrong: it &lt;em&gt;can&lt;/em&gt; be very inefficient and expensive, especially if you use a previous-generation platform not designed from the ground up to handle streaming iterative computation.&lt;/p&gt;

&lt;p&gt;Let’s see what happens if you build on something better, using just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group_by&lt;/code&gt;, of course.&lt;/p&gt;

&lt;h2 id=&quot;differential-dataflow&quot;&gt;Differential dataflow&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://www.cidrdb.org/cidr2013/Papers/CIDR13_Paper111.pdf&quot;&gt;Differential dataflow&lt;/a&gt; is a way of implementing data-parallel dataflow computations so that work is done only in response to changes in the data. If you change one input to your computation, you would prefer not to re-evaluate the entire computation. Rather, changes to the input produces some changes in the output of your first dataflow operator, which are then changes to the inputs of subsequent operators. Eventually these changes either evaporate or spill out the end of your dataflow as changed outputs.&lt;/p&gt;

&lt;p&gt;Chasing these changes and doing &lt;em&gt;only&lt;/em&gt; this work is what differential dataflow does well.&lt;/p&gt;

&lt;p&gt;Importantly—and as far as we are aware, uniquely—differential dataflow also maintains this property in the context of iterative dataflow computations.&lt;/p&gt;

&lt;p&gt;Going through the math for this usually results in long-lasting head-aches for the readers, so I’ll summarize instead. As changes flow around the dataflow, they “happen” at various logical times. These times reflect the epoch of streaming input data, and iteration counters for any loops they may be contained in. Rather than eagerly aggregate these changes, we leave them disaggregated, allowing future computation to use arbitrary subsets of the changes. Although we leave ourselves more work to do for each change, we end up with an orders-of-magnitude reduction in the numbers of changes.&lt;/p&gt;

&lt;p&gt;We might be getting ahead of ourselves with detail. Let’s step back and set some context.&lt;/p&gt;

&lt;h2 id=&quot;data-parallel-dataflow&quot;&gt;Data-parallel dataflow&lt;/h2&gt;

&lt;p&gt;The big-data-scenti have caught up with the idea that we should use high-level programming constructs to describe data-parallel computations. Two operators I’ll detail are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group_by&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt; : I’m going to over-simplify some of the operators in the coming sections. I recommend checking out &lt;a href=&quot;https://github.com/frankmcsherry/differential-dataflow/tree/master/src/operators&quot;&gt;the code&lt;/a&gt; for the real details, some of which are smart and some of which are gross.&lt;/p&gt;

&lt;h4 id=&quot;group-by&quot;&gt;Group By&lt;/h4&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group_by&lt;/code&gt; operator acts on a collection of records. I’ll imagine a type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream&amp;lt;D&amp;gt;&lt;/code&gt; to indicate a not-yet-fully-formed data collection of records of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;D&lt;/code&gt;. In this setting, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group_by&lt;/code&gt; has the following sort of signature:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fn group_by&amp;lt;K, V, F, R&amp;gt;(inp: Stream&amp;lt;(K,V)&amp;gt;, red: F) -&amp;gt; Stream&amp;lt;R&amp;gt;
    where F: Fn(&amp;amp;K, &amp;amp;[V], &amp;amp;mut Vec&amp;lt;R&amp;gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For those of you familar with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reduce&lt;/code&gt;, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group_by&lt;/code&gt; function is basically like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reduce&lt;/code&gt;. The operator acts on a stream of key-value pairs, groups incoming pairs by their key value, and applies the reduction function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;red&lt;/code&gt; to each non-empty pair of key and matching records.&lt;/p&gt;

&lt;p&gt;They way I’ve done this, it gets a mutable vector of elements &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;R&lt;/code&gt; and it is supposed to write into them. I’m open to other suggestions. It turns out that writing tasteful interfaces to performant code is hard.&lt;/p&gt;

&lt;h4 id=&quot;join&quot;&gt;Join&lt;/h4&gt;

&lt;p&gt;The second important (for this post) data-parallel operator is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join&lt;/code&gt;, a binary operator matching pairs of input records with the same key.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fn join&amp;lt;K, V1, V2&amp;gt;(in1: Stream&amp;lt;(K,V1)&amp;gt;, in2: Stream&amp;lt;(K,V2)&amp;gt;) -&amp;gt; Stream&amp;lt;(K,V1,V2)&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join&lt;/code&gt; operator is &lt;em&gt;bi-linear&lt;/em&gt;, which means it has some nice mathematical properties (roughly: each input can be processed record-at-a-time, oblivious to other records from the same input).&lt;/p&gt;

&lt;h4 id=&quot;other-operators&quot;&gt;Other operators&lt;/h4&gt;

&lt;p&gt;So I lied. We are going to add some more simple operators such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filter&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;concat&lt;/code&gt;. These operators do not involve any data movement or grouping, and can always be performed &lt;em&gt;in situ&lt;/em&gt;. They are also &lt;em&gt;linear&lt;/em&gt;, and consequently &lt;a href=&quot;https://users.soe.ucsc.edu/~abadi/Papers/differential_revision-app.pdf&quot;&gt;do not need any special differential dataflow implementation&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;prelude-bread-first-search&quot;&gt;Prelude: Bread-first search&lt;/h2&gt;

&lt;p&gt;Let’s use what we have so far start to build up an example of breadth-first search.&lt;/p&gt;

&lt;p&gt;Our approach will be to maintain a stream &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dists&lt;/code&gt; of pairs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(node, dist)&lt;/code&gt;, indicating that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node&lt;/code&gt; can be reached using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dist&lt;/code&gt; edges. From a supplied set of edges &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edges&lt;/code&gt;, we want to expand this set by one hop: each node provides the distance in which it can be reached to each of its neighbors, who then retain the smallest distance they’ve seen. I’m going to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u32&lt;/code&gt;s for node identifiers. Sorry.&lt;/p&gt;

&lt;p&gt;Our code follows this intuition, by using a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join&lt;/code&gt; to provide the per-node information to neighbors. Under the covers, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join&lt;/code&gt; maintains a list of key-value pairs as a trie: a flat list of keys, each with an offset into a flat list of values. Ignoring than the fact that these lists might change, this is the same as the &lt;a href=&quot;http://en.wikipedia.org/wiki/Adjacency_list&quot;&gt;adjacency list&lt;/a&gt; representation of a graph.&lt;/p&gt;

&lt;p&gt;The output of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join&lt;/code&gt; then needs to go through a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt; to increase the proposed distances by one.&lt;/p&gt;

&lt;p&gt;To the proposals produced by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join&lt;/code&gt; we &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;concat&lt;/code&gt; the original distances. You might want one of those.&lt;/p&gt;

&lt;p&gt;Finally, we use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group_by&lt;/code&gt; to collect the distance proposals. Here I’m going to cheat and use the fact that my implementation of differential dataflow sorts records in each group (an important part of “consolidation”; details later). To find the least distance, we can just pop the first distance in the sorted order; we are guaranteed one exists, because we wouldn’t have been called if there was no data.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;dists&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.join_u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;nf&quot;&gt;.map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
         &lt;span class=&quot;nf&quot;&gt;.concat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;nf&quot;&gt;.group_by_u&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You might have noticed that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join_u&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group_by_u&lt;/code&gt; each have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_u&lt;/code&gt; suffix. Rust doesn’t think highly of operator overloading, and so you need to have distinct method names if you would like to have distinct implementations. In this case, we are using an implementation that knows we have unsigned integer keys, and can use a more efficient index (a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt; rather than a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HashMap&lt;/code&gt;).&lt;/p&gt;

&lt;h2 id=&quot;iterative-data-parallel-dataflow&quot;&gt;Iterative data-parallel dataflow&lt;/h2&gt;

&lt;p&gt;We need one more operator. A different kind of operator. A &lt;em&gt;higher-order&lt;/em&gt; operator: we need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iterate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iterate&lt;/code&gt; operator has a simple signature (these higher-order things always do, don’t they?)&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fn iterate&amp;lt;D, F: FnOnce(Stream&amp;lt;D&amp;gt;)-&amp;gt;Stream&amp;lt;D&amp;gt;&amp;gt;(s: Stream&amp;lt;D&amp;gt;, f: F) -&amp;gt; Stream&amp;lt;D&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The operator takes a stream, and a description of how to act on the stream. Its output is the result of acting on the input stream in the supplied manner a very large number of times.&lt;/p&gt;

&lt;p&gt;Of course, since &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iterate&lt;/code&gt; just takes a description of what to do repeatedly, it doesn’t define any data movement or new computation itself. In actual fact, we will need a few helpers behind the scenes to tell just when we can stop computing, but we don’t need to detail them to understand what &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iterate&lt;/code&gt; is supposed to produce.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iterate&lt;/code&gt; operator is &lt;strong&gt;incredibly important&lt;/strong&gt; for efficient computation. It explains to your computer that you plan to do the same thing multiple times, and that it might be a good idea to spend a bit of effort to take advantage of that. Although you can do graph computation by repeatedly invoking &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group_by&lt;/code&gt;, you are doing your computer a disservice if you treat the invocations as unrelated.&lt;/p&gt;

&lt;h2 id=&quot;breadth-first-search&quot;&gt;Breadth first search&lt;/h2&gt;

&lt;p&gt;The combination of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iterate&lt;/code&gt; with our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;step&lt;/code&gt; function produces a breadth-first search implementation.&lt;/p&gt;

&lt;p&gt;The only non-obvious step is that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edges&lt;/code&gt; stream needs to be brought in to the body of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iterate&lt;/code&gt;. This is not easy to do automatically (at least, not at the moment), and so we explicitly do this with the help of a stream that is inside the iterative context: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dists&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// iteratively expand distances&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;roots&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.iterate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dists&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dists&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.enter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In the fullness of time it may make sense for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iterate&lt;/code&gt; to take a function of two parameters, a stream like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dists&lt;/code&gt; and some sort of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;context&lt;/code&gt; object that does the things we had to ask &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dists&lt;/code&gt; to do for us.&lt;/p&gt;

&lt;p&gt;That doesn’t really look all that complicated, does it? Now, we don’t yet know very much about what differential dataflow is doing under the covers, but that doesn’t have to stop us from grabbing the code and running it.&lt;/p&gt;

&lt;h2 id=&quot;all-systems-go&quot;&gt;ALL SYSTEMS GO!!!&lt;/h2&gt;

&lt;p&gt;I turned this on with 500M edges and 100M nodes, using nodes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt; as roots (so the distances are the distances from any of these three nodes). I then asked it to update (randomly re-wire) one edge per second, so that I could see how long updates take. If you do the same thing, you’ll see some output that looks like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;520.00s: observed at (Root, 520): 2 changes; elapsed: 0.004067797010065988s
522.00s: observed at (Root, 522): 2 changes; elapsed: 0.003325369005324319s
524.00s: observed at (Root, 524): 2 changes; elapsed: 0.002495013002771884s
526.00s: observed at (Root, 526): 2 changes; elapsed: 0.0021344090055208653s
527.00s: observed at (Root, 527): 2 changes; elapsed: 0.00011885000276379287s
532.00s: observed at (Root, 532): 2 changes; elapsed: 0.0019779420108534396s
541.00s: observed at (Root, 541): 2 changes; elapsed: 0.0032094839989440516s
545.00s: observed at (Root, 545): 3 changes; elapsed: 0.0020695959974545985s
548.00s: observed at (Root, 548): 2 changes; elapsed: 0.0017733170097926632s
553.00s: observed at (Root, 553): 2 changes; elapsed: 0.001985581999178976s
577.00s: observed at (Root, 577): 1 changes; elapsed: 0.0019018270104425028s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This tells us a few things: first, it takes about 500s to get the computation up and running. That is &lt;em&gt;a lot&lt;/em&gt; of time to do BFS. We are spending time laying out the graph in a nice fashion, getting reading for incremental updates, all that stuff. I’m not going to try and defend this as good performance.&lt;/p&gt;

&lt;p&gt;Did I mention this was single-threaded? It goes faster with more cores and computers. I’m pretty sure.&lt;/p&gt;

&lt;p&gt;Once we start going we see that each incremental update (one random edge removal and addition) happens pretty quickly (the numbers over on the right). Most of these are single-digit milliseconds.&lt;/p&gt;

&lt;p&gt;You might also notice that we miss some epochs; the node distances don’t always change, and in those cases we don’t print anything. They also go fast, but we don’t want to pat ourselves on the back just because the data didn’t change.&lt;/p&gt;

&lt;h2 id=&quot;graph-connectivity&quot;&gt;Graph connectivity&lt;/h2&gt;

&lt;p&gt;Way back when, someone said doing graph computation using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group_by&lt;/code&gt; was inefficient.&lt;/p&gt;

&lt;p&gt;Let’s return to that, shall we?&lt;/p&gt;

&lt;p&gt;Graph connectivity is usually done with an algorithm very similar to BFS. Rather than propagate a distance that we increase by one, we propagate a set of labels (the same set as the node identifiers), and each node keeps the smallest label it sees. The only difference from BFS is that we start each node with its own label, rather than a few roots with distance zero (and we don’t add one to anything).&lt;/p&gt;

&lt;p&gt;It turns out that this algorithm is horrible. We should all stop using it. Even graph-processing experts.&lt;/p&gt;

&lt;p&gt;The algorithm is dumb because we start circulating all labels, even very large labels that will eventually get dominated by smaller labels. This is a waste of everyone’s time.&lt;/p&gt;

&lt;h3 id=&quot;improving-with-prioritization&quot;&gt;Improving with prioritization&lt;/h3&gt;

&lt;p&gt;A better algorithm, from at least as far back as the &lt;a href=&quot;http://rio.ecs.umass.edu/mnilpub/papers/socc11-zhang.pdf&quot;&gt;PrIter&lt;/a&gt; paper, is to start circulating small labels first, and move on to larger labels only once nodes have gotten a chance to see if they should bother proposing their name as a label. If they see a better label in the meantime, they don’t.&lt;/p&gt;

&lt;p&gt;You can do this in differential dataflow with a fancier version of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enter&lt;/code&gt;, that method that brought our set of edges in to the loop. We have an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enter_at&lt;/code&gt; method that lets you specify a function from record to iteration number, and the record first gets added to resulting collection at that iteration number. This lets us write clever things like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;let nodes = inner.context().enter_at(&amp;amp;nodes,
    |r| 256 * (64 - r.as_usize().leading_zeros() as u32));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What a mess. What this is saying is that we should introduce a record (here a node identifier) at an iteration equal to 256 times its base 2 logarithm. Why that number? It turns out it is a (provably) good trade-off between giving labels enough time to propagate and not using too many distinct batches. Each batch gets a 256 iteration head-start on the next batch, which grow in size geometrically.&lt;/p&gt;

&lt;p&gt;This approach is a bit different from PrIter’s approach, which just says “start running and always use the smaller labels”. This works great in PrIter’s more ad-hoc setting, but we really need to be clear about which iteration is which for differential dataflow to work its magic.&lt;/p&gt;

&lt;h3 id=&quot;running-things&quot;&gt;Running things&lt;/h3&gt;

&lt;p&gt;Since graph connectivity is a bit different from BFS, in particular we have to symmetrize the graph before doing the propagation, we are going to use a different number of nodes and edges than above.
I chose 20M nodes and 400M edges, generated randomly, because those numbers are pretty similar (in magnitude) to taking half of the vertices in the &lt;a href=&quot;http://an.kaist.ac.kr/traces/WWW2010.html&quot;&gt;twitter_rv&lt;/a&gt; dataset (about 42M nodes and 1.5B edges).&lt;/p&gt;

&lt;p&gt;It takes about 800s to compute the connectivity information. This is &lt;a href=&quot;http://www.frankmcsherry.org/graph/scalability/cost/2015/01/15/COST.html&quot;&gt;slower than fancy graph processing systems&lt;/a&gt; on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;twitter_rv&lt;/code&gt; dataset, and we are only processing one fourth the data. The slowness can be partially explained by the fact that we are generating a bunch of random data, ingesting and sorting it, paging because reasons, and then doing graph connectivity using differential dataflow, which does math and stuff. Also, I just wrote this stuff last week and haven’t found a good profiler yet.&lt;/p&gt;

&lt;p&gt;It is also worth pointing out that the 800s time is one thread, and the computation is inherently parallel.
If I had a cluster I could tell you how well it parallelizes. Given that I don’t have a cluster (or a profiler) it might take a bit of love before it does, but &lt;a href=&quot;http://dl.acm.org/citation.cfm?id=2522738&quot;&gt;it has been done before&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Anyhow, it would be interesting to see if it has similar performance to the graph processing systems, because it can totally do some things they can’t even approach.&lt;/p&gt;

&lt;h3 id=&quot;tell-us-what-can-it-do-frank&quot;&gt;Tell us what can it do, Frank!&lt;/h3&gt;

&lt;p&gt;So we have our 400M edge graph, and we want to change it. Maybe someone tweetled an instachat.&lt;/p&gt;

&lt;p&gt;We’ll do this two ways, to get a good latency measurement and then a good throughput measurement. Specifically, let’s try changing one edge at a time, and then let’s try changing 1,000 edges at a time.&lt;/p&gt;

&lt;p&gt;We get the following cumulative density functions for the response time (from change introduced until confirmation that all output changes produced) for randomly tweaking one and one thousand edges, respectively:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.frankmcsherry.org/assets/latency.png&quot; alt=&quot;My helpful screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To explain, if we do one update at a time we get a median response time of 130 microseconds. If we do 1,000 updates at a time we get a median response time of 33 milliseconds. At this rate we should be able to do 30,000 edge updates per second, observing the correct graph connectivity output with a faster refresh rate than your favorite feature films.&lt;/p&gt;

&lt;p&gt;The elapsed running times for graph connectivity are within striking distance of purpose-built graph processors running on clusters (stay tuned!), and we get near real-time interactive updates too boot.&lt;/p&gt;

&lt;p&gt;Hey, and we did this all with just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;join&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group_by&lt;/code&gt; (and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iterate&lt;/code&gt;, to be fair). Who likes apples?&lt;/p&gt;

&lt;h2 id=&quot;up-next-under-the-covers&quot;&gt;Up next: Under the covers&lt;/h2&gt;

&lt;p&gt;I’m going to try and explain differential dataflow again. It is pretty exhausting, but I think it is the sort of thing people need to understand if they want to work in the space of real-time graph analytics. I’ve got a bit of travel in the coming weeks, so in the meantime if you are keen you can totally &lt;a href=&quot;https://github.com/frankmcsherry/differential-dataflow&quot;&gt;check out the repo&lt;/a&gt; and lament the lack of intelligent comments and tests.&lt;/p&gt;

&lt;p&gt;I’m hard at work tweaking the performance (mostly memory footprint) of differential dataflow. There are still a few cool things to do, and if you are interested in getting involved, let me know!&lt;/p&gt;
</description>
        <pubDate>Tue, 12 May 2015 15:20:00 +0000</pubDate>
        <link>http://www.frankmcsherry.org/differential/dataflow/2015/05/12/bfs.html</link>
        <guid isPermaLink="true">http://www.frankmcsherry.org/differential/dataflow/2015/05/12/bfs.html</guid>
        
        
        <category>differential</category>
        
        <category>dataflow</category>
        
      </item>
    
      <item>
        <title>Abomonation: terrifying serialization</title>
        <description>&lt;p&gt;Today we’re going to look at a simple serialization library written in Rust. Simple and utterly terrifying.&lt;/p&gt;

&lt;p&gt;The library, Abomonation (typo intentional), uses Rust’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt; keyword in some interesting ways. The intent is that it is actually safe if you only deserialize binary data serialized by the library.&lt;/p&gt;

&lt;p&gt;I should say that many of the things going on in here look a lot like what goes on in &lt;a href=&quot;https://capnproto.org&quot;&gt;CapnProto&lt;/a&gt;. If you are serious about being a grown-up about things, I would absolutely look over there. Among other things, it has the advantage of not necessarily lowering your opinion of me, unlike this post.&lt;/p&gt;

&lt;h2 id=&quot;serialization-and-deserialization&quot;&gt;Serialization and Deserialization&lt;/h2&gt;

&lt;p&gt;Our goal is to write a pair of methods with the following signatures:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;typed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That is, if you present a slice of data &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[T]&lt;/code&gt; we can populate a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&amp;lt;u8&amp;gt;&lt;/code&gt; with appropriate binary data. Similarly, if you provide us some binary data &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt;, we can interpret it as typed data &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[T]&lt;/code&gt; for you.&lt;/p&gt;

&lt;p&gt;We aren’t going to do this by the end of the post, but we’ll have something pretty similar. These are the actual signatures at the moment (the implementations are at the end of the post; don’t read them yet!):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;typed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;There are important distinctions here, so let’s briefly think about what’s being said by the interfaces.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;We are only going to be able to do this for &lt;strong&gt;some&lt;/strong&gt; types &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt;, those implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Abomonation&lt;/code&gt;.
This makes sense, because some types are hard to serialize. The name doesn’t make sense. Yet.&lt;/li&gt;
  &lt;li&gt;We are taking a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;mut [u8]&lt;/code&gt; rather than a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt;. That’s weird. Why would we need exclusive access to the input data? Are we going to change the binary data we’ve received? (I’m so sorry.)&lt;/li&gt;
  &lt;li&gt;We are returning a reference to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt;. Where did that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; come from and who owns it?
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;decode&lt;/code&gt; function doesn’t take one as a parameter, and you (usually) don’t just get references to owned data out of nowhere.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Well isn’t that just a big pile of mysterious language? Apparently we are going to do some things, and you should all be terrified.&lt;/p&gt;

&lt;h2 id=&quot;encoding-and-decoding-nice-data&quot;&gt;Encoding and Decoding nice data&lt;/h2&gt;

&lt;p&gt;Let’s start with some easy examples, ones that use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt; keyword but which we’ll pretty easily convince ourselves are really pretty friendly. We’ll get to learn a bit about how Rust manages its data under the covers, and maybe get our feet wet. What could go wrong?&lt;/p&gt;

&lt;p&gt;The following function takes a slice of typed data and presents it back as a slice of binary data.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;typed_to_bytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from_raw_parts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.as_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                               &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;size_of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Notice the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt; label on the function. This is because it uses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;from_raw_parts&lt;/code&gt; method, which is itself unsafe (it doesn’t check to make sure that the underlying data look correct). In this case it is safe, because we only want to look at the binary data (not mutate it) and every byte is a valid &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u8&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I could choose to remove the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt; label and put unsafe interally like so, declaring the method safe:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;typed_to_bytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from_raw_parts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.as_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                   &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;size_of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I have been warned this is bad, though, because reading a struct-padding byte is undefined behavior. This will not be the most irresponsible thing that happens in this post.&lt;/p&gt;

&lt;p&gt;On the other end of the safety spectrum, I present:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes_to_typed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from_raw_parts_mut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.as_mut_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                   &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;size_of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This not only exposes mutable slices, but permits any type at all. Imagine what would happen if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt; was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;, for example: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; owns its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[u8]&lt;/code&gt; buffer and cointains a pointer to it. You would be able to dereference arbitrary places in memory! Danger!! Danger!! The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt; label stays on for this one.&lt;/p&gt;

&lt;p&gt;Imagine you just saw &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bytes_to_typed&lt;/code&gt; eat a goat. It is powerful, but the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt; fence protects us. Just like in that movie with all those cautionary tales that I didn’t stay for the end of.&lt;/p&gt;

&lt;h3 id=&quot;you-said-nice-data&quot;&gt;You said “nice data”&lt;/h3&gt;

&lt;p&gt;These two methods work great if all you want to serialize are slices of primitive types. If you have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u64]&lt;/code&gt; you can just transmute it over to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt; and write out the data. If you get a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt; back you can (in good conscience) know that transmuting it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u64]&lt;/code&gt; works well.&lt;/p&gt;

&lt;p&gt;We are going to use the above two functions in our implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encode&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;decode&lt;/code&gt;, so let’s be brave and add the following sorts of implementations:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u16&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// lots of others ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Notice that these implementations don’t &lt;em&gt;do&lt;/em&gt; anything, they just indicate that it is safe for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encode&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;decode&lt;/code&gt; to transmute them around. In fact, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Abomonation&lt;/code&gt; will have a few methods, but these types can just use the default implementations.&lt;/p&gt;

&lt;h2 id=&quot;the-abomonation-stirs-&quot;&gt;The Abomonation stirs …&lt;/h2&gt;

&lt;p&gt;Primitive types are neat and all, but we are serious people! What about things like tuples? Surely I should be able to transmute a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[(u64, i8)]&lt;/code&gt; to binary and back, right? Yes, you should. But…&lt;/p&gt;

&lt;p&gt;… it’s complicated. You see, we built this trait to reach beyond primitive types. Much further beyond.&lt;/p&gt;

&lt;p&gt;Ok, let’s just write down this trait.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;entomb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_writer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;exhume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Wow, grim. In case &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt; wasn’t enough, I chose creepy method names. Creepy, but appropriate!&lt;/p&gt;

&lt;p&gt;The two methods have the following intents:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;entomb&lt;/code&gt;: having written &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;self&lt;/code&gt; as binary, serialize any further data it might own.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exhume&lt;/code&gt;: having populated &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;mut self&lt;/code&gt; with binary data, deserialize data it owned.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason this trait exists is because our method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encode(&amp;amp;Vec&amp;lt;T&amp;gt;, &amp;amp;mut Vec&amp;lt;u8&amp;gt;)&lt;/code&gt; is just going to copy the contents of the typed vector into the binary vector. Seems harmless enough for the types implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Abomonation&lt;/code&gt; so far, but we will use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;entomb&lt;/code&gt; to check with each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt; to see if they want to write any more. Similarly, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;decode&lt;/code&gt; uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exhume&lt;/code&gt; on each element to recover any owned data.&lt;/p&gt;

&lt;p&gt;Wait, owned data? What? I mean, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u64&lt;/code&gt; doesn’t own any data, so does it need to do anything? Nope!&lt;br /&gt;
All those other primitive types don’t need to either. They can use the default empty implementation.&lt;/p&gt;

&lt;p&gt;All those happy townsfolk, oblivious to what awaits…&lt;/p&gt;

&lt;p&gt;We got this far because we needed to talk about tuples. Let’s see how we might implement the weirdly capable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Abomonation&lt;/code&gt; for a pair of abomonable types.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;entomb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.entomb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.1&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.entomb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;exhume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.exhume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.1&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.exhume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Really, the whole interface is probably just messsing with forces we don’t understand.&lt;/p&gt;

&lt;p&gt;For example, you probably don’t understand why &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exhume&lt;/code&gt; uses a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt; when we said up above that we were going to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;mut [u8]&lt;/code&gt;. In retrospect, there were warning signs that something was wrong.&lt;/p&gt;

&lt;h2 id=&quot;late-in-the-lab-one-night-&quot;&gt;Late in the lab one night …&lt;/h2&gt;

&lt;p&gt;Some drinks in, an ambitious scientist finds she needs to serialize a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[Vec&amp;lt;Vec&amp;lt;u64&amp;gt;&amp;gt;]&lt;/code&gt;. She thinks:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hey, couldn’t I implement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Abomonation&lt;/code&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T: Abomonation&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, you can certainly type it.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;entomb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// write out the contents of self&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;exhume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// read back the contents into &amp;amp;mut self&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You know, this might just work. A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt; is a pointer, a length, and a capacity. Three &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;usize&lt;/code&gt; values. Serializing this triple doesn’t serialize the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt;’s &lt;em&gt;contents&lt;/em&gt;, but we can just do that in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;entomb&lt;/code&gt;. Similarly, when we pick up the serialized form of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt;, a triple, it doesn’t point at anything valid (to our great shame, it points at some location in memory wherever we serialized this), but  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exhume&lt;/code&gt; gives us a chance to read valid data back and make things right.&lt;/p&gt;

&lt;p&gt;If the scientist did something responsible in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exhume&lt;/code&gt;, like create a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; with new backing memory and assign it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*self&lt;/code&gt;, the story ends happily. But our story doesn’t follow that path.&lt;/p&gt;

&lt;h2 id=&quot;writing-the-code-that-shouldnt-be-written&quot;&gt;Writing the code that shouldn’t be written&lt;/h2&gt;

&lt;p&gt;The scientist, unfortunately, was mad with power. She demanded &lt;em&gt;zero&lt;/em&gt; allocations. She would use the binary data &lt;em&gt;as provided&lt;/em&gt; to back her creepy, hard-to-reason-about deserialization machinations.&lt;/p&gt;

&lt;p&gt;Rust provides unsafe functions capable of assembling a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt; out of “raw parts”: a pointer, a length, and a capacity. Our scientist is going to connect wires not meant to be connected, by building a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt; using a pointer into the supplied byte array.&lt;/p&gt;

&lt;p&gt;Now, our scientist may be power-mad, but she is still a scientist so she’s going to do this properly.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;exhume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 1. extract the memory from bytes to back our vector&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;binary_len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;size_of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;binary_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;binary_len&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 2. transmute buffer to &amp;amp;mut [u8], and then to a &amp;amp;mut [T].&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;bytes_to_typed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transmute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 3. build a new vector using this memory&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from_raw_parts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.as_mut_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 4. overwrite *self w/o dropping it.&lt;/span&gt;
    &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 5. pretend everything is normal; call exhume on each element&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;element&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.iter_mut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.exhume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pretty much all of these lines (everything except step &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.&lt;/code&gt;) are unsafe. Because the method has the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt; tag, we don’t need to flag individual regions as unsafe. They are all &lt;em&gt;quite&lt;/em&gt; unsafe.&lt;/p&gt;

&lt;p&gt;Let’s take a tranquil moment to reflect on what’s been done, and what might happen. We’ve minted a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; pointing at memory that cannot be freed. The memory is valid, and the rest of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; appears legitimate (length, capacity). But, as soon as we return from this method that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; is loose in the country-side and who knows what might happen.&lt;/p&gt;

&lt;p&gt;Well, what could go wrong, really?&lt;/p&gt;

&lt;h2 id=&quot;graaaaaaaaarrrrr&quot;&gt;GRAAAAAAAAARRRRR!!&lt;/h2&gt;

&lt;p&gt;You know, actually it all sort of works.&lt;/p&gt;

&lt;h2 id=&quot;graaaaaaoooo&quot;&gt;GRAAAaaaoooo?&lt;/h2&gt;

&lt;p&gt;Yeah, there is a disasterous mess of things here, and really Rust’s memory safety isn’t touching any of this with a ten foot lifetime bound. But let’s talk about the possible issues.&lt;/p&gt;

&lt;p&gt;First, let’s be clear that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exhume&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt;. Don’t call it. I haven’t figured out how to keep people from calling it yet (public traits export all their methods), but just don’t. It makes a few assumptions about how it will be called. It assumes that:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt; data is, in fact, exclusively held. We go and transmute it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;mut [u8]&lt;/code&gt; which is very wrong unless we are sure that we have the only reference. Fortunately, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;decode&lt;/code&gt; takes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;mut [u8]&lt;/code&gt; as its argument, and this ensures that our references are exclusive. We are also careful to partition the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt; into disjoint parts when we use it, to maintain this invariant.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;mut self&lt;/code&gt; parameter to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exhume&lt;/code&gt; will only be presented outwards as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;self&lt;/code&gt;. We can’t let anyone mutate or own this data. Trying to add elements to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt; will trigger a re-alloction, which would attempt to release the memory. It is very important that all references to the data are just that, only references. And references with lifetimes bounded by that of the source &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[u8]&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;mut self&lt;/code&gt; parameter to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exhume&lt;/code&gt; will never be dropped. Dropping the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt; would attempt to release the backing memory, which … well it isn’t going to work out. Fortunately, there never was a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt;. There were some bytes, and we pretended that a reference to the bytes was actually a reference to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt;, but there was never a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt; to drop.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are probably some other assumptions. Everything breaks if you pass in invalid data, for example. Don’t do that either.&lt;/p&gt;

&lt;p&gt;In fact, sorting out whether these assumptions are properly nailed down, or whether something truly horrible has happened, is basically what I’m up to now. I don’t really know, and it isn’t particularly clear what I need to do to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unsafe&lt;/code&gt; correctly. I invite your thoughts and criticism.&lt;/p&gt;

&lt;p&gt;Rust’s position is, perhaps refreshingly honestly: “yeah, it’s unsafe. you even said so.”&lt;/p&gt;

&lt;h2 id=&quot;epilogue&quot;&gt;Epilogue&lt;/h2&gt;

&lt;p&gt;What about our ambitious, power-mad scientist; where did she get off to?&lt;/p&gt;

&lt;p&gt;While we have been fretting about what might have gone wrong, she is experiencing excellent throughput numbers. Let’s look at a few using Rust’s benchmarking facilities.&lt;/p&gt;

&lt;h3 id=&quot;encoding&quot;&gt;Encoding&lt;/h3&gt;

&lt;p&gt;Here is a helpful routine to spin Rust’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bencher&lt;/code&gt; struct on any type implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Abomonation&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_bench_enc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bencher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Bencher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bencher&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bencher&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We try it out with types &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&amp;lt;u64&amp;gt;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&amp;lt;String&amp;gt;&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&amp;lt;Vec&amp;lt;(u64, String)&amp;gt;&amp;gt;&lt;/code&gt; and see:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;test bench_enc_u64     ... bench:       411 ns/iter (+/- 84) = 19990 MB/s
test bench_enc_string  ... bench:     12039 ns/iter (+/- 3330) = 2893 MB/s
test bench_enc_vec_u_s ... bench:     12578 ns/iter (+/- 1665) = 3482 MB/s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; is just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;format!(&quot;grawwwwrr!&quot;)&lt;/code&gt;. Results may vary with the string’s length, silliness.&lt;/p&gt;

&lt;p&gt;I should also point out that the throughput is not &lt;em&gt;goodput&lt;/em&gt;. It includes the extra pointers and capacities and stuff we didn’t need to send. The numbers do get a bit worse, especially for short strings. It’s the sort of thing you could fix by having a buffer implement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encode&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;decode&lt;/code&gt; and push/pull just lengths. However, you do need to stage the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&lt;/code&gt;s somewhere, and our approach let &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bytes&lt;/code&gt; do that for us.&lt;/p&gt;

&lt;h3 id=&quot;decoding&quot;&gt;Decoding&lt;/h3&gt;

&lt;p&gt;What about deserialization? That is where all the mess is, right? Well, it is a bit hard to measure deserialization speed fairly, because we are really not doing much other than fixing up some pointers.&lt;/p&gt;

&lt;p&gt;I did put some testing code in place to make sure that we were getting the right results back:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_bench_dec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Eq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bencher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Bencher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bencher&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bencher&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;assert!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nd&quot;&gt;assert!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;These give pretty comparable results. Apparently pushing a bunch of bytes works at about the same rate as checking that those bytes are what you expected. Again, not goodput numbers; &lt;em&gt;caveat emptor&lt;/em&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;test bench_dec_u64     ... bench:       525 ns/iter (+/- 262) = 15649 MB/s
test bench_dec_string  ... bench:     11289 ns/iter (+/- 2432) = 3086 MB/s
test bench_dec_vec_u_s ... bench:     12557 ns/iter (+/- 2183) = 3488 MB/s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Just to show how silly things are, here are the numbers where we comment out the assert loop, just checking the resulting lengths.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;test bench_dec_u64     ... bench:         2 ns/iter (+/- 0) = 4108000 MB/s
test bench_dec_string  ... bench:      2625 ns/iter (+/- 1031) = 13272 MB/s
test bench_dec_vec_u_s ... bench:      3020 ns/iter (+/- 1266) = 14503 MB/s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Hey look, 4TB/s deserialization. Maybe it is time to head back to Silicon Valley, maybe start things up.&lt;/p&gt;

&lt;h2 id=&quot;appendix-encode-and-decode&quot;&gt;Appendix: encode and decode&lt;/h2&gt;

&lt;p&gt;I wanted to put the implementations up here, but they aren’t very pretty. In particular, I suspect they could be greatly improved (possibly by deletion).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;typed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from_raw_parts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transmute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;typed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;size_of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.write_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;typed&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.entomb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Abomonation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;split2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.split_at_mut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;size_of&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;mem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transmute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split1&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.get_unchecked_mut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;unsafe&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.exhume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The whole pile of stuff will be up on &lt;a href=&quot;https://github.com/frankmcsherry&quot;&gt;github&lt;/a&gt; and &lt;a href=&quot;https://crates.io&quot;&gt;crates.io&lt;/a&gt; eventually, but I wanted to take at least a bit of time with other eyeballs on this to see if a) it is all horribly wrong, and b) whether the risk is ever worth it.&lt;/p&gt;

&lt;p&gt;Also, I should obviously avoid serializing pointers to your memory locations. No one needs to see that.&lt;/p&gt;
</description>
        <pubDate>Mon, 04 May 2015 15:20:00 +0000</pubDate>
        <link>http://www.frankmcsherry.org/serialization/2015/05/04/unsafe-at-any-speed.html</link>
        <guid isPermaLink="true">http://www.frankmcsherry.org/serialization/2015/05/04/unsafe-at-any-speed.html</guid>
        
        
        <category>serialization</category>
        
      </item>
    
      <item>
        <title>Data-parallelism in timely dataflow</title>
        <description>&lt;p&gt;The &lt;a href=&quot;http://www.frankmcsherry.org/dataflow/relational/join/2015/04/11/genericjoin.html&quot;&gt;previous post&lt;/a&gt; described a neat algorithm of &lt;a href=&quot;http://arxiv.org/abs/1310.3314&quot;&gt;Ngo et al&lt;/a&gt; and then described how one could go and implement this in &lt;a href=&quot;https://github.com/frankmcsherry/timely-dataflow&quot;&gt;timely dataflow&lt;/a&gt;. There were several bits of Rust code, a few places where details were glossed over, and then the claim that this was now a data-parallel implementation of the neat algorithm.&lt;/p&gt;

&lt;p&gt;In this post we walk through how the data-parallel bit happens, and detail some of the moving parts.&lt;/p&gt;

&lt;p&gt;Let’s start at the top, with some code we didn’t see in the previous post: a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; method:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;workers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// whatever you like here!&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;guards&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;communicator&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;ProcessCommunicator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;workers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;guards&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
                        &lt;span class=&quot;nf&quot;&gt;.name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;format!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;worker thread {}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;communicator&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()))&lt;/span&gt;
                        &lt;span class=&quot;nf&quot;&gt;.scoped&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;triangles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;communicator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
                        &lt;span class=&quot;nf&quot;&gt;.unwrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here we identify some desired number of workers, ask &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ProcessCommunicator&lt;/code&gt; (whoever that is) to create a new vector (of something), and for each element of this vector we start up a thread. Now, the elements of that vector have been suggestively named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;communicator&lt;/code&gt;, and indeed they each implementation a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Communicator&lt;/code&gt; trait. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scoped&lt;/code&gt; method is where the thread is started, and as you can see the only input it takes is one of these communicators. What could they possibly do?&lt;/p&gt;

&lt;h2 id=&quot;communicator&quot;&gt;Communicator&lt;/h2&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Communicator&lt;/code&gt; trait provides the &lt;em&gt;only&lt;/em&gt; point of contact between workers in timely dataflow. It has a fairly simple definition, so let’s look at it:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Communicator&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;peers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_channel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Send&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Columnar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pushable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pullable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The only functionality a communicator exposes to workers is to tell them their identity, the number of other workers, and to set up channels with the other workers. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new_channel&lt;/code&gt; method returns some targets the worker can push typed data at (one per worker) and one source the worker can pull from.&lt;/p&gt;

&lt;p&gt;When a worker pulls from a source they receive data pushed by other workers into their corresponding targets. The workers are expected to create the same sequence of channels; things go sideways if not.&lt;/p&gt;

&lt;p&gt;That’s all. If workers want to communicate in any other ways, they’ll need to create some channels.&lt;/p&gt;

&lt;h2 id=&quot;using-communicators&quot;&gt;Using communicators&lt;/h2&gt;

&lt;p&gt;Let’s look at the last bit of code from the previous post, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;triangles&lt;/code&gt; method referenced just above, in which we define the dataflow computation and set it going. I’m going to base this off of the code as it currently exists in &lt;a href=&quot;https://github.com/frankmcsherry/dataflow_join&quot;&gt;the repository&lt;/a&gt;, which has a few syntactic differences from the previous post.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;triangles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Communicator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;communicator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// load up our fragment of the graph&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Rc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;RefCell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* load up mmap&apos;d file */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// define extenders for a -&amp;gt; b and  (a, b) -&amp;gt; c, respectively&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b_extend&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.extend_using&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c_extend&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.extend_using&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
                        &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.extend_using&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})]&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// create a new root dataflow context&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;GraphRoot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;communicator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.new_subgraph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.new_input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// enable, extend, extend&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.enable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;nf&quot;&gt;.extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b_extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;nf&quot;&gt;.extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c_extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// return the input handle&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// iterate until done&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// introduce input&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Perhaps the most important thing to observe about this method is that it is &lt;em&gt;per-communicator&lt;/em&gt; logic. Each worker will call this method independently, and so they need to make sure they act in concert. This mostly boils down to constructing channels in some consistent order, and not going off and doing something else while other workers are waiting for you.&lt;/p&gt;

&lt;p&gt;Ideally the code isn’t too threatening.  The first few lines are related to the previous post, where we load up some graphs and define “prefix extenders” used to drive the aforementioned neat algorithm.&lt;/p&gt;

&lt;p&gt;There are some very important lines, though, so let’s look a bit more carefully at them.&lt;/p&gt;

&lt;h2 id=&quot;creating-a-dataflow-graph-root&quot;&gt;Creating a dataflow graph root&lt;/h2&gt;

&lt;p&gt;The point at which the dataflow graph first comes in to existence is when we wrap up our trusty communicator in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GraphRoot&lt;/code&gt;. This is that momentous line of code:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;GraphRoot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;communicator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GraphRoot&lt;/code&gt; is pretty simple, actually. It is the simplest implementator of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GraphBuilder&lt;/code&gt; trait, whose main role in life is to hand back references to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Communicator&lt;/code&gt; so that we can make channels.&lt;/p&gt;

&lt;p&gt;There are much more advanced and interesting, implementors of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GraphBuilder&lt;/code&gt;. Let’s see one now!&lt;/p&gt;

&lt;h2 id=&quot;creating-a-dataflow-subgraph&quot;&gt;Creating a dataflow subgraph&lt;/h2&gt;

&lt;p&gt;The next fragment of code creates a dataflow subgraph.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subgraph&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.new_subgraph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new_subgraph()&lt;/code&gt; method, defined by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GraphBuilder&lt;/code&gt; trait, returns a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SubgraphBuilder&lt;/code&gt;.
This is that advanced and interesting implementor of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GraphBuilder&lt;/code&gt; I mentioned above.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SubgraphBuilder&lt;/code&gt; provides access to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Communicator&lt;/code&gt; and the ability to call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new_subgraph&lt;/code&gt;, but it also has a non-trivial implementation of the method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add_scope&amp;lt;S: Scope&amp;gt;(scope: S)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Of course, I’m sure you all remember what a &lt;a href=&quot;http://www.frankmcsherry.org/dataflow/naiad/2014/12/29/TD_time_summaries.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Scope&lt;/code&gt;&lt;/a&gt; is, right? (\(\leftarrow\) click! do it. omg… click! click!).&lt;/p&gt;

&lt;h3 id=&quot;a-refresher-on-scopes&quot;&gt;A refresher on Scopes&lt;/h3&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Scope&lt;/code&gt; trait defines the methods different components of a timely dataflow graph need to use to discuss the progress of execution in the graph. A scope can explain to its parent how many inputs and outputs it has, what sorts of messages it might plan to send on those outputs, and given the chance maybe do a little bit of work to make progress itself, which it can then report to its parent.&lt;/p&gt;

&lt;p&gt;There are more details in &lt;a href=&quot;http://www.frankmcsherry.org/dataflow/naiad/2014/12/29/TD_time_summaries.html&quot;&gt;the link above&lt;/a&gt;, and much more to say about the progress tracking protocol.&lt;/p&gt;

&lt;p&gt;Our first example of a scope is actually in the very next line of code:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subgraph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.new_input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input&lt;/code&gt; method is provided by an extension trait on implementors of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GraphBuilder&lt;/code&gt;. Let’s see it.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;InputExtensionTrait&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GraphBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InputHelper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;D&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                        &lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;D&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ok, that might have been too much information. We see that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new_input&lt;/code&gt; does return two things, and apparently these are an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InputHelper&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream&lt;/code&gt;, but what these are is presently a total mystery. Also, what does all that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;G::Timestamp&lt;/code&gt; noise mean?&lt;/p&gt;

&lt;p&gt;Last things first, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;G::Timestamp&lt;/code&gt; is an &lt;em&gt;associated type&lt;/em&gt; of the graph builder &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;G&lt;/code&gt;. Each graph builder has an associated timestamp, and all scopes that graph builder will tolerate must use that timestamp. Both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InputHelper&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream&lt;/code&gt; are defined in terms of a specific common timestamp, given by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;G&lt;/code&gt;. They each also have a second type parameter, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;D&lt;/code&gt;, which is the type of data the input will pass along.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/frankmcsherry/timely-dataflow/blob/master/src/example_static/input.rs&quot;&gt;implementation&lt;/a&gt; of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input&lt;/code&gt; is neither particularly self-explanatory or illuminating, so I’ll summarize. The method does a few things, including creating an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InputHelper&lt;/code&gt; (to push data at) and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream&lt;/code&gt; (to connect dataflow to). Importantly, it also creates an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InputScope&lt;/code&gt;, which implements &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Scope&lt;/code&gt; and is what makes promises about which input timestamps are sealed. It is added to the subgraph builder.&lt;/p&gt;

&lt;p&gt;The implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input&lt;/code&gt; also snags a channel from the communicator, and wires it up so that submitted data get routed directly to consumers of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream&lt;/code&gt;. Other than keeping track of the number of records sent, which &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InputScope&lt;/code&gt; needs, all of the logic lives behind the channel implementations.&lt;/p&gt;

&lt;h2 id=&quot;intermission-data-parallelism&quot;&gt;Intermission: data-parallelism&lt;/h2&gt;

&lt;p&gt;Let’s take a moment to reflect on where we are. We’ve defined some infrastructure that will let each worker send records in to a dataflow computation, and, if we keep it up, exchange these records with other workers. Although a lot of our discussion here sounds like we are just talking about a single thread of execution, &lt;em&gt;that is exactly how we want it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;One of the main virtues of data-parallel programming is that we get to define our computation as if it were single threaded, knowing that if we spin up an arbitrary number of copies of the workers, and exchange data between them in an appropriate manner, the same result comes out the other end.&lt;/p&gt;

&lt;p&gt;This programming pattern does come with restrictions, mainly that all communication must be explicit and that the structure of communication (the dataflow graph itself) is defined ahead of time, but subject to these restrictions we don’t have to think too hard about concurrency. That is really good news.&lt;/p&gt;

&lt;h2 id=&quot;adding-some-more-scopes&quot;&gt;Adding some more scopes&lt;/h2&gt;

&lt;p&gt;Let’s get back to work. Our worker must define what it should do with this input stream now that it has it.&lt;/p&gt;

&lt;p&gt;At this point we get in to a slightly mysterious detail of our current implementation, which connects to Rust’s seriousness about shared access to mutable state. Rust is really quite serious about this topic.&lt;/p&gt;

&lt;p&gt;The graph builder &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;builder&lt;/code&gt; owns a bunch of mutable state, and we need to be very clear about when we are using it and when we are not using it. We we explicitly asked &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;builder&lt;/code&gt; to create an input for us, which is pretty clear. At the same time, we’d probably rather not involve a reference to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;builder&lt;/code&gt; in each method call, but we must know who is currently responsible for it.&lt;/p&gt;

&lt;p&gt;What we have at the moment is a notion of an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveStream&lt;/code&gt;, which is the information about a stream (basically a name and a listener list one can join) plus a graph builder. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveStream&lt;/code&gt; &lt;em&gt;owns&lt;/em&gt; that graph builder, in the Rust sense, so it knows that it has exclusive mutable access to its services.&lt;/p&gt;

&lt;p&gt;We create active streams from inactive streams by calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enable&lt;/code&gt; with a graph builder argument.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.enable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;.extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b_extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;.extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c_extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You might look at this and say: “whoa, is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;builder&lt;/code&gt; gone now?” In this case, yes. However, you can always get it back from an active stream; we just happened to discard the results in this case.&lt;/p&gt;

&lt;p&gt;Additionally, and this was someone else’s clever idea, not mine: there is a blanket implementation&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GraphBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GraphBuilder&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;G&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// lots of &quot;fn func(&amp;amp;mut self) { (**self).func() }&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;which says we can use a mutable reference &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;mut G&lt;/code&gt; anywhere we would use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;G: GraphBuilder&lt;/code&gt;. So we could have replaced &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;builder&lt;/code&gt; up there with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;mut builder&lt;/code&gt;, and not risked losing track of it.&lt;/p&gt;

&lt;h2 id=&quot;actually-adding-more-scopes&quot;&gt;Actually adding more scopes&lt;/h2&gt;

&lt;p&gt;Writing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extend&lt;/code&gt; a few times is sort of a cop-out as an example of additional scopes. Not so helpful.&lt;/p&gt;

&lt;p&gt;In &lt;a href=&quot;http://www.frankmcsherry.org/dataflow/relational/join/2015/04/11/genericjoin.html&quot;&gt;the previous post&lt;/a&gt; I elided the implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StreamPrefixExtender&lt;/code&gt;, the trait providing actions on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveStream&lt;/code&gt; including things like counting the number of extensions, proposing extensions, and validating the extensions. Let’s go through one of these implementations.&lt;/p&gt;

&lt;p&gt;I’m going to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;propose&lt;/code&gt; as the simplest implementation. The other two are roughly the same, but with slightly more inner logic. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;propose&lt;/code&gt; implementation uses an extension method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unary&lt;/code&gt; that I’ve written, wrapping the implementation of a scope with one input and one output. We will move to its implementation in just a moment, but let’s see its arguments to understand what it needs to know.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;StreamPrefixExtender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Rc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RefCell&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Columnar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Columnar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GraphBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;PE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PrefixExtender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;+&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&apos;static&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;propose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActiveStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActiveStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clone&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Exchange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// &amp;lt;-- minor lie&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.unary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;format!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Propose&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extender&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.borrow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.input&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.pull&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.output&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datum&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// send each extension&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.give&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extender&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.propose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unary&lt;/code&gt; method takes three arguments: a description of how its inputs need to be exchanged (to make sure the data arrive at the right worker), a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; naming the operator, and logic that takes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;handle&lt;/code&gt; through which inputs can be read and outputs sent.&lt;/p&gt;

&lt;p&gt;If we think for a moment, there really isn’t all that much more that needs to be said about an operator:*&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Which records should go to which worker.&lt;/li&gt;
  &lt;li&gt;What should the worker do with the inputs when they arrive.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the example above, we just want to consistently route prefixes to the same worker, so we use a hash of the prefix. When presented with some prefixes, the right thing to do is extend each of them and send the pair of prefix and extensions. Nothing else to say about the logic here, really.&lt;/p&gt;

&lt;p&gt;*: Actually there is more to know about, namely &lt;strong&gt;notification&lt;/strong&gt;, which is a &lt;em&gt;hugely&lt;/em&gt; important part of timely dataflow, but we just haven’t gotten there yet. Subsequent post, for sure.&lt;/p&gt;

&lt;h2 id=&quot;unary-scope&quot;&gt;Unary scope&lt;/h2&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unary&lt;/code&gt; method hides a lot of boiler plate, so that we can focus on the dataflow operator logic.&lt;/p&gt;

&lt;p&gt;What actually goes on inside that method? Nothing wildly complicated. It is &lt;a href=&quot;https://github.com/frankmcsherry/timely-dataflow/blob/master/src/example_static/unary.rs#L81-L174&quot;&gt;about 90 lines of code&lt;/a&gt;. When &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unary&lt;/code&gt; is called it uses the first argument, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exch&lt;/code&gt; above, to transform a channel from the communicator into a simple push/pull interface. It registers the “push” part of this with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stream&lt;/code&gt; and attaches the “pull” part to its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;handle&lt;/code&gt; object. It also prepares an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveStream&lt;/code&gt; for output, and connects the output of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;handle&lt;/code&gt; to the (to be populated) list of interested listeners. Finally, it creates a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UnaryScope&lt;/code&gt; who owns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;handle&lt;/code&gt; and will both call the user logic on it when needed, and report the number of records received and sent to its parent scope.&lt;/p&gt;

&lt;p&gt;That was a pretty long paragraph, so maybe it isn’t all that simple. But, it isn’t wildly complicated either.&lt;/p&gt;

&lt;h2 id=&quot;setting-things-in-motion&quot;&gt;Setting things in motion&lt;/h2&gt;

&lt;p&gt;The final bit of code looks like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// introduce input&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That looks pretty simple and harmless, but it is what sets the whole dataflow computation into motion.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root.step()&lt;/code&gt; call recursively traverses the scopes, asking each about any progress they’ve made. This is where each scope looks around, says “hey I &lt;em&gt;do&lt;/em&gt; have input!”, does a little bit of work, and then tells its parent what its done. The return value indicates whether there remains more work to do.&lt;/p&gt;

&lt;p&gt;In our case, the scopes will start moving input records, and start proposing extensions, and doing all the bits of logic we’ve programmed in. They will keep going as long as the inputs remain open, the operators report they are not yet done, and the &lt;a href=&quot;http://www.frankmcsherry.org/dataflow/naiad/2014/12/29/TD_time_summaries.html&quot;&gt;progress tracking protocol&lt;/a&gt; reports unresolved work.&lt;/p&gt;

&lt;p&gt;Crucially, all this business is written so that each of these workers operates independent of the others. While they do coordinate implicitly through data exchange and the progress tracking protocol, we just need to turn each of them on and run each of them until it stops.&lt;/p&gt;

&lt;p&gt;Ideally this stopping happens sooner with more workers, but… well I don’t want to spoil the surprise.&lt;/p&gt;
</description>
        <pubDate>Sun, 19 Apr 2015 15:20:00 +0000</pubDate>
        <link>http://www.frankmcsherry.org/dataflow/relational/join/2015/04/19/data-parallelism.html</link>
        <guid isPermaLink="true">http://www.frankmcsherry.org/dataflow/relational/join/2015/04/19/data-parallelism.html</guid>
        
        
        <category>dataflow</category>
        
        <category>relational</category>
        
        <category>join</category>
        
      </item>
    
      <item>
        <title>Worst-case optimal joins, in dataflow</title>
        <description>&lt;p&gt;I’ve gotten  &lt;a href=&quot;https://github.com/frankmcsherry/timely-dataflow&quot;&gt;timely dataflow in Rust&lt;/a&gt; up and running exciting computations! I’m going to explain one that I think is especially cool, and that I’m going to try using for some performance analysis of the underlying system (the system has so far been subjected only to latency micro-benchmarks …).&lt;/p&gt;

&lt;p&gt;The code for everything I’ll talk about is &lt;a href=&quot;https://github.com/frankmcsherry/dataflow_join&quot;&gt;available online&lt;/a&gt;. It isn’t particularly pretty yet, but just you wait.&lt;/p&gt;

&lt;p&gt;Seriously, you might actually want to wait. In the meantime, you can read this excellent post!&lt;/p&gt;

&lt;h2 id=&quot;relational-joins&quot;&gt;Relational Joins&lt;/h2&gt;

&lt;p&gt;A relational join is a pretty well studied thing, and I’m just going to lay some bare bones description here so that we have some common terminology. The problem starts with a collection of relations, think tables; we’ll call these relations \(R_1, R_2, \ldots R_k\). There are also several attributes, which we will refer to as \(A_1, A_2, \ldots A_j\). Each relation names some subset of these attributes, and each element in the relation has a value for each named attribute. Not all relations need to use the same set of attributes.&lt;/p&gt;

&lt;p&gt;The relational join problem is, given several relations, determine the set of tuples over the full attribute space so that for each tuple, its projection onto the attributes of each relation exists in that relation.&lt;/p&gt;

&lt;h3 id=&quot;an-example&quot;&gt;An example&lt;/h3&gt;

&lt;p&gt;Consider the first records of the three relations \(R_1, R_2, R_3\), over the three attributes \(A_1, A_2, A_3\):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.frankmcsherry.org/assets/join.png&quot; alt=&quot;My helpful screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The relational join between the three relations must contains at least the triples \((1, x, 2)\) and \((5, x, 4)\), because we can see in the first relation the pairs \((1,x)\) and \((5,x)\), in the second relation the pairs \((x,2)\) and \((x,4)\), and in the third relation the pairs \((1,2)\) and \((5,4)\).&lt;/p&gt;

&lt;p&gt;Of course, there may be more records in the full join as we see more of the records from each relation.&lt;/p&gt;

&lt;h3 id=&quot;ye-olde-methodologees&quot;&gt;Ye Olde Methodologees&lt;/h3&gt;

&lt;p&gt;There are many ways to go about doing a binary join, between two relations, but the simplest is a hash-join, where you look at the attributes in common between the two relations and hash each tuple based on their restriction to those attributes. For each pair of matching tuples (one from each relation), you form the extended tuple that takes the union of the attributes of the relations.&lt;/p&gt;

&lt;p&gt;In the example above, we might join the first two relations, by hashing records using the attribute \(A_2\). This matches \((1,x)\) and  \((5,x)\) from the first relation with  \((x,2)\) and  \((x,4)\) from the second relation.
The output data in this case are the four triples:  \((1,x,2)\),  \((1,x,4)\),  \((5,x,2)\), and \((5,x,4)\).
The record \((1,q)\) matches nothing in the second relation, and results in no output.&lt;/p&gt;

&lt;p&gt;To do a multi-way join, one could just keep grabbing relations and joining them in, until all relations have been used. This gives the correct answer, but can be really slow. A smarter way is to form a “plan”, which is a binary tree in which the leaves are relations and the internal nodes correspond to joins of relations. The root of this tree is the join of all relations, but the tree structure suggests which relations are good to start joining together.&lt;/p&gt;

&lt;p&gt;In the example above, we might prefer to join the three relations by first joining \(R_2\) and \(R_3\), which would produce only two records before joining with \(R_1\).
Building a smart join plan is something that database researchers like to talk about at their fancy meetings, and is a good way to strike up a conversation.&lt;/p&gt;

&lt;h3 id=&quot;more-recent-work&quot;&gt;More recent work&lt;/h3&gt;

&lt;p&gt;Relational joins have been around for such a long while, you might be a bit surprised to learn that there is still new work going on here. You might be even more surprised to learn that, in some respects, folks have been doing it wrong for quite some time. This is exactly what &lt;a href=&quot;http://arxiv.org/abs/1310.3314&quot;&gt;Ngo et al&lt;/a&gt; observe, in awesome work:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;The standard approach to computing relational joins, in which one repeatedly does binary joins, can do asymptotically more work than the join could ever possibly produce output tuples.&lt;/p&gt;

    &lt;p&gt;In the three-way join example, if each relation has size \(m\), there can be at most \(m^{3/2}\) output tuples, because &lt;em&gt;math&lt;/em&gt;. However, inputs exist so that any plan based on binary joins will do do \(m^2\) work.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;There exist algorithms that never do more work (asymptotically) than the join could, for some input of the same size, produce output tuples. For a three-way join, they will do \(O(m^{3/2})\) computation.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This second point doesn’t mean that they will only do as much work as they will produce output tuples, only that when they do lots of work they at least have the excuse that they &lt;em&gt;might&lt;/em&gt; have had to do it.&lt;/p&gt;

&lt;p&gt;You know who doesn’t even have that excuse? The standard approaches to computing relational joins.&lt;/p&gt;

&lt;h2 id=&quot;generic-join&quot;&gt;Generic Join&lt;/h2&gt;

&lt;p&gt;The algorithm that Ngo et al detail is really quite general. They even call the algorithm GenericJoin.&lt;/p&gt;

&lt;p&gt;I’m going to focus on a specific realization of it. I should say at this point that the specific realization is due to other people, not me. I’m not really sure who gets credit, but Semih Salihoglu and Chris Ré are the ones that taught me about worst-case join processing, and it was with Semih, Chris, and Michael Isard that we banged out &lt;a href=&quot;https://github.com/MicrosoftResearch/NaiadSamples/tree/master/Join&quot;&gt;the first version of this in Naiad&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;specific-join&quot;&gt;Specific Join&lt;/h3&gt;

&lt;p&gt;Rather than think of adding in relations one at a time, the way classical join processing might, we are going to think about adding in &lt;em&gt;attributes&lt;/em&gt; one at a time.&lt;/p&gt;

&lt;p&gt;Imagine we had the join on attributes \(A_1, \ldots A_i\), and want to output the join on attributes \(A_1, \ldots A_{i+1}\).
For each \((a_1, \ldots a_i)\) we want to produce the set of \((a_1, \ldots a_{i+1})\) supported by each relation \(R_j\).&lt;/p&gt;

&lt;p&gt;The easiest way to do this is just to ask each \(R_j\): “what extensions \(a_{i+1}\) do you support for \((a_1, \ldots a_i)\)?” For each \((a_1, \ldots a_i)\) we intersect their results, and return \((a_1, \ldots a_{i+1})\) for each \(a_{i+1}\) in the intersection.&lt;/p&gt;

&lt;p&gt;Of course, to ask the relations for the extensions and get the answer back quickly, each relation needs to be indexed by each prefix of attributes. This introduces redundancy, but we are going to work with it.&lt;/p&gt;

&lt;h3 id=&quot;smarter-join&quot;&gt;Smarter Join&lt;/h3&gt;

&lt;p&gt;Nothing about this algorithm is smart yet, but what Ngo et al observe is that if you do this intersection &lt;em&gt;carefully&lt;/em&gt;, you get a worst-case optimal join algorithm.  The specific care you have to take is, for each \((a_1, \ldots a_i)\), only do work proportional to the &lt;em&gt;smallest&lt;/em&gt; set of candidate extensions from any relation \(R_j\). Rather than just intersect everything willy-nilly, we have to start with the smallest and work our way up.&lt;/p&gt;

&lt;p&gt;Fortunately, computing the intersection of a small set with a large set is something that can take time roughly linear in the size of the smaller set. You can either hash everything all over the place, or eat a logarithmic factor (they are ok with that) using various binary search techniques.&lt;/p&gt;

&lt;p&gt;But, let’s look for a second and make sure we understand this. For each \((a_1, \ldots a_i)\) we have to ask a &lt;em&gt;data-dependent&lt;/em&gt; relation to propose some extensions, and then ask the other relations to validate them. There isn’t a static plan saying “everyone first ask \(R_1\), then \(R_7\), then ..”; rather than pipeline records through relations, like a traditional join plan might, we are going to exchange them all over the place.&lt;/p&gt;

&lt;p&gt;“Exchange”, you say? I hope you can see where this is going.&lt;/p&gt;

&lt;h2 id=&quot;a-rust-implementation&quot;&gt;A Rust implementation&lt;/h2&gt;

&lt;p&gt;Obviously we were going to do this. Don’t act surprised.&lt;/p&gt;

&lt;h3 id=&quot;some-abstractions&quot;&gt;Some abstractions&lt;/h3&gt;

&lt;p&gt;Do we really want relations and tuples all over the place in our code? No! Let’s do some abstraction.&lt;/p&gt;

&lt;p&gt;From the discussion above we can see that we really only need a few things from a relation:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It should be able to report how many extensions it would propose for \((a_1, \ldots a_i)\).&lt;/li&gt;
  &lt;li&gt;It should be able to propose specific extensions for \((a_1, \ldots a_i)\).&lt;/li&gt;
  &lt;li&gt;It should be able to intersect proposed extensions for \((a_1, \ldots a_i)\) with its extensions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So let’s write a trait that does this. I’m going to call the tasks above “count”, “propose”, and “intersect”.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PrefixExtender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Extension&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;propose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Extension&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;intersect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Extension&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I think this is as promised. This is what a relation needs to implement for us to be able to extend an element of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Prefix&lt;/code&gt; (think tuples \((a_1, \ldots a_i)\)) with an element of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Extension&lt;/code&gt; (think \(a_{i+1}\)).&lt;/p&gt;

&lt;h3 id=&quot;distributed-implementation&quot;&gt;Distributed implementation&lt;/h3&gt;

&lt;p&gt;Of course, what we would really like is to extend each of the prefixes in parallel across many workers.
At least, that is what I want. If you couldn’t care less, you should totally just skim this part.&lt;/p&gt;

&lt;p&gt;To make this happen I’m going to use the &lt;a href=&quot;https://github.com/frankmcsherry/timely-dataflow&quot;&gt;timely dataflow libary&lt;/a&gt;, which uses a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream&amp;lt;G, Prefix&amp;gt;&lt;/code&gt; type to represent a distributed stream of records of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Prefix&lt;/code&gt;. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;G&lt;/code&gt; type parameter describes how the stream is distributed and how the computation will execute, and we’ll just ignore it for now.&lt;/p&gt;

&lt;p&gt;We need to lift the implementation of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrefixExtender&amp;lt;P, E&amp;gt;&lt;/code&gt; to work on streams &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream&amp;lt;G, P&amp;gt;&lt;/code&gt;.
Fortunately, I’m going to do this for us, by implementing the following trait for any type implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrefixExtender&amp;lt;P, E&amp;gt;&lt;/code&gt; (plus some information about how to distribute the prefixes among workers).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;StreamPrefixExtender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;propose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;intersect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The records carry more information around with them; information that used to be on the stack now needs to be put in the records themselves. For example, we indicate the relation with the least count by a triple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(prefix: P, count: u64, index: u64)&lt;/code&gt;, data that would otherwise be in local variables. The signature of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; is also changed to take and produces triples, like updating stack variables.&lt;/p&gt;

&lt;p&gt;Although we are going to &lt;em&gt;use&lt;/em&gt; this interface, you don’t need to know too much about about this. The main thing to know is that there are about fifty fairly predictable lines of code that go and implement a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StreamPrefixExtender&amp;lt;G, P, E&amp;gt;&lt;/code&gt; for any type implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrefixExtender&amp;lt;P, E&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;StreamPrefixExtender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Rc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RefCell&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PrefixExtender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// the library does this for you, you just implement PrefixExtender.&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Technically speaking, you will also need to tell timely dataflow how to distribute the prefixes. This will depend on how you distribute your relation, and is something I’ll say more about in an upcoming post.&lt;/p&gt;

&lt;h3 id=&quot;specific-join-in-rust&quot;&gt;Specific Join in Rust&lt;/h3&gt;

&lt;p&gt;With these abstractions, we are now ready to build a layer of the specific join algorithm. Before we do, let’s see what that would mean.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SpecificJoinExt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extenders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;StreamPrefixExtender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We need to write a method for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream&amp;lt;G, P&amp;gt;&lt;/code&gt; that, given a vector of arbitrary things implementing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StreamPrefixExtender&amp;lt;G, P, E&amp;gt;&lt;/code&gt; trait, produces a stream of pairs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(P, Vec&amp;lt;E&amp;gt;)&lt;/code&gt;. Also, we have to do it in the smart way described above, otherwise we’ll go slow like all the creaky database systems.&lt;/p&gt;

&lt;p&gt;I’m just going to show you the code, but the comments should talk you through it. It’s just like we said.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SpecificJoinExt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extenders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;StreamPrefixExtender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// start with horrible proposals from a non-relation&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// ask each extender to try to improve each proposal&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;counts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;63&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;63&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;extenders&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;counts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extenders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;counts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// for each of the extenders ...&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;extenders&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// find the prefixes the extender &quot;won&quot; the right to extend&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nominations&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;counts&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                                        &lt;span class=&quot;nf&quot;&gt;.select&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// get the extensions and ask each other extender to validate&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extensions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extenders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.propose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nominations&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;extenders&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;extensions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extenders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.intersect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// fold surviving extensions into the output&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.concat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is the whole algorithm. It is really not super complicated. Rather, that is one layer of the algorithm.
To fill out a full relational join we need to call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extend&lt;/code&gt; multiple times, with different &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrefixExtender&lt;/code&gt; objects wrapping the same relations, just for different lengths of prefix. Let’s do an example.&lt;/p&gt;

&lt;h2 id=&quot;a-low-latency-triangle-enumerator&quot;&gt;A low-latency triangle enumerator&lt;/h2&gt;

&lt;p&gt;If we define a graph as a set of pairs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(src, dst)&lt;/code&gt;, a triangle is defined as a triple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(a,b,c)&lt;/code&gt; where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(a,b)&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(b,c)&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(a,c)&lt;/code&gt; are each in the set of pairs. We can think of the triangles query as a relational join over three relations, which are the same data just bound to different pairs of attributes.&lt;/p&gt;

&lt;h3 id=&quot;defining-a-prefixextender&quot;&gt;Defining a &lt;strong&gt;PrefixExtender&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;We will represent a fragment of graph by a list of destinations and offsets into this list for each vertex. For each interval, we will keep the destinations sorted to make the intersection tests easier.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GraphFragment&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Ord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’ll just write a quick helper function to let use get at the edges associated with a node:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Ord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GraphFragment&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.nodes&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It is worth pointing out that Rust is doing some very clever things under the hood here. It notices that we are returning a reference to some memory, the type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;[E]&lt;/code&gt;, and that the only thing this could refer to is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;self&lt;/code&gt;. Rust then sets up the lifetime bound for the output to be that of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;self&lt;/code&gt; and will ensure that when we use the result it is not allowed to out-live &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;self&lt;/code&gt; itself.&lt;/p&gt;

&lt;p&gt;I’m going to lie a little and present a simplified sketch of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrefixExtender&lt;/code&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GraphFragment&lt;/code&gt;. The simplified version uses a reference-counted  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GraphFragment&lt;/code&gt;, all that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Rc&amp;lt;RefCell&amp;lt;...&amp;gt;&amp;gt;&lt;/code&gt; stuff. This allows us to have just one copy of the graph loaded and to share it out between folks who need it. We also need a helper function of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;L: Fn(&amp;amp;P)-&amp;gt;u64&lt;/code&gt; to extract a node identifier from the type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;P&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PrefixExtender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Rc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RefCell&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GraphFragment&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Ord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// counting is just looking up the edges&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;logic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prev&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.borrow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// proposing is just reporting the slice back&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;propose&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;logic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.borrow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.to_vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// intersection &apos;gallops&apos; through a sorted list to find matches&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// what is &quot;galloping&quot;, you ask? details coming in just a moment&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;intersect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;P&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;logic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.borrow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.retain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;move&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;gallop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// skips past elements &amp;lt; value&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That was pretty easy, huh? It sure was a lot messier before I wrote that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edges(node)&lt;/code&gt; helper method. It is also a bit grottier when I’m not lying about how things work, but let’s not let that get between us.&lt;/p&gt;

&lt;p&gt;In the interest of completeness (and eyeballs on my code) let’s look at the implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gallop&lt;/code&gt;. From an input slice and value, it skips forward in exponentially increasing steps, and then once it would overshoot the target value it moves forward in exponentially shrinking steps.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// advances slice to the first element not less than value.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gallop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Ord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&apos;a&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;step&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;// this shouldn&apos;t explode... right?&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;slice&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gallop&lt;/code&gt; method can use any slice as input, and we give it a slice cut out of the graph’s edge list. If we mis-use the result slice there could be a horrible risk of aliasing, data races, de-, or re-allocation. Rust can confirm that none of this happens and just lets us use the same memory. It does this through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;retain&lt;/code&gt;’s closure, through a method I wrote (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gallop&lt;/code&gt;), and through lots of weird logic. This is great!&lt;/p&gt;

&lt;h3 id=&quot;building-the-triangles-query&quot;&gt;Building the triangles query&lt;/h3&gt;

&lt;p&gt;So to recap, we have an implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PrefixExtender&amp;lt;P,E&amp;gt;&lt;/code&gt; whenever we have a combination of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GraphFragment&amp;lt;E&amp;gt;&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;L: Fn(&amp;amp;P)-&amp;gt;u64&lt;/code&gt;. This second part, the function, is what tells us how to turn a prefix &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;P&lt;/code&gt; into an integer we can use to look up extensions in the graph. We use the same graph data everywhere, but fake out multiple relations by letting the function from prefix to attribute vary.&lt;/p&gt;

&lt;p&gt;That being said, let’s count some triangles! We’ll do this in parts. I’m going to lie a bit again, and present some simplifications. Nothing horrible, just possibly mysterious things that would require a diversion to rationalize (and may just be bad design on my part).&lt;/p&gt;

&lt;p&gt;First we just write some code that from a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Communicator&lt;/code&gt;, which indicates a worker’s index and its number of peers, figures out what fragment of the graph this worker will load up and be responsible for. The code then prepares a dataflow computation and an input into which we will feed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a: u32&lt;/code&gt; values. This is all timely dataflow boiler-plate, and not super exciting from an algorithmic point of view.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;triangles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;communicator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;graph_source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Communicator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GraphFragment&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// load up the slice of graph corresponding to our index out of peers.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;graph_source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;communicator&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;communicator&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.peers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Rc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;RefCell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// prepare a new computation with one input.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;computation&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;new_computation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;communicator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;computation&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.new_input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The next step is to create the extender from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(a,b)&lt;/code&gt;. We just need to pair a copy of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;graph&lt;/code&gt; with a function that converts from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt; into a graph node identifier, and it will propose the adjacent &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt; values.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;c1&quot;&gt;//  // define an extender that uses &apos;a&apos; to suggest x: &apos;(a,x)&apos; extensions&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ext_b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}))];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pairs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ext_b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flatten()&lt;/code&gt; method simply converts a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream&amp;lt;G, (P, Vec&amp;lt;E&amp;gt;)&amp;gt;&lt;/code&gt; to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream&amp;lt;G, (P, E)&amp;gt;&lt;/code&gt;. It is just 17 lines of code somewhere, so hooray for timely dataflow being easy to use (succint, at least).&lt;/p&gt;

&lt;p&gt;The next step is to create the extenders from pairs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(a,b)&lt;/code&gt; to triples &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;((a,b),c)&lt;/code&gt;. There are going to be two extenders, as we want values &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c&lt;/code&gt; such that both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(a,c)&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(b,c)&lt;/code&gt; are present in the graph. Each extender needs to take an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(a,b)&lt;/code&gt; and identify an edge source, and there only two choices.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;c1&quot;&gt;//  // define extenders using &apos;a&apos; and &apos;b&apos; to suggest x: (a,x) and x:(b,x)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ext_c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})),&lt;/span&gt;
                     &lt;span class=&quot;nn&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}))]&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;triangles&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pairs&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ext_c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Let’s also take a peek at what we see, printing out the triangles (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;observe&lt;/code&gt; is another 16 lines of code).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;c1&quot;&gt;//  // take a peek at what gets produced.&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;triangles&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.observe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;triangle: {:?}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ok, we’ve got the data loaded up, the computation defined, and are ready to go. Let’s triangularate!&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;c1&quot;&gt;//  // finalize dataflow structure&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;computation&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.borrow_mut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.get_internal_summary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;computation&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.borrow_mut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.set_external_summary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// introduce u32s to find triangles rooted from them&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.borrow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.nodes&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.send_messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.advance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;computation&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.borrow_mut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// close input and finish any computation&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.close_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.borrow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.nodes&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;computation&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.borrow_mut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This runs through each possible source &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node&lt;/code&gt; and produces all triangles starting from that vertex.&lt;/p&gt;

&lt;h3 id=&quot;didnt-you-say-low-latency&quot;&gt;Didn’t you say “low-latency”?&lt;/h3&gt;

&lt;p&gt;Good point. Although we went through all nodes in order, to enumerate all triangles, we didn’t have to. We could just as easily have written the main loop as:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoch&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;read_u32_from_console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// not a real function!&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.send_messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.advance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epoch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;computation&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.borrow_mut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This reads input in from the user, fires that off to the dataflow, which prints out observed triangles asap. To give a sense for how promptly, let’s throw a timer in the standard loop up above, and start things up single-threaded on a fairly standard &lt;a href=&quot;https://snap.stanford.edu/data/soc-LiveJournal1.html&quot;&gt;LiveJournal dataset&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Caveat&lt;/em&gt;: the code is barely tested and may be wrong; do not buy/sell anything based on these data.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;enumerated triangles from (0..1) in 67947ns
enumerated triangles from (1..2) in 42738ns
enumerated triangles from (2..3) in 26064ns
enumerated triangles from (3..4) in 48006ns
enumerated triangles from (4..5) in 19921ns
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So the time between injecting a node identifier and getting all the triangles back out is some tens of microseconds.
These numbers get a little better with batching, cutting out some of the overhead:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;enumerated triangles from (0..10) in 231301ns
enumerated triangles from (10..20) in 276721ns
enumerated triangles from (20..30) in 149940ns
enumerated triangles from (30..40) in 137285ns
enumerated triangles from (40..50) in 155781ns
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And even better with even more batching, as it starts to look more like 10-15us amortized per vertex.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;enumerated triangles from (0..1000) in 10129064ns
enumerated triangles from (1000..2000) in 12018695ns
enumerated triangles from (2000..3000) in 11323832ns
enumerated triangles from (3000..4000) in 11224684ns
enumerated triangles from (4000..5000) in 14810182ns
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This program is meant to stress several components and reveal what is slow and needs some work.
The underlying system still has lots of room to improve (I think).The numbers &lt;em&gt;should&lt;/em&gt; only get better.&lt;/p&gt;

&lt;h3 id=&quot;triangles-are-soooo-lame&quot;&gt;Triangles are &lt;em&gt;soooo&lt;/em&gt; lame.&lt;/h3&gt;

&lt;p&gt;Triangles are &lt;em&gt;so&lt;/em&gt; 2014! Everyone does triangles! Do something cooler!&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// define extenders to add a &apos;d&apos; coordinate connected to each of a, b, c.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ext_d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})),&lt;/span&gt;
                 &lt;span class=&quot;nn&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})),&lt;/span&gt;
                 &lt;span class=&quot;nn&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}))];&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quads&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;triangles&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ext_d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.flatten&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In case you missed it, that was us defining a dataflow computation to enumerate 4-cliques.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;enumerated 4-cliques from (0..1) in 164474ns
enumerated 4-cliques from (1..2) in 230093ns
enumerated 4-cliques from (2..3) in 40667ns
enumerated 4-cliques from (3..4) in 154921ns
enumerated 4-cliques from (4..5) in 67344ns
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Not impressed? How about:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;enumerated 5-cliques from (0..1) in 115570ns
enumerated 5-cliques from (1..2) in 311967ns
enumerated 5-cliques from (2..3) in 110915ns
enumerated 5-cliques from (3..4) in 126838ns
enumerated 5-cliques from (4..5) in 104806ns
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What about:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;enumerated 6-cliques from (0..1) in 107292ns
enumerated 6-cliques from (1..2) in 342537ns
enumerated 6-cliques from (2..3) in 93778ns
enumerated 6-cliques from (3..4) in 134948ns
enumerated 6-cliques from (4..5) in 87148ns
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I can keep going (seriously, I wrote a program to do it).&lt;/p&gt;

&lt;p&gt;Arbitrary graph motif finding (small subgraphs you want to find in a large graph) is really easy to write. I’m sure there is lots of smarter research on how to do it will, but this is worst-case optimal, at least.&lt;/p&gt;

&lt;h2 id=&quot;wrap-up&quot;&gt;Wrap-up&lt;/h2&gt;

&lt;p&gt;There is some seriously cool work going on in join research. It isn’t nearly as stale as I thought it was.
But, importantly, these new algorithms need better systems than your bog-standard batch processors.&lt;/p&gt;

&lt;p&gt;How many of your favorite graph processors are able to start handing back 6-cliques in less than a millisecond, with a memory footprint that is roughly just the graph itself? Not the one you use? Why not? Being bad at things stopped being cool in high school…&lt;/p&gt;

&lt;p&gt;On the positive side, in addition to this code, &lt;a href=&quot;https://github.com/MicrosoftResearch/NaiadSamples/tree/master/Join&quot;&gt;Naiad can do all this&lt;/a&gt;, and &lt;a href=&quot;https://flink.apache.org&quot;&gt;Flink&lt;/a&gt; should be able to as well.&lt;/p&gt;

&lt;p&gt;From my point of view, I now have a cool problem I can use to tune &lt;a href=&quot;https://github.com/frankmcsherry/timely-dataflow&quot;&gt;timely dataflow&lt;/a&gt;. I would expect it to start getting better and more usable (and I’ll probably actually try it in more configurations now too).&lt;/p&gt;

&lt;h2 id=&quot;footnote-skew&quot;&gt;Footnote: Skew&lt;/h2&gt;

&lt;p&gt;One of the theoretical concerns about this sort of approach is how well it distributes in the presence of skew. Each of our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;propose&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;intersect&lt;/code&gt; operators is “data-parallel”, but is this actually helpful when the data are themselves not uniformly distributed among workers.&lt;/p&gt;

&lt;p&gt;The implementation I’ve talked about is not skew-resilient. If a vertex has a really high degree, all the edges may be placed on one machine. Adding more machines will not make &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;propose&lt;/code&gt; go any faster, and will not prevent all proposals from being sent to this one machine for intersection.&lt;/p&gt;

&lt;p&gt;However, you can whip together a skew-resilient version of each of these operations.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; is naturally skew resilient because we just need a number for each prefix. They can be distributed (uniformly) by a hash on the prefix.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;propose&lt;/code&gt; can be made skew resilient by distributing the extensions for each prefix to machines sequentially from a machine identified by a hash of the prefix. We need to know how many machines to ask for extensions, but each proposal should come with a count (I dropped it; oops).&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;intersect&lt;/code&gt; can be made skew resilient by distributing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(P, E)&lt;/code&gt; records by hash, and shipping proposals to the known location for each relation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’m not intimate enough with the work in the area to know for sure that this ticks all of the desired boxes. For example, one desideratum is that the computation take few “rounds”, in the MapReduce sense. Given that I don’t use MapReduce and still get computation done, this doesn’t really bother me.&lt;/p&gt;

&lt;p&gt;Flip comments aside, it would be good to understand if this approach does represent a scalable, skew-resilient worst-case optimal join algorithm. I’ve been told that this would be pretty cool.&lt;/p&gt;

&lt;h2 id=&quot;credits-and-on-going-work&quot;&gt;Credits and on-going work&lt;/h2&gt;

&lt;p&gt;This area (worst-case optimal join processing) is super cool, and I owe a debt to &lt;a href=&quot;http://web.stanford.edu/~semih/&quot;&gt;Semih Salihoglu&lt;/a&gt; and &lt;a href=&quot;http://cs.stanford.edu/people/chrismre/&quot;&gt;Chris Ré&lt;/a&gt; for introducing the two of us. These folks and their colleagues are still very active, pushing the boundaries of what can be done to make join processing faster and faster.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://arxiv.org/abs/1410.4156&quot;&gt;Semih, Chris and others&lt;/a&gt; have an approach to make even more complex joins efficient by using hypertree decompositions of queries. “Tree-like” acyclic queries have knowns ways to process them optimally (Viterbi-style). Once you get cycles they break, but if you can decompose the query into a tree of small cyclic queries and use the worst-case optimal techniques on the cyclic queries you get some nice results.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://arxiv.org/abs/1503.02368&quot;&gt;Chris Aberger, Chris and others&lt;/a&gt; have a neat way to make the intersection testing much more efficient using SIMD instructions, and show that they can get orders-of-magnitude speed-ups by using these techniques. Various “unnamed” proprietary vendors seem to get well and thoroughly trounced.&lt;/p&gt;

&lt;p&gt;There is plenty of other work I’m not as familiar with (yet), but it seems to just keep coming. Be excited!&lt;/p&gt;
</description>
        <pubDate>Sat, 11 Apr 2015 15:20:00 +0000</pubDate>
        <link>http://www.frankmcsherry.org/dataflow/relational/join/2015/04/11/genericjoin.html</link>
        <guid isPermaLink="true">http://www.frankmcsherry.org/dataflow/relational/join/2015/04/11/genericjoin.html</guid>
        
        
        <category>dataflow</category>
        
        <category>relational</category>
        
        <category>join</category>
        
      </item>
    
      <item>
        <title>Differential dataflow</title>
        <description>&lt;p&gt;I’m now in Berlin, working on &lt;a href=&quot;https://github.com/frankmcsherry/timely-dataflow&quot;&gt;timely dataflow in Rust&lt;/a&gt;. It is up and running, multiple processes and all, so the next step is to get some experience using it to point out performance and usability issues. Unfortunately, at the moment there is a Rust internal compiler error preventing me from doing this, so I’m going to write a bit about some of the applications!&lt;/p&gt;

&lt;p&gt;Differential dataflow is an approach to doing incremental data-parallel computation that several of us &lt;a href=&quot;http://www.cidrdb.org/cidr2013/Papers/CIDR13_Paper111.pdf&quot;&gt;introduced at CIDR 2013&lt;/a&gt;. I’ll work through the details, but the rough idea is rather than have a point in the computation (a dataflow vertex) maintain a dataset, it maintains a collection of differences from which the data can be efficiently updated. If this sounds a bit like incremental dataflow computation, it is, and differential dataflow can be viewed as a generalization of existing techniques from incremental dataflow computation to dataflow graphs with possibly nested iteration.&lt;/p&gt;

&lt;h2 id=&quot;setting&quot;&gt;Setting&lt;/h2&gt;

&lt;p&gt;Differential dataflow is primarily motivated by its ability to efficiently compute and update arbitrarily nested iterative data-parallel computations (things like MapReduce, but extended with an iteration operator). Each operator in a differential dataflow computation is defined much like a MapReduce stage, including:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;For each input &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt; a key function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Key_i: Si -&amp;gt; K&lt;/code&gt; from source records to a common key type.&lt;/li&gt;
  &lt;li&gt;A reduction fuction &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Reduce: (K, &amp;amp;[S0], &amp;amp;[S1], ... , &amp;amp;[Sk]) -&amp;gt; &amp;amp;[R]&lt;/code&gt; from a key and some source records to some result records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The output collection for any input collection is whatever results from grouping the source records by the key function, applying the reduction function, and merging the result records across all groups.&lt;/p&gt;

&lt;p&gt;These operators are arranged in a graph, with outputs from one leading in to the inputs of others.&lt;/p&gt;

&lt;p&gt;To this we add an iteration operator on collections, which takes an iteration function from collections to collections and a number of iterations. If we were to write this in Rust it might look like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IterableExt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// returns something equivalent to iteration^steps(data)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iterate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;-&amp;gt;Self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This iteration operator starts to make dataflow look like a pretty powerful computational framework, but implementing it efficiently raises all sorts of questions.&lt;/p&gt;

&lt;h3 id=&quot;naive-implementation&quot;&gt;Naive implementation&lt;/h3&gt;

&lt;p&gt;Clearly there are some ways we could implement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iterate&lt;/code&gt; for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Collection&amp;lt;S&amp;gt;&lt;/code&gt;. We could just have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt;-loop which does as many steps as requested:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Iterable&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Collection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iterate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;-&amp;gt;Self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This approach is possibly very wasteful, especially in a not-uncommon case where the collection stabilizes as it iterates, reaching a fixed point (where each application of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt; results in the same collection). We could put some logic in place checking each iteration to see if it has change and exiting early if it hasn’t, but we may still be leaving a lot of performance on the table if &lt;em&gt;most&lt;/em&gt; of the collection is not changing.&lt;/p&gt;

&lt;h3 id=&quot;incremental-dataflow&quot;&gt;Incremental dataflow&lt;/h3&gt;

&lt;p&gt;If we only want to do one &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iterate&lt;/code&gt; at a time (“obviously”, you say; bear with me), a smarter approach is to frame the implementation in terms of incremental updates to the collections. Rather than fully form collections &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt; each iteration, the operators could remember the previous values of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt; and we could instead determine and communicate only &lt;em&gt;changes&lt;/em&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt;. If all operators are implemented this way, the entire computation can be in terms of these differences.&lt;/p&gt;

&lt;p&gt;Incrementally updates are not only more efficient for communication, but in the data-parallel setting they can also dramatically improve performance. If I have a collection of one billion records, and I am presented with a single change: “add/remove &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;record&lt;/code&gt;”, I only need to apply the key function to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;record&lt;/code&gt; and incrementally update the &lt;em&gt;group&lt;/em&gt; that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;record&lt;/code&gt; might belong to. Other groups have not changed, and our computational model requires their outputs be functions only of the inputs. The only possible change to the output is due to changes to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;key(record)&lt;/code&gt; group.&lt;/p&gt;

&lt;h3 id=&quot;limitations&quot;&gt;Limitations&lt;/h3&gt;

&lt;p&gt;One way to view incremental computation is that for a &lt;em&gt;sequence&lt;/em&gt; of collections, it determines and communicates the differences between collections. There are several cases where this is a perfect fit, but also many where the requirement that the collections form a sequence is limiting.&lt;/p&gt;

&lt;p&gt;Consider the problem of incrementally updating the input to an iterative computation. While the iterates of the iterative computation do form a sequence, we would now like to change the &lt;em&gt;first&lt;/em&gt; element in that sequence. What incremental computation allows us to do is update the &lt;em&gt;final&lt;/em&gt; element of the sequence. For some computations this may be correct, but for many computations it is simply incorrect to “patch” the output; it doesn’t produce the right answer.&lt;/p&gt;

&lt;p&gt;Consider a hypothetical case where a bank uses some fraud data and a set of financial transactions to expand its view of “risky customers” to those people connected to a fraudster through any number of financial transactions. Perhaps you are accidentally determined to have had fraudulent activity, leading to your associates being labeled as fraudsters too. While you might correct the data after talking with the bank, removing your name from the source fraud dataset, your associates are still labeled as fraudsters and in one iteration you will be again too. In fact, none of you should be labeled as such, but updates to the current state cannot fix that. We must “restart” the iterative computation with the change.&lt;/p&gt;

&lt;h2 id=&quot;differential-interpretation&quot;&gt;Differential interpretation&lt;/h2&gt;

&lt;p&gt;Differential dataflow removes the restriction to sequences of collections by generalizing the notion of “differences between collections”. Formally, it uses a technique called “&lt;a href=&quot;http://en.wikipedia.org/wiki/Möbius_inversion_formula&quot;&gt;Moebius inversion&lt;/a&gt;” to allow differencing along an arbitrary partial order (rather than just a sequence). Although this sounds very complicated, and the area is deep and interesting, our application of it really isn’t so mysterious.&lt;/p&gt;

&lt;p&gt;Imagine we have a set of collections &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Collection[t]&lt;/code&gt; indexed by some elements &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t&lt;/code&gt; of a set. If we avoid being too specific about this set, some examples become a bit easier to understand.&lt;/p&gt;

&lt;p&gt;For our example of propagation of fraud data along edges in a financial transaction graph, the collection indicating who is or isn’t risky could be indexed by the pair &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(epoch, iteration)&lt;/code&gt;, indicating the state of the collection using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;epoch&lt;/code&gt; version of input fraud data, after &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iteration&lt;/code&gt; rounds of computation. The risk data are initially indexed by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0,0)&lt;/code&gt; and perhaps converge to their limit at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0,1032)&lt;/code&gt;. You place a phone call to the bank, who can then update the base fraud data with index &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1,0)&lt;/code&gt; indicating new data and no iteration yet. This data can then lead to a revised output now indexed by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1,937)&lt;/code&gt; (as perhaps the number of rounds to converge has decreased without you in the input data).&lt;/p&gt;

&lt;p&gt;Pedagogically, the collection at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1,0)&lt;/code&gt; is more similar to the collection at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0,0)&lt;/code&gt; than it is to the collection “just a moment ago” at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0,1032)&lt;/code&gt;. Rather than attempt to describe the collection at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1,0)&lt;/code&gt; in terms of the collection at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0,1032)&lt;/code&gt;, as incremental computation might, we will describe a more flexible differencing scheme that instead relates it to the collection at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0,0)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;More generally, we can describe the collection at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1,i)&lt;/code&gt; by the collections at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0,i)&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1,i-1)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The indices we choose to use, and the partial order we define over them, will drive how the differencing is done. By choosing an ordering that better reflects the development and dependence of data we are able to have fewer and smaller differences. The fact that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1,0)&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0,1032)&lt;/code&gt; are not ordered using the standard partial order allows us to avoid using either as part of the definition of the other.&lt;/p&gt;

&lt;p&gt;Using the product partial order (one pair is less-or-equal another if both coordinates are), we get a nice alignment with the actual dependencies in an iterative computation, where an iterate depends only on collections at prior iterations and prior epochs.&lt;/p&gt;

&lt;h3 id=&quot;differential-representation&quot;&gt;Differential representation&lt;/h3&gt;

&lt;p&gt;Let’s write down a fairly uncontroversial view of how we might represent collections &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Collection[t]&lt;/code&gt; as the accumulation of differences &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Difference[t]&lt;/code&gt; over a common ordered set of indices:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Collection[t] = sum_{s &amp;lt;= t} Difference[s]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This feels very safe and intuitive for a total order like the natural numbers: we add up the differences up to and including index &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nothing about this equation requires that the elements be &lt;em&gt;totally&lt;/em&gt; ordered. We can just as easily use a partial order like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(u64, u64)&lt;/code&gt;: pairs of numbers where one is less or equal another exactly when both coordinates are, just like in our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(epoch, iteration)&lt;/code&gt; example above.&lt;/p&gt;

&lt;h3 id=&quot;differential-computation&quot;&gt;Differential computation&lt;/h3&gt;

&lt;p&gt;Our intuition for computation may start to break down without a total order, raising the technical question of “how do we determine the values of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Difference[t]&lt;/code&gt;?” A bit of mathematical re-arrangement takes the implicit definition of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Difference[t]&lt;/code&gt; above to&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Difference[t] = Collection[t] - sum_{s &amp;lt; t} Difference[s]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;which indicates that we can derive differences from the collection at index &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t&lt;/code&gt; and all “earlier” differences. This adds a technical requirement that the partial order be “&lt;a href=&quot;http://en.wikipedia.org/wiki/Well-founded_relation&quot;&gt;well-founded&lt;/a&gt;”, which informally means that we can always find something to start the differencing from. We will make this a lot easier by assuming that our computation is driven by a system (a timely dataflow system, perhaps!) presenting differences in an order so that for each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t&lt;/code&gt; we have already determined and recorded &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Difference[s]&lt;/code&gt; for all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s &amp;lt; t&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This may look very expensive, and of course if we had to compute &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Difference[t]&lt;/code&gt; for every &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t&lt;/code&gt; we would be pretty upset. Fortunately, as with incremental computation, when an operator is presented with new input differences, we can restrict the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t&lt;/code&gt; for which it may possibly produce output differences. Further, our use of partial orders can yield substantially sparser differences, meaning less work to do.&lt;/p&gt;

&lt;p&gt;Like in incremental dataflow, an differential dataflow operator only needs to update the output for keys associated with changed inputs. However, an update at an index &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t&lt;/code&gt; may require outputs at indices other than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t&lt;/code&gt;, which is very much &lt;em&gt;un&lt;/em&gt;-like incremental computation. For the moment, let’s just say that we can compute these indices efficiently, and the number is often not particularly large.&lt;/p&gt;

&lt;h2 id=&quot;implementation&quot;&gt;Implementation&lt;/h2&gt;

&lt;p&gt;Let’s go through a simple implementation of a differentail dataflow operator (in Rust).&lt;/p&gt;

&lt;h3 id=&quot;representing-collections&quot;&gt;Representing collections&lt;/h3&gt;

&lt;p&gt;The first thing to define is a differential representation of a collection. In our simple approach we will just store each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;difference[t]&lt;/code&gt; as a list of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(V, i64)&lt;/code&gt; indicating the change to the count associated with each record. In fact, we’ll just stash all of these in the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&amp;lt;(V, i64)&amp;gt;&lt;/code&gt;, and use a second array to delineate which regions are associated with which indices, of index type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CollectionAsList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;updates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;// value and delta&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;indices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// index and count&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;What do we need to be able to do with this representation? So far, I’ve been using traits for accessing the data as indexed collections or as indexed differences:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Collection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setc&lt;/code&gt; method takes an index and an intended collection, and inserts the correct differences at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index&lt;/code&gt; to ensure that when accumulated the result is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt;. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getc&lt;/code&gt; method populates the supplied &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vec&amp;lt;(V, i64)&amp;gt;&lt;/code&gt; with the accumulated differences, which by definition is the value of the collection at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Difference&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Difference&lt;/code&gt; trait, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setd&lt;/code&gt; method just internalizes the suppied vector of updates and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getd&lt;/code&gt; method just returns the slice corresponding to the requested index.&lt;/p&gt;

&lt;p&gt;The method names have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d&lt;/code&gt; suffixes so that I don’t have to show you Rust’s name resolution. It isn’t that horrible, but it is distracting I’ll probably just end up changing the traits anyhow.&lt;/p&gt;

&lt;p&gt;In my implementation I’m currently using the constraint that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;V: Ord&lt;/code&gt;, meaning we can tell when two instances are the same (important for accumulating differences) and there is some ordering on them. This ordering property is less obviously necessary, but it does allow me to do merge-based accumulation, rather than faking out a hash table inside the arrays or something horrible like that.&lt;/p&gt;

&lt;h3 id=&quot;representing-computations&quot;&gt;Representing computations&lt;/h3&gt;

&lt;p&gt;The next thing to consider is how an operator might be implemented. In the interest of manageable type signatures we will do a unary operator, from collections of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt; to collection of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;R&lt;/code&gt;, indexed by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For each key, we will need to keep track of the accepted source differences, and we will want to keep track of the produced result differences. We will use the following simple structure for per-key differential state:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DifferentialShard&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CollectionAsList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CollectionAsList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Next we’ll want to put these together as an operator, involving a key selection function, some logic to apply to transform source records to result records, and some storage to make it all stick together.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DifferentialOperator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;key_fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;shards&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DifferentialShard&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This isn’t particularly instructive yet. Let’s constrain the types and implement some methods.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DifferentialOperator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;KF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Ord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Ord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;PartialOrd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)],&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;KF&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;K&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// .. rest of implementation broken out below ..&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;These constraints mean that our data &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;R&lt;/code&gt; need to be totally ordered and clonable, and our index &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt; needs to be partially ordered and clonable. We also have types &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KF&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;L&lt;/code&gt; corresponding to closures for our key function and operator logic. The key function must take a reference to a source record and produce a key, the operator logic must take a reference to a key, a reference to source data, and a populatable vector for result data. The logic is asked to work on pairs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(S,i64)&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(R,i64)&lt;/code&gt; in order to make my life easier, rather than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;R&lt;/code&gt; followed by accumulating the counts.&lt;/p&gt;

&lt;p&gt;Next we write a method to accept new input data, not yet grouped by key, which does the partitioning of source records and loads each of the groups into the appropriate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DifferentialShard&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;accept_source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.key_fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;Occupied&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.into_mut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;Vacant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.drain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shard&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.shards&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;Occupied&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.into_mut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;Vacant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;DifferentialShard&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// using orderedness to merge&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;shard&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.source&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.setd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In principle, we could be stashing the input records at the ends of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shard.source.updates&lt;/code&gt; rather than staging them first, but we would need to tweak the interface a bit to expose more than just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setd&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, for now, we write a method to relate the source collection to result collection at a specific index, and to correct the result differences if they do not currently accumulate to the correct result collection.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;update_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp_src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp_dst&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logic&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.logic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shard&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.shards&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;temp_src&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp_dst&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// fetch collection, run logic, compare&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;shard&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.source&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.getc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp_src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp_src&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;logic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp_src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp_dst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;shard&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.setc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp_dst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shard&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.getd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The main possibly surprising part (the part I got wrong the first time) is that we only want to call the logic on non-empty &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;temp_src&lt;/code&gt; collections. This is an important part of correctness, as everything goes horribly wrong if we let &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logic&lt;/code&gt; produce output on empty collections; we don’t want to have to run it for all possible keys, but rather only the ones for which we’ve actually seen data.&lt;/p&gt;

&lt;h2 id=&quot;trying-it-out&quot;&gt;Trying it out!&lt;/h2&gt;

&lt;p&gt;Oh yeah! We wrote all that code, let’s see what it does. Here is an operator which counts the number of distinct strings with each length, producing a weird &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(String, usize)&lt;/code&gt; result pair for each length.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vertex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;DifferentialOperator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(((&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;format!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;length: {:?}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sched&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]),&lt;/span&gt;
                     &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]),&lt;/span&gt;
                     &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]),&lt;/span&gt;
                     &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;Product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;vec!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;b&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)])];&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;differences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sched&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;vertex&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.accept_source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;differences&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;vertex&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.update_result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;println!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;difference[{:?}] : {:?}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Let’s go through the produced output line by line:&lt;/p&gt;

&lt;p&gt;First we see here the changes from the initially empty result collection to the output on the supplied source collection. We have two length-one strings and one length-two string:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;difference[(0, 0)] : [((&quot;length: 1&quot;, 2), 1), ((&quot;length: 2&quot;, 1), 1)]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next we see what happens when we remove the length-one strings: we lose any record of “length: 1”:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;difference[(0, 1)] : [((&quot;length: 1&quot;, 2), -1)]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The next change is in a different coordinate, and doesn’t include the removal of the length-one strings that occurs at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0, 1)&lt;/code&gt;. Rather, it removes one of the two length-one strings and decrements the number of the other by one (though two occurences of “c” should still remain). The result is one distinct length-one string, and the way differential dataflow represents that is to subtract one record and add the other.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;difference[(1, 0)] : [((&quot;length: 1&quot;, 1), 1), ((&quot;length: 1&quot;, 2), -1)]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Although we might like to change the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2&lt;/code&gt; to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; by directly subtracting one, those two values are now part of the data rather than the counts. If they had been formatted into the string instead, it would be clearer that this is not possible.&lt;/p&gt;

&lt;p&gt;Finally, we add back an “a” (as otherwise the total would be negative) and leave ourselves with one “b”. Index &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1,1)&lt;/code&gt; has one length-one string, and so the output is naturally:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;difference[(1, 1)] : [((&quot;length: 1&quot;, 2), 1)]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Yeah, that last one isn’t obvious at all, is it?&lt;/p&gt;

&lt;p&gt;We would like the output to be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[((&quot;length: 1&quot;, 1), 1), ((&quot;length: 2&quot;, 1), 1)]&lt;/code&gt;, and the difference above is the right quantity to add in to make this be the case. We subtracted too many &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[((&quot;length: 1&quot;, 2), 1)]&lt;/code&gt; records, at both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0, 1)&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1, 0)&lt;/code&gt;, and need to put one back.&lt;/p&gt;

&lt;h2 id=&quot;things-yet-to-do&quot;&gt;Things yet to do&lt;/h2&gt;

&lt;p&gt;The main issue that this example avoids is knowing on which indices to call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vertex.update_result&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, even if we had not had any input differences at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1, 1)&lt;/code&gt;, which would be odd as the counts for both “a” and “b” would go negative, we would still need to call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update_result&lt;/code&gt;. The input difference at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1, 0)&lt;/code&gt; introduces the need to at least check the output at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1, 1)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we just call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update_result&lt;/code&gt; at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1, 1)&lt;/code&gt;, without adding source differences, we get the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;difference[(1, 1)] : [((&quot;length: 1&quot;, 1), -1), ((&quot;length: 1&quot;, 2), 2)]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here we are told that we have too many reports of one length-one string, and need multiple reports of two length-one strings. This is because we were already short one such report, and there are now two length-one strings (though with negative counts; not something we paid attention to in our logic).&lt;/p&gt;

&lt;p&gt;Roughly, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;difference[t]&lt;/code&gt; can lead to result differences at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t&lt;/code&gt; joined (in the partial order sense of least upper bound) with the join of any subset of pre-existing indices with non-empty differences.&lt;/p&gt;

&lt;p&gt;We should also be more careful about noting the subset of keys that need updating at a given index (we currently update  them all. Naughty!). This isn’t much more complicated than maintaining a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HashMap&amp;lt;T, HashSet&amp;lt;K&amp;gt;&amp;gt;&lt;/code&gt;, populated by the join logic described above.&lt;/p&gt;

&lt;p&gt;None of this is too complicated to compute, but stitching it all together properly requires something like a timely dataflow system, where one can respond to source differences with a combination of result differences and notification requests (for checking possible result updates at future indices).&lt;/p&gt;

&lt;p&gt;Ideally the Rust ICE blocking me wiring all this together gets sorted out promptly, otherwise I will be forced to delight you with a post about worst-case optimal join processing in timely dataflow.&lt;/p&gt;
</description>
        <pubDate>Tue, 07 Apr 2015 14:00:00 +0000</pubDate>
        <link>http://www.frankmcsherry.org/differential/dataflow/2015/04/07/differential.html</link>
        <guid isPermaLink="true">http://www.frankmcsherry.org/differential/dataflow/2015/04/07/differential.html</guid>
        
        
        <category>differential</category>
        
        <category>dataflow</category>
        
      </item>
    
      <item>
        <title>Bigger data; same laptop</title>
        <description>&lt;p&gt;This post follows up the previous post, &lt;a href=&quot;http://www.frankmcsherry.org/graph/scalability/cost/2015/01/15/COST.html&quot;&gt;“Scalability! But at what COST?”&lt;/a&gt;, which got a great response. The short version of the previous post is that for the graph datasets and computations the scalable systems research community is currently looking at, a laptop outperforms the scalable systems.&lt;/p&gt;

&lt;p&gt;There were several flavors of response to the post (many of which were very supportive!), but one that I’d like to address in this post is&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;One billion edges is just a few gigabytes; it’s hardly “big data” …&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My personal opinion is that if your system doesn’t perform well on one billion things, you don’t yet have the credibility to claim it’s going to work much better on a trillion things. But let’s see where this goes…&lt;/p&gt;

&lt;p&gt;The only reported data I know of for graph processing at the “one trillion things” scale is &lt;a href=&quot;https://www.facebook.com/notes/facebook-engineering/scaling-apache-giraph-to-a-trillion-edges/10151617006153920&quot;&gt;Facebook’s processing of a one trillion edge graph&lt;/a&gt;, where they got the per-iteration pagerank time down to under four minutes on 200 commodity machines (number of cores unknown to me). That’s really quite good (and, much better than Giraph performed in GraphX’s evaluation).&lt;/p&gt;

&lt;p&gt;I don’t have a trillion edge graph, unfortunately, but thanks to the nice people at &lt;a href=&quot;http://webdatacommons.org&quot;&gt;Web Data Commons&lt;/a&gt;, I was able to download a &lt;a href=&quot;http://webdatacommons.org/hyperlinkgraph/index.html&quot;&gt;128 billion edge graph&lt;/a&gt;, based on the hyperlink structure from &lt;a href=&quot;http://commoncrawl.org&quot;&gt;Common Crawl&lt;/a&gt;. Using the same algorithms from the previous post yields a new (mostly empty) column:&lt;/p&gt;

&lt;center&gt;
&lt;table border=&quot;1&quot; style=&quot;width:75%&quot;&gt;
&lt;tr&gt; &lt;th&gt;System&lt;/th&gt; &lt;th&gt;cores&lt;/th&gt; &lt;th&gt;twitter_rv&lt;/th&gt; &lt;th&gt;uk_2007_05&lt;/th&gt;&lt;th&gt;common crawl&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Spark&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;857s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1759s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;unknown&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Giraph&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;596s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1235s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;unknown&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;GraphLab&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;249s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;833s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;unknown&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;GraphX&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;419s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;462s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;unknown&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Single thread&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;242s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;256s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;46600s*&lt;/td&gt;&lt;/tr&gt;
&lt;caption&gt;Twenty pagerank iterations&lt;/caption&gt;
&lt;/table&gt;
&lt;/center&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;*: An earlier version had 23,653 seconds as the number for pagerank. Unfortunately, the code was set to do only ten iterations (in order to compare to GraphX, whose scaling measurements are for only ten iterations). We apologize for the error.&lt;/p&gt;

&lt;center&gt;
&lt;table border=&quot;1&quot; style=&quot;width:75%&quot;&gt;
&lt;tr&gt; &lt;th&gt;System&lt;/th&gt; &lt;th&gt;cores&lt;/th&gt; &lt;th&gt;twitter_rv&lt;/th&gt; &lt;th&gt;uk_2007_05&lt;/th&gt;&lt;th&gt;common crawl&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Spark&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1784s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;8000s+&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;unknown&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Giraph&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;200s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;8000s+&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;unknown&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;GraphLab&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;242s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;714s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;unknown&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;GraphX&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;251s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;800s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;unknown&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Single thread&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;15s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;30s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;1700s&lt;/td&gt;&lt;/tr&gt;
&lt;caption&gt;Graph connectivity&lt;/caption&gt;
&lt;/table&gt;
&lt;/center&gt;

&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Importantly, these algorithms are &lt;em&gt;exactly&lt;/em&gt; the same as in the previous post. Nothing has been changed, other than (a few sections down) a different on-disk representation of the graph so that it fit on my SSD. The edges per second are mostly within a factor of two across the graphs; this approach even scales!&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Note&lt;/b&gt;: The single-thread numbers for pagerank on the smaller graphs are not the same as in the previous post. Because the common crawl dataset is so large, I was only able to process it in Hilbert curve order. It seemed appropriate to use the corresponding numbers for the smaller graphs, rather than the vertex order numbers.&lt;/p&gt;

&lt;h3 id=&quot;a-cost-challenge-for-scalable-systems&quot;&gt;A COST challenge for scalable systems&lt;/h3&gt;

&lt;p&gt;I’d like to challenge the teams behind the existing graph processing platforms to evaluate and report their COST (Configuration that Outperforms a Single Thread) for this dataset. The dataset is nearly two orders of magnitude larger than those most currently use for system evaluation, and yet still nearly one order of magnitude &lt;em&gt;smaller&lt;/em&gt; than can be easily processed by my laptop. It is much “bigger” data than we’ve been using previously, and it is available for everyone to download, so get to it!&lt;/p&gt;

&lt;p&gt;The only partial data we have is for Giraph, for which 200 machines is more than enough.
I don’t think we know very much about any other scalable graph processing system. Let’s fix that!&lt;/p&gt;

&lt;p&gt;I’ll happily post updates for performance numbers on specific systems. To make it fun, let’s say that each submission gets to name another system whose row is “unknown” until its measurements arrive.&lt;/p&gt;

&lt;h2 id=&quot;implementation-notes&quot;&gt;Implementation notes&lt;/h2&gt;

&lt;p&gt;There were some relatively minor implementation details involved in getting 128 billion edges onto my laptop, and then processed efficiently (which isn’t to say that I got them right the first time). Doing the math, if one writes down a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(u32, u32)&lt;/code&gt; for each edge, this ends up at a terabyte of data, and my laptop doesn’t have that kind of SSD. So, we’ll have to be a bit smarter.&lt;/p&gt;

&lt;p&gt;Using the Hilbert curve layout leads to a very simple compressed representation: having sorted the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u64&lt;/code&gt; values it uses to represent edges, one can simply delta-encode them (writing the differences between adjacent values). Because the Hilbert curve teases out locality, the gaps are often quite small, and therefore compress nicely. When I combined a variable-length integer encoding with gzip, the resulting file was only 45GB; about 2.8 bits per edge). Decompressed (with just the variable-length delta encoding), it is 154GB, which is the representation I used to get the measurements above.&lt;/p&gt;

&lt;p&gt;Not only is the Hilbert curve good for compression, it is pretty much mandatory due to the locality it provides when accessing the per-vertex state. Random access into main memory is not all that bad, but when the working set for vertex state in pagerank is 40GB, random access turns in to thrashing. This is not only slow, but it wears out my SSD, and no one wants that. The near-linear performance scaling as we went up two orders of magnitude in size is evidence for how cool the Hilbert curve is.&lt;/p&gt;

&lt;p&gt;Transforming the data to the compressed Hilbert curve encoding was relatively efficient. My laptop was able to transform (encode, sort, and compress) the data faster than I could download it, so I didn’t work too hard to optimize the performance. However, this is an excellent example of where parallelism can help: each of the &lt;a href=&quot;http://webdatacommons.org/hyperlinkgraph/2012-08/data/arc.list.txt&quot;&gt;700 files comprising the graph&lt;/a&gt; could be processed independently (and I did fire up a second process on occasion). If you had 100 cores (like most of the scalable systems use) the whole conversion process would just take a few minutes.&lt;/p&gt;

&lt;p&gt;The implementation is available in the &lt;a href=&quot;https://github.com/frankmcsherry/COST&quot;&gt;COST repository&lt;/a&gt;, though the documentation is not yet brilliant. If you have questions, especially if you’d like to be doing this sort of thing in your scalable system and want pointers, just shoot me an email.&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;

&lt;p&gt;I’m not going to try this nonsense on any larger graphs. The ball is now in the court of the scalable systems. We’ll just have to wait and see how they do before setting any stretch performance goals.&lt;/p&gt;

&lt;p&gt;Instead, it’s time to get back to work on &lt;a href=&quot;https://github.com/frankmcsherry/timely-dataflow&quot;&gt;timely dataflow in Rust&lt;/a&gt;! Stay tuned for that kind of post.&lt;/p&gt;
</description>
        <pubDate>Wed, 04 Feb 2015 14:00:00 +0000</pubDate>
        <link>http://www.frankmcsherry.org/graph/scalability/cost/2015/02/04/COST2.html</link>
        <guid isPermaLink="true">http://www.frankmcsherry.org/graph/scalability/cost/2015/02/04/COST2.html</guid>
        
        
        <category>graph</category>
        
        <category>scalability</category>
        
        <category>cost</category>
        
      </item>
    
      <item>
        <title>Scalability! But at what COST?</title>
        <description>&lt;p&gt;Michael Isard, Derek Murray, and I recently sent in a &lt;a href=&quot;https://www.usenix.org/conference/hotos15&quot;&gt;HotOS&lt;/a&gt; submission (it’s not blind, so no harm talking about it, we think). The subject is hinted at from the post title (stolen from the paper title):&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Big data systems may scale well, but this can often be just because they introduce a lot of overhead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rather than making your computation go faster, the systems introduce substantial overheads which can require large compute clusters just to bring under control.&lt;/p&gt;

&lt;p&gt;In many cases, you’d be better off running the same computation on your laptop.&lt;/p&gt;

&lt;h2 id=&quot;methodology&quot;&gt;Methodology&lt;/h2&gt;

&lt;p&gt;Here is the set-up: we took several recent graph-processing publications from the systems community, and compared the measurements they report to simple single-threaded implementations running on my work laptop (RIP). We wrote competent implementations, but we didn’t obsess deeply over fancy algorithms, cunning data-dependent tweaks, or what have you. I’ll show you the code, and you decide.&lt;/p&gt;

&lt;p&gt;We evaluated &lt;a href=&quot;http://en.wikipedia.org/wiki/PageRank&quot;&gt;PageRank&lt;/a&gt; (20 iterations) and &lt;a href=&quot;http://en.wikipedia.org/wiki/Connected_component_(graph_theory)&quot;&gt;graph connectivity&lt;/a&gt; on two graphs, &lt;a href=&quot;http://an.kaist.ac.kr/traces/WWW2010.html&quot;&gt;twitter_rv&lt;/a&gt; and &lt;a href=&quot;http://law.di.unimi.it/webdata/uk-2007-05/&quot;&gt;uk_2007_05&lt;/a&gt;. We chose these algorithms and datasets mostly because a recent OSDI 2014 paper &lt;a href=&quot;https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-gonzalez.pdf&quot;&gt;GraphX&lt;/a&gt; used exactly these to evaluate several of the top systems, and we wanted to borrow their numbers rather than try to reproduce them on systems we aren’t expert with.&lt;/p&gt;

&lt;p&gt;As a caveat, these algorithms are quite specific to graph processing, and the data sets are not large (billions of edges, but still just a few gigabytes). Our conclusion is not that the systems we are going to look at are obviously bad, but rather that there is not yet much evidence that they are especially good.&lt;/p&gt;

&lt;p&gt;The point we try to make in the HotOS submission is that evaluation of these systems, especially in the academic context, is lacking. Folks have gotten all wound-up about scalability, despite the fact that scalability is just a means to an end (performance, capacity). When we actually look at performance, the benefits the scalable systems bring start to look much more sketchy. We’d like that to change.&lt;/p&gt;

&lt;h2 id=&quot;measurements&quot;&gt;Measurements&lt;/h2&gt;

&lt;p&gt;All measurements other than the “single thread” lines are from the GraphX paper linked above. I’ll talk about the single-threaded implementations soon, but the results for the most direct implementations of PageRank and label propagation (the algorithm the systems use for graph connectivity) look like this:&lt;/p&gt;

&lt;center&gt;
&lt;table border=&quot;1&quot; style=&quot;width:75%&quot;&gt;
&lt;tr&gt; &lt;th&gt;System&lt;/th&gt; &lt;th&gt;cores&lt;/th&gt; &lt;th&gt;twitter_rv&lt;/th&gt; &lt;th&gt;uk_2007_05&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Spark&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;857s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1759s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Giraph&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;596s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1235s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;GraphLab&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;249s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;833s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;GraphX&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;419s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;462s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Single thread&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;300s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;651s&lt;/td&gt; &lt;/tr&gt;
&lt;caption&gt;Twenty pagerank iterations&lt;/caption&gt;
&lt;/table&gt;
&lt;/center&gt;

&lt;p&gt;&lt;/p&gt;

&lt;center&gt;
&lt;table border=&quot;1&quot; style=&quot;width:75%&quot;&gt;
&lt;tr&gt; &lt;th&gt;System&lt;/th&gt; &lt;th&gt;cores&lt;/th&gt; &lt;th&gt;twitter_rv&lt;/th&gt; &lt;th&gt;uk_2007_05&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Spark&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1784s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;8000s+&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Giraph&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;200s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;8000s+&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;GraphLab&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;242s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;714s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;GraphX&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;128&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;251s&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;800s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Single thread&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;153s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;417s&lt;/td&gt; &lt;/tr&gt;
&lt;caption&gt;Label propagation to fixed-point (graph connectivity)&lt;/caption&gt;
&lt;/table&gt;
&lt;/center&gt;

&lt;p&gt;&lt;/p&gt;
&lt;p&gt;These single-threaded numbers paint the scalable systems in a pretty dismal light. With 128x as many cores, none of the scalable systems consistently out-perform a single thread at PageRank, which is probably one of the simplest graph computations (sparse matrix-vector multiplications). The systems are almost a factor of two slower than the single-threaded implementation for label propagation.&lt;/p&gt;

&lt;p&gt;Unfortunately, it is only going to get worse for the scalable systems as our investigation continues.&lt;/p&gt;

&lt;h2 id=&quot;implementation&quot;&gt;Implementation&lt;/h2&gt;

&lt;p&gt;Let’s talk a bit about what the implementations look like, so that we can figure out whether we are being totally unfair. First, the measurements were taken on a laptop I no longer possess. I’ve reproduced most of the code in &lt;a href=&quot;http://www.rust-lang.org&quot;&gt;Rust&lt;/a&gt; on my new laptop, and the results are mostly the same (some faster, some slower). The measurements above are from a C# implementation, so this isn’t a “Rust is super fasterest” issue. The code is available on GitHub, including both &lt;a href=&quot;https://github.com/MicrosoftResearch/NaiadSamples/tree/master/COST&quot;&gt;the C# version&lt;/a&gt; and &lt;a href=&quot;https://github.com/frankmcsherry/COST&quot;&gt;the Rust version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The algorithms we are talking about are all based on iterative scans of the edges in a graph. To support these algorithms, we are going to produce a few implementations of the following trait:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EdgeMapper&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map_edges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FnMut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map_edges&lt;/code&gt; method is generic with respect to a closure. It’s a mouthful, but it means &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action&lt;/code&gt; can be called multiple times on inputs of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(u32, u32)&lt;/code&gt;,  edges, and may modify its environment. This will probably make more sense once we see it used.&lt;/p&gt;

&lt;p&gt;One nice thing about Rust is that each closure corresponds to a new type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F&lt;/code&gt;, and a new specialized instance of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map_edges&lt;/code&gt;, inlining the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action&lt;/code&gt; method. In C# I had to manually unroll some loops. Ew.&lt;/p&gt;

&lt;h3 id=&quot;algorithms&quot;&gt;Algorithms&lt;/h3&gt;

&lt;p&gt;Let’s look at how PageRank gets implemented with respect to such a trait:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pagerank&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EdgeMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0f32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.collect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0f32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.collect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;f32&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0f32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.collect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.map_edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1f32&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_iteration&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;..&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;deg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;dst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1f32&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.map_edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I’m not going to say very much about whether this is a good pagerank implementation. There are &lt;a href=&quot;http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.59.5449&quot;&gt;better versions&lt;/a&gt;, but everyone uses this algorithm for benchmarking
to ensure they are measuring their system’s performance, not the algorithm’s. This is textbook PageRank, no bells, no whistles.&lt;/p&gt;

&lt;p&gt;Label propagation is basically just as simple. For those of you unfamiliar with the algorithm, each node maintains a label, and repeatedly improves it by asking their neighbors if they have a smaller label. This is run until convergence, at which point all nodes in the same component have the same label.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label_propagation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EdgeMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.collect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.map_edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;These are pretty simple implementations. No tricks. Let’s look at how we implement an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EdgeMapper&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;edgemapper-implementation&quot;&gt;EdgeMapper implementation&lt;/h3&gt;

&lt;p&gt;Our first EdgeMapper implementation will be based on an adjacency list representation, which we will just map into memory and iterate over. There will be a smarter one later on.&lt;/p&gt;

&lt;p&gt;We’ll assume we have two files, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;graph.nodes&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;graph.edges&lt;/code&gt;, where&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;graph.nodes&lt;/code&gt; contains a sequence of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(u32, u32)&lt;/code&gt; pairs representing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(node_id, degree)&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;graph.edges&lt;/code&gt; contains a sequence of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u32&lt;/code&gt; values representing edge endpoints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To enumerate the edges, we iterate through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;graph.nodes&lt;/code&gt;, where for each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(node_id, degree)&lt;/code&gt; pair we read &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;degree&lt;/code&gt; values from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;graph.edges&lt;/code&gt; and then pair each with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_id&lt;/code&gt; to form an edge.&lt;/p&gt;

&lt;p&gt;Ignoring the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TypedMemoryMap&lt;/code&gt; for the moment (it’s 32 lines of wrapper around Rust’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MemoryMap&lt;/code&gt;),
the mapper structure and its implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EdgeMapper&lt;/code&gt; are:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NodesEdgesMapper&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;TypedMemoryMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;TypedMemoryMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EdgeMapper&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NodesEdgesMapper&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map_edges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FnMut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;edge&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;.edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.iter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nf&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;edge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That’s all! (except for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TypedMemoryMap&lt;/code&gt; type, available in the repository).&lt;/p&gt;

&lt;h2 id=&quot;getting-smarter&quot;&gt;Getting smarter&lt;/h2&gt;

&lt;p&gt;Perhaps the single-threaded implementations above are a bit too simple. There are some very easy modifications that result in significantly improved performance. Remember how the simple single-threaded implementations were on-par with or maybe slightly better than the scalable systems? Here are the improved results using smarter algorithms (still from my old laptop and C#):&lt;/p&gt;

&lt;center&gt;
&lt;table border=&quot;1&quot; style=&quot;width:75%&quot;&gt;
&lt;tr&gt; &lt;th&gt;System&lt;/th&gt; &lt;th&gt;cores&lt;/th&gt; &lt;th&gt;twitter_rv&lt;/th&gt; &lt;th&gt;uk_2007_05&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Single thread (simple)&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;300s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;651s&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Single thread (smarter)&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;110s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;256s&lt;/td&gt; &lt;/tr&gt;
&lt;caption&gt;Twenty pagerank iterations&lt;/caption&gt;
&lt;/table&gt;
&lt;/center&gt;

&lt;p&gt;&lt;/p&gt;

&lt;center&gt;
&lt;table border=&quot;1&quot; style=&quot;width:75%&quot;&gt;
&lt;tr&gt; &lt;th&gt;System&lt;/th&gt; &lt;th&gt;cores&lt;/th&gt; &lt;th&gt;twitter_rv&lt;/th&gt; &lt;th&gt;uk_2007_05&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Single thread (simple)&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;153s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;417s&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td&gt;Single thread (smarter)&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;1&lt;/td&gt; &lt;td align=&quot;right&quot;&gt;15s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;30s&lt;/td&gt; &lt;/tr&gt;
&lt;caption&gt;Label propagation to fixed-point (graph connectivity)&lt;/caption&gt;
&lt;/table&gt;
&lt;/center&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;There are two types of improvement, processing edges in different orders and using better algorithms, that put the single-threaded implementations so far ahead of the scalable systems.&lt;/p&gt;

&lt;h3 id=&quot;different-data-layout&quot;&gt;Different data layout&lt;/h3&gt;

&lt;p&gt;The edge order used above processes all edges from one vertex before moving to the next vertex. Notice that neither of the algorithms need this to hold to be correct. A different ordering, one which exhibits much better cache locality, is the &lt;a href=&quot;http://en.wikipedia.org/wiki/Hilbert_curve&quot;&gt;Hilbert curve&lt;/a&gt; ordering. Informally, it is like ordering edges (x,y) by the interleaving of the bits of x and y: you trade the great locality in the x coordinates to upgrade the poor locality in the y coordinate. You get (good, good) locality instead of (great, poor).&lt;/p&gt;

&lt;p&gt;Visually, we can think of ordering pairs (x,y) as drawing a continuous line (a space-filling curve) through a 2d square. An initially square region is decomposed into four squares, each of which will be filled completely before moving to the next square. Notice that the sequence 0, 1, 2, 3 moves only to adjacent squares. The process is applied recursively within each square to order elements within, and even at the finer granularity the movements are only to adjacent squares, even when moving between squares at the coarser granularity. The ordering (curve) we produce is continuous, and very localized.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.frankmcsherry.org/assets/hilbert.png&quot; alt=&quot;My helpful screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The Hilbert implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EdgeMapper&lt;/code&gt; is similar to our prior implementation, with some minor layout changes. We start from two files:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;graph.upper&lt;/code&gt; contains a sequence of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;((u16, u16), u32)&lt;/code&gt; values, each a 65536x65536 square indicating the upper 16 bits of x and y, followed by the number of edges in the square. These entries are ordered by the order their corresponding squares are visited by the curve.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;graph.lower&lt;/code&gt; contains a sequence of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(u16, u16)&lt;/code&gt; values for the lower 16 bits for each edge. These entries are ordered by the the order the curve visits them when traversing their square.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EdgeMapper&lt;/code&gt; implementation is basically identical to the implementation for vertex-order above, but results in improved PageRank running times. One lesson is that PageRank is essentially memory-bound; the time is spent either thrashing the TLB or waiting for results from the memory controller, rather than the CPU or disk. It scales because each machine offers an indepedent memory controller. Instead, you can partially fix the problem by re-arranging memory accesses like we have done here.  You could also use &lt;a href=&quot;http://en.wikipedia.org/wiki/Page_(computer_memory)#Huge_pages&quot;&gt;huge pages&lt;/a&gt; (the C# code does), which I can’t do yet on OS X.&lt;/p&gt;

&lt;p&gt;We should also admit that the Hilbert curve ordering is not typically how graphs are presented, and consequently there would be some work involved in transforming your input data to this ordering. If you are only planning on doing one computation, it may not be worth the effort. Fortunately, a single-threaded implementation takes less time to re-order the edges (about 180s in Rust) than any of the systems above take to perform 20 iterations of PageRank. It’s included in the repository.&lt;/p&gt;

&lt;p&gt;Both GraphLab and GraphX do their own data layout as part of pre-processing. One of the layouts they consider resembles the Hilbert ordering: partitioning vertices into k parts and sending sub-squares of edges to k^2 workers. The Hilbert-curve ordering is totally compatible with their system designs, and they could probably speed up a bit if they used it. It is somewhat in conflict with their approaches to sparse updates (when you only want to process a subset of vertices), trading latency in the sparse-update case for throughput in the (often more expensive) bulk-processing case.&lt;/p&gt;

&lt;h3 id=&quot;different-algorithms&quot;&gt;Different algorithms&lt;/h3&gt;

&lt;p&gt;The label propagation algorithm is not commonly taught as a good algorithm for graph connectivity. It has poor asymptotic bounds, even when compared with more traditional &lt;a href=&quot;http://en.wikipedia.org/wiki/Borůvka&apos;s_algorithm&quot;&gt;algorithms from 90 years ago&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Consider the standard &lt;a href=&quot;http://en.wikipedia.org/wiki/Disjoint-set_data_structure&quot;&gt;union-find algorithm&lt;/a&gt;, which maintains a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt; variable for each vertex, and scans the edges once, merging roots when it finds edges whose endpoints have different roots. We use the “weighted-union” variation, where the merge pays attention to how deep each root is when determining which way to swing the pointers (this gives it an O(m log n) asymptotic complexity).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;union_find&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EdgeMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;G&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u32&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.collect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rank&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nodes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0u8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.collect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;graph&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.map_edges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(|&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rank&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.cmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rank&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;Less&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;Greater&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;Equal&lt;/span&gt;   &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rank&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is one more line of code than label propagation, but it is 10x faster and 100x less embarassing.&lt;/p&gt;

&lt;p&gt;The union-find algorithm is fundamentally incompatible with the graph computation approaches Giraph, GraphLab, and GraphX put forward (the so-called “think like a vertex” model). That doesn’t stop it from being way better than label propagation, and even &lt;a href=&quot;http://theory.stanford.edu/~sergei/papers/spaa11-matchings.pdf&quot;&gt;parallelizable&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;important-things&quot;&gt;Important things&lt;/h2&gt;

&lt;p&gt;Several factors affect the performance we’ve seen above. Let’s see where the differences come from.&lt;/p&gt;

&lt;p&gt;To get consistent measurements despite using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mmap&lt;/code&gt;, each of these numbers are from a second run. This means that the data are effectively “in memory”. The first runs are sketchier and more variable.
We’ll use graph connectivity as our example, from label propagation to our fastest union-find, running on my laptop (a 2014 MacBook pro) using Rust implementations.&lt;/p&gt;

&lt;table style=&quot;width:75%&quot;&gt;
&lt;tr&gt; &lt;td align=&quot;right&quot;&gt;150.6s&lt;/td&gt; &lt;td&gt;:&lt;/td&gt; &lt;td&gt; Label propagation and vertex ordering&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td align=&quot;right&quot;&gt;31.1s&lt;/td&gt; &lt;td&gt;:&lt;/td&gt; &lt;td&gt; Switching to Union Find&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td align=&quot;right&quot;&gt;16.0s&lt;/td&gt; &lt;td&gt;:&lt;/td&gt; &lt;td&gt; Switching to Hilbert-curve ordering&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td align=&quot;right&quot;&gt;11.8s&lt;/td&gt; &lt;td&gt;:&lt;/td&gt; &lt;td&gt; Using unsafe indexing for roots&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td align=&quot;right&quot;&gt;8.7s&lt;/td&gt; &lt;td&gt;:&lt;/td&gt; &lt;td&gt; Alternate EdgeMapper with smaller memory footprint (in the repo)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt; &lt;td align=&quot;right&quot;&gt;8.0s&lt;/td&gt; &lt;td&gt;:&lt;/td&gt; &lt;td&gt; Pre-fetching root[x] and root[y] before the while loop.&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Note, these numbers differ from those reported above due to being a totally different implementation. Mainly, the first measurements took advantage of large pages, which do not seem to be exist on OS X. This benefit is largely recouped by using the Hilbert ordering, which also eases pressure on the TLB. The custom EdgeMapper re-uses a small buffer, and likely messes up the TLB less than mmap does.&lt;/p&gt;

&lt;h2 id=&quot;take-aways&quot;&gt;Take-aways&lt;/h2&gt;

&lt;p&gt;Lots of people struggle with the complexities of getting big data systems up and running, when they possibly shouldn’t be using the systems in the first place. The data sets above are certainly not small (billions of edges), but still run just fine on a laptop. Much faster than the distributed systems, at least.&lt;/p&gt;

&lt;p&gt;Here are two helpful guidelines (for largely disjoint populations):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;If you are going to use a big data system for yourself, see if it is faster than your laptop.&lt;/li&gt;
  &lt;li&gt;If you are going to build a big data system for others, see that it is faster than my laptop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We implemented a few of these algorithms in &lt;a href=&quot;http://research.microsoft.com/Naiad/&quot;&gt;Naiad&lt;/a&gt; and were able to get performance that beats the single thread. Admittedly, it took some work for &lt;a href=&quot;http://theory.stanford.edu/~sergei/papers/spaa11-matchings.pdf&quot;&gt;parallel union-find&lt;/a&gt;, but we learned quite a bit and made something better as a result (delightfully: we didn’t have to change Naiad itself; just application logic).&lt;/p&gt;

&lt;p&gt;We think everyone should have to do this, because it leads to better systems and better research.&lt;/p&gt;

&lt;p&gt;Of course, there are some reasons why you might expect some overheads for your distributed system. The machines Amazon provides as VMs are differently spec’ed than my laptop, which targets peak single-threaded performance. GraphX uses hash tables (which can add an order of magnitude to the critical path) rather than dense arrays, to maintain its per-vertex state, to provide a layer of robustness. Fault-tolerance calls for data to be pushed from fast random access memory out to stable storage to insure against lost work in the case of a failure.&lt;/p&gt;

&lt;p&gt;At the same time, it is worth understanding which of these features are boons, and which are the tail wagging the dog. We go to EC2 because it is too expensive to meet the hardware requirements of these systems locally and fault-tolerance is only important because we have involved so many machines, at which point we wonder whether the robustness of these hash tables is worth all the additional effort.
 Perhaps, but we should be thinking about it quite a bit harder than we currently are.&lt;/p&gt;
</description>
        <pubDate>Thu, 15 Jan 2015 14:00:00 +0000</pubDate>
        <link>http://www.frankmcsherry.org/graph/scalability/cost/2015/01/15/COST.html</link>
        <guid isPermaLink="true">http://www.frankmcsherry.org/graph/scalability/cost/2015/01/15/COST.html</guid>
        
        
        <category>graph</category>
        
        <category>scalability</category>
        
        <category>cost</category>
        
      </item>
    
      <item>
        <title>Timely dataflow: core concepts</title>
        <description>&lt;p&gt;This is the second post in a series on timely dataflow. The first post overviewed some of the context for timely dataflow, and suggestions at new directions. This post will start to detail some of the moving parts of the new hierarchical approach.&lt;/p&gt;

&lt;p&gt;Our plan is to organize timely dataflow graphs hierarchically, into nested &lt;em&gt;scopes&lt;/em&gt;, each of which can be implemented by other timely dataflow graphs. This has several advantages, mostly due to the layer of abstraction hiding details that other parts of the outer timely dataflow graph need not know. On the other hand, we will need to think carefully about the abstraction boundary to avoid giving up precision.&lt;/p&gt;

&lt;h2 id=&quot;approach&quot;&gt;Approach&lt;/h2&gt;

&lt;p&gt;Whereas Naiad maintained a flat view of the timely dataflow graph, with counts for each (location, timestamp) pair indicating unfinished work, we will need to take a different approach. Rather than present such fine detail about where unfinished work lives to all participants, we will use the hierarchical structure to simplify the information.&lt;/p&gt;

&lt;p&gt;Each scope will &lt;em&gt;project&lt;/em&gt; its unfinished work to its outputs, reporting for each output the timestamps at which messages might possibly emerge. As it performs work, it will communicate progress to the outer timely dataflow graph in the form of changes to the counts for (output, timestamp) pairs.&lt;/p&gt;

&lt;p&gt;Similarly, unfinished work and progress made elsewhere in the graph is first projected to the scope’s inputs before it is communicated to the subgraph.&lt;/p&gt;

&lt;h2 id=&quot;an-example&quot;&gt;An example&lt;/h2&gt;

&lt;p&gt;To take an example we borrow an enormous figure from the &lt;a href=&quot;http://research.microsoft.com/pubs/201100/naiad_sosp2013.pdf&quot;&gt;Naiad paper&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://www.frankmcsherry.org/assets/Pointstamps.png&quot; alt=&quot;My helpful screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In this example we have two contexts, or “scopes” as we’ll call them: the outer “streaming” scope and the inner “loop” scope. While the inner loop scope may progress rapidly, in the outer streaming scope vertex D only needs to be informed when some externally visible progress is made.
Specifically, when the output of the inner loop scope will no longer produce messages bearing a specific timestamp.&lt;/p&gt;

&lt;p&gt;In fact, the outer streaming scope doesn’t need to know much of anything about the inner loop scope, other than it has one input and one output, and whenever it makes externally visible progress.&lt;/p&gt;

&lt;p&gt;Our plan is just to structure the progress tracking mechanisms along these lines, reporting progress upwards having projected out any irrelevant detail and coalesced (and often cancelled) the results.&lt;/p&gt;

&lt;p&gt;Generally, there are other things it will be helpful for the outer scope to know that this figure doesn’t demonstrate especially well. If the inner scope had multiple inputs and outputs, it would be helpful to know which inputs connected which outputs. If the inner scope had an exogenous source of input data, it would need to warn the outer scope to expect it.  We will sort out these details.&lt;/p&gt;

&lt;h2 id=&quot;core-traits&quot;&gt;Core traits&lt;/h2&gt;

&lt;p&gt;Three traits lie at the core of our timely dataflow approach: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Timestamp&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PathSummary&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Scope&lt;/code&gt;.
Each scope has its own timestamp and path summary types, implementing the corresponding traits.&lt;/p&gt;

&lt;h3 id=&quot;the-timestamp-trait&quot;&gt;The Timestamp trait&lt;/h3&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Timestamp&lt;/code&gt; trait represents a type suitable for use as a message timestamp. In Naiad this role was played by a sequence of unsigned integers. We plan to admit more general timestamps, but with some constraints, specifically (from the code):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;PartialOrd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Eq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;PartialEq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Copy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Show&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Send&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&apos;static&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is something of a mess, and strictly speaking not all of the constraints are necessary. Just helpful.&lt;/p&gt;

&lt;p&gt;The most relevant constraint is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PartialOrd&lt;/code&gt;, which provides for methods &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;le&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lt&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ge&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gt&lt;/code&gt;, for comparing two elements of the same type. Types implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Timestamp&lt;/code&gt; must be partially ordered.&lt;/p&gt;

&lt;h3 id=&quot;the-pathsummary-trait&quot;&gt;The PathSummary trait&lt;/h3&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PathSummary&lt;/code&gt; trait is parameterized by a type implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Timestamp&lt;/code&gt;. It indicates how the timestamp should be expected to advance as it moves from one location to another in a timely dataflow graph. It also provides support for combining two summaries (summarizing the concatenated paths).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PathSummary&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;PartialOrd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Eq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Copy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Clone&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Show&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&apos;static&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;results_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;       &lt;span class=&quot;c1&quot;&gt;// advances a timestamp&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;followed_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;// composes two summaries&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Path summaries allow us to translate events at some distant location to the event’s implications locally. If a distant vertex finishes processing the last of its inputs and produces no outputs, which timestamp (or timestamps) should be decremented locally?&lt;/p&gt;

&lt;p&gt;To summarize &lt;em&gt;all&lt;/em&gt; paths from one location to another, we exploit the fact that types implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PathSummary&lt;/code&gt; are partially ordered, and that we are interested in the earliest timestamps that might emerge. Any set of path summaries can be reduced down to those elements that are not strictly less than some other set element, which we facilitate with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Antichain&amp;lt;S&amp;gt;&lt;/code&gt; structure.&lt;/p&gt;

&lt;p&gt;It is likely that the trait will be updated as additional constraints are discovered. For example, it is very likely that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;results_in&lt;/code&gt;  should return either &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Iterator&amp;lt;T&amp;gt;&lt;/code&gt; types rather than single elements. Otherwise, it may be difficult to accommodate bounded loops that discard messages once their timestamp exceeds the loop bound. Other similar unsupported patterns likely exist.&lt;/p&gt;

&lt;h3 id=&quot;the-scope-trait&quot;&gt;The Scope trait&lt;/h3&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Scope&lt;/code&gt; trait is where the pieces start to come together. A scope represents an element in a timely dataflow graph, as seen from the outside world (or its parent scope). A scope is parameterized by a timestamp type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt; and a path summary type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt;. To implement the trait, a type must provide several methods, which we will soon detail (some diagnostic methods are elided).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Scope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PathSummary&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;&apos;static&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;uint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;// number of inputs&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;outputs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;uint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// number of outputs&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// get and set summary information as part of set-up.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_internal_summary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Antichain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                           &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;set_external_summary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;summaries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Antichain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                       &lt;span class=&quot;n&quot;&gt;external&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// push and pull progress information at run-time.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;push_external_progress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;external&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;pull_internal_progress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;internal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                         &lt;span class=&quot;n&quot;&gt;consumed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                         &lt;span class=&quot;n&quot;&gt;produced&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This definition is something of a mouthful, but I hope by the end of the post the reasons for each of the parts will become clearer.&lt;/p&gt;

&lt;h4 id=&quot;inputs-and-outputs&quot;&gt;Inputs and outputs&lt;/h4&gt;

&lt;p&gt;Each scope is, to the outside world, a vertex that consumes and produces messages timestamped by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt;. The first thing the outside world will need to know is the shape of the scope: how many inputs does it have, and how many outputs does it have. While not conceptually deep, all further communication between the scope and the outer world will be in terms of these inputs and outputs.&lt;/p&gt;

&lt;h4 id=&quot;initialization&quot;&gt;Initialization&lt;/h4&gt;

&lt;p&gt;Two methods are used as part of initializing the computation, the scope summarizing itself to the outer world, and the outer world summarizing itself back to the scope. Because of the hierarchical structure, scopes first summarize themselves to their parent scope, who eventually has enough information to summarize the structure around the scope back to the scope itself.&lt;/p&gt;

&lt;p&gt;These summaries include both what the scope (and the outer world) are capable of doing to messages received at their inputs in terms of messages produced at their outputs, and also any initial counts of timestamped messages at each output.&lt;/p&gt;

&lt;p&gt;The first method is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_internal_summary&lt;/code&gt;, which asks the scope for path summaries for each of its (input, output) pairs, as well as initial counts at each of its outputs for each timestamp.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_internal_summary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Antichain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                       &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The result is simply the pair of summaries for each input to each output, and the list for each output of increments to counts for each timestamp.&lt;/p&gt;

&lt;p&gt;The second method is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set_internal_summary&lt;/code&gt;, which reports back to the scope the path summaries from each of its outputs back to its inputs, as well as the initial counts for each of its inputs.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;set_external_summary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;summaries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Antichain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                   &lt;span class=&quot;n&quot;&gt;external&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The two arguments are the counterparts to the returned values above: path summaries from each of the scope’s outputs to each of the scope’s inputs, and initial counts at each of the scope’s inputs.&lt;/p&gt;

&lt;h4 id=&quot;runtime&quot;&gt;Runtime&lt;/h4&gt;

&lt;p&gt;Once the computation has begun, the outer world interacts with the scope in two ways: it notifies the scope of progress made in the outer world, and it requests information about progress made in the scope itself. These are the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;push_external_progress&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pull_internal_progress&lt;/code&gt; methods.&lt;/p&gt;

&lt;p&gt;The first of these two methods is relatively simple: it communicates to the scope a list of updates to timestamp counts for each of its inputs. These updates communicates progress in the outer world projected down to the scope’s inputs.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;push_external_progress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;external&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The method does not return anything, reflecting our desire to have this be asynchronous communication: the notification is not coupled with a response, it is simply sent to the scope from the outer world with the hope that progress will be made in the future.&lt;/p&gt;

&lt;p&gt;The second method is slightly more complicated, at least in signature. Its role is to retrieve information from the scope about progress it has made, so that the system as a whole may advance.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;pull_internal_progress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;internal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                     &lt;span class=&quot;n&quot;&gt;consumed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                     &lt;span class=&quot;n&quot;&gt;produced&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Vec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The arguments are vectors meant to be populated, indicating (respectively) the changes to any internal work projected to the scope’s outputs, the numbers of messages consumed at each of the scope’s inputs, and the numbers of messages produced at each of the scope’s outputs. The method returns a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bool&lt;/code&gt; indicating whether there is unreported work that may not result in output messages that should prevent the computation from shutting down (for example, vertices with side-effects but no outputs).&lt;/p&gt;

&lt;p&gt;This method collects three types of updates together because it is important for correctness that two types of updates occur together:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Consuming an input message resulting in more internal work (consuming -&amp;gt; internal).&lt;/li&gt;
  &lt;li&gt;Producing an output message resulting in less internal work (internal -&amp;gt; producing).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If either of these two become decoupled, it is possible the outer world may have an incomplete (and incorrect) view of the state of the scope. For example, if the scope acknowledges consuming an input message but does not indicate the new internal work, the outer scope may reasonably assume there is none, and communicate incorrect progress information to others. Likewise, if internal work is retired producing an output message, there is no reason to believe the outside world will know about the message in a timely fashion unless told by the scope itself.&lt;/p&gt;

&lt;p&gt;While we might have broken the above into two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pull_internal&lt;/code&gt; methods, there is a nice symmetry in having one method for each direction of information flow. Moreover, there does not yet seem to be an advantage in soliciting only a subset of progress information from a scope. The scope can report what it wants to report.&lt;/p&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;

&lt;p&gt;We’ve described the interfaces currently in place for our hierarchical implementation of timely dataflow. There is still a lot to say about how to implement scopes, especially the generic logic for a scope which wraps a connected collection of other scopes. We’ll talk about this next, and almost certainly surface details that are still up for discussion.&lt;/p&gt;
</description>
        <pubDate>Mon, 29 Dec 2014 16:00:00 +0000</pubDate>
        <link>http://www.frankmcsherry.org/dataflow/naiad/2014/12/29/TD_time_summaries.html</link>
        <guid isPermaLink="true">http://www.frankmcsherry.org/dataflow/naiad/2014/12/29/TD_time_summaries.html</guid>
        
        
        <category>dataflow</category>
        
        <category>naiad</category>
        
      </item>
    
  </channel>
</rss>
