diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml
index e4324a9d..21542707 100644
--- a/.github/workflows/cmake.yml
+++ b/.github/workflows/cmake.yml
@@ -20,35 +20,11 @@ jobs:
     steps:
       - uses: actions/checkout@v2
 
-      - uses: actions/cache@v2
-        id: InstalledDependencyCache
-        with:
-          path: ${{runner.workspace}}/install
-          key: InstalledDependencyCache
-
-      - name: Install xxHash
-        if: steps.InstalledDependencyCache.outputs.cache-hit != 'true'
-        shell: bash
-        working-directory: ${{runner.workspace}}
-        run: |
-          git clone -b v0.8.0 https://github.com/Cyan4973/xxHash.git
-          cd xxHash
-          mkdir build
-          cd build
-          cmake -DCMAKE_INSTALL_PREFIX=${{runner.workspace}}/install ../cmake_unofficial
-          cmake --build . --target install
-
-      - name: Install GTest
-        if: steps.InstalledDependencyCache.outputs.cache-hit != 'true'
-        shell: bash
-        working-directory: ${{runner.workspace}}
-        run: |
-          git clone -b release-1.10.0 https://github.com/google/googletest.git
-          cd googletest
-          mkdir build
-          cd build
-          cmake -DCMAKE_INSTALL_PREFIX=${{runner.workspace}}/install ..
-          cmake --build . --target install
+      # - uses: actions/cache@v2
+      #   id: InstalledDependencyCache
+      #   with:
+      #     path: ${{runner.workspace}}/install
+      #     key: InstalledDependencyCache
 
       - name: Create Build Environment
         # Some projects don't allow in-source building, so create a separate build directory
diff --git a/README.md b/README.md
index 5bff04d1..1116c6af 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,7 @@ int main() {
   auto CC = g.connected_components();            // Extract the connected components in the graph defined by the stream
 }
 ```
+A more detailed example can be found in `tools/process_stream.cpp`.
 
 ### Binary Stream Format
 GraphZeppelin uses a binary stream format for efficient file parsing. The format of these files is as follows.
@@ -53,9 +54,9 @@ Other file formats can be used by writing a simple file parser that passes graph
 If receiving edge updates over the network it is equally straightforward to define a stream format that will receive, parse, and provide those updates to the graph `update()` function.
 
 ## Configuration
-GraphZeppelin has a few parameters set via a configuration file. These include the number of CPU threads to use, and which datastructure to buffer updates in. The file `example_streaming.conf` gives an example of one such configuration and provides explanations of the various parameters.
+GraphZeppelin has a number of parameters. These can be defined with the `GraphConfiguration` object. Key parameters include the number of graph workers and the guttering system to use for buffering updates.
 
-To define your own configuration, copy `example_streaming.conf` into the `build` directory as `streaming.conf`. If using GraphZeppelin as an external library the process for defining the configuration is the same. Once you make changes to the configuration, you should see them reflected in the configuration displayed at the beginning of the program.
+See `include/graph_configuration.h` for more details.
 
 ## Debugging
 You can enable the symbol table and turn off compiler optimizations for debugging with tools like `gdb` or `valgrind` by performing the following steps
diff --git a/include/binary_graph_stream.h b/include/binary_graph_stream.h
index cbfe81da..12fae379 100644
--- a/include/binary_graph_stream.h
+++ b/include/binary_graph_stream.h
@@ -106,8 +106,8 @@ class BinaryGraphStream_MT {
    *
    * IMPORTANT: When perfoming queries it is the responsibility of the user to ensure that 
    * all threads have finished processing their current work. This is indicated by all threads 
-   * pulling data from the stream returning NXT_QUERY.
-   * If all threads do not return NXT_QUERY then not all updates have been applied to the graph.
+   * pulling data from the stream returning BREAKPOINT.
+   * If all threads do not return BREAKPOINT then not all updates have been applied to the graph.
    * Threads must not call get_edge while the query is in progress otherwise edge cases can occur.
    * This is true for both on_demand and registered queries.
    */
@@ -148,7 +148,7 @@ class BinaryGraphStream_MT {
   uint64_t end_of_file;  // the index of the end of the file
   std::atomic<uint64_t> stream_off;  // where do threads read from in the stream
   std::atomic<uint64_t> query_index; // what is the index of the next query in bytes
-  std::atomic<bool> query_block;     // If true block read_data calls and have thr return NXT_QUERY
+  std::atomic<bool> query_block;     // If true block read_data calls and have thr return BREAKPOINT
   const uint32_t edge_size = sizeof(uint8_t) + 2 * sizeof(uint32_t); // size of binary encoded edge
   const size_t header_size = sizeof(node_id_t) + sizeof(edge_id_t); // size of num_nodes + num_upds
 
@@ -198,9 +198,7 @@ class MT_StreamReader {
     // if we have read all the data in the buffer than refill it
     if (buf - start_buf >= data_in_buf) {
       if ((data_in_buf = stream.read_data(start_buf)) == 0) {
-        if (stream.query_index <= stream.stream_off)
-          return {{-1, -1}, NXT_QUERY}; // return that a query should be processed now
-        return {{-1, -1}, END_OF_FILE}; // return that the stream is over
+        return {{0, 0}, BREAKPOINT}; // return that a break point has been reached
       }
       buf = start_buf; // point buf back to beginning of data buffer
     }
diff --git a/include/dsu.h b/include/dsu.h
index 4feb0fc5..2642437b 100644
--- a/include/dsu.h
+++ b/include/dsu.h
@@ -1,43 +1,92 @@
 #pragma once
 #include <vector>
+#include <atomic>
+
+template<class T>
+struct DSUMergeRet {
+  bool merged; // true if a merge actually occured
+  T root; // new root
+  T child; // new child of root
+};
 
 template <class T>
 class DisjointSetUnion {
   std::vector<T> parent;
   std::vector<T> size;
 public:
-  DisjointSetUnion();
-  DisjointSetUnion(T n);
+  DisjointSetUnion(T n) : parent(n), size(n, 1) {
+    for (T i = 0; i < n; i++) {
+      parent[i] = i;
+    }
+  }
+
+  inline T find_root(T u) {
+    while(parent[parent[u]] != u) {
+      parent[u] = parent[parent[u]];
+      u = parent[u];
+    }
+    return u;
+  }
 
-  void link(T i, T j);
-  T find_set(T i);
-  void union_set(T i, T j);
+  inline DSUMergeRet<T> merge(T u, T v) {
+    T a = find_root(u);
+    T b = find_root(v);
+    if (a == b) return {false, 0, 0};
+
+    if (size[a] < size[b]) std::swap(a,b);
+    parent[b] = a;
+    size[a] += size[b];
+    return {true, a, b};
+  }
+
+  inline void reset() {
+    for (T i = 0; i < parent.size(); i++) {
+      parent[i] = i;
+      size[i] = 1;
+    }
+  }
 };
 
+// Disjoint set union that uses atomics to be thread safe
+// thus is a little slower for single threaded use cases
 template <class T>
-DisjointSetUnion<T>::DisjointSetUnion() {}
+class MT_DisjoinSetUnion {
+private:
+  std::vector<std::atomic<T>> parent;
+  std::vector<T> size;
+public:
+  MT_DisjoinSetUnion(T n) : parent(n), size(n, 1) {
+    for (T i = 0; i < n; i++)
+      parent[i] = i;
+  }
 
-template <class T>
-DisjointSetUnion<T>::DisjointSetUnion(T n) : parent(n), size(n, 1) {
-  for (T i = 0; i < n; i++) {
-    parent[i] = i;
+  inline T find_root(T u) {
+    while (parent[parent[u]] != u) {
+      parent[u] = parent[parent[u]];
+      u = parent[u];
+    }
+    return u;
   }
-}
 
-template <class T>
-void DisjointSetUnion<T>::link(T i, T j) {
-  if (size[i] < size[j]) std::swap(i,j);
-  parent[j] = i;
-  size[i] += size[j];
-}
+  // use CAS in this function to allow for simultaneous merge calls
+  inline DSUMergeRet<T> merge(T u, T v) {
+    while ((u = find_root(u)) != (v = find_root(v))) {
+      if (size[u] < size[v])
+        std::swap(u, v);
 
-template <class T>
-T DisjointSetUnion<T>::find_set(T i) {
-  if (parent[i] == i) return i;
-  return parent[i] = find_set(parent[i]);
-}
+      // if parent of b has not been modified by another thread -> replace with a
+      if (std::atomic_compare_exchange_weak(&parent[u], &v, u)) {
+        size[u] += size[v];
+        return {true, u, v};
+      }
+    }
+    return {false, 0, 0};
+  }
 
-template <class T>
-void DisjointSetUnion<T>::union_set(T i, T j) {
-  link(find_set(i), find_set(j));
-}
+  inline void reset() {
+    for (T i = 0; i < parent.size(); i++) {
+      parent[i] = i;
+      size[i] = 1;
+    }
+  }
+};
diff --git a/include/graph.h b/include/graph.h
index 42393317..89abb390 100644
--- a/include/graph.h
+++ b/include/graph.h
@@ -20,8 +20,6 @@
 // forward declarations
 class GraphWorker;
 
-typedef std::pair<Edge, UpdateType> GraphUpdate;
-
 // Exceptions the Graph class may throw
 class UpdateLockedException : public std::exception {
   virtual const char* what() const throw() {
@@ -126,15 +124,15 @@ class Graph {
 
   inline void update(GraphUpdate upd, int thr_id = 0) {
     if (update_locked) throw UpdateLockedException();
-    Edge &edge = upd.first;
+    Edge &edge = upd.edge;
 
-    gts->insert(edge, thr_id);
-    std::swap(edge.first, edge.second);
-    gts->insert(edge, thr_id);
+    gts->insert({edge.src, edge.dst}, thr_id);
+    std::swap(edge.src, edge.dst);
+    gts->insert({edge.src, edge.dst}, thr_id);
 #ifdef USE_EAGER_DSU
     if (dsu_valid) {
-      auto src = std::min(edge.first, edge.second);
-      auto dst = std::max(edge.first, edge.second);
+      auto src = std::min(edge.src, edge.dst);
+      auto dst = std::max(edge.src, edge.dst);
       std::lock_guard<std::mutex> sflock (spanning_forest_mtx[src]);
       if (spanning_forest[src].find(dst) != spanning_forest[src].end()) {
         dsu_valid = false;
@@ -152,6 +150,8 @@ class Graph {
         }
       }
     }
+#else
+    dsu_valid = false;
 #endif // USE_EAGER_DSU
   }
 
diff --git a/include/l0_sampling/sketch.h b/include/l0_sampling/sketch.h
index e6727704..8ba41005 100644
--- a/include/l0_sampling/sketch.h
+++ b/include/l0_sampling/sketch.h
@@ -94,6 +94,9 @@ class Sketch {
 
   inline static size_t sketchSizeof()
   { return sizeof(Sketch) + num_elems * (sizeof(vec_t) + sizeof(vec_hash_t)) - sizeof(char); }
+
+  inline static size_t serialized_size()
+  { return num_elems * (sizeof(vec_t) + sizeof(vec_hash_t)); }
   
   inline static vec_t get_failure_factor() 
   { return failure_factor; }
diff --git a/include/supernode.h b/include/supernode.h
index 6896be5e..5b012062 100644
--- a/include/supernode.h
+++ b/include/supernode.h
@@ -5,8 +5,6 @@
 
 #include "l0_sampling/sketch.h"
 
-typedef std::pair<node_id_t, node_id_t> Edge;
-
 /**
  * This interface implements the "supernode" so Boruvka can use it as a black
  * box without needing to worry about implementing l_0.
@@ -14,6 +12,7 @@ typedef std::pair<node_id_t, node_id_t> Edge;
 class Supernode {
   // the size of a super-node in bytes including the all sketches off the end
   static size_t bytes_size; 
+  static size_t serialized_size; // the size of a supernode that has been serialized
   int idx;
   int num_sketches;
   std::mutex node_mt;
@@ -82,13 +81,19 @@ class Supernode {
 
   static inline void configure(uint64_t n, vec_t sketch_fail_factor=100) {
     Sketch::configure(n*n, sketch_fail_factor);
-    bytes_size = sizeof(Supernode) + log2(n)/(log2(3)-1) * Sketch::sketchSizeof() - sizeof(char);
+    bytes_size = sizeof(Supernode) + size_t(log2(n)/(log2(3)-1)) * Sketch::sketchSizeof() - sizeof(char);
+    serialized_size = size_t(log2(n)/(log2(3)-1)) * Sketch::serialized_size();
   }
 
   static inline size_t get_size() {
     return bytes_size;
   }
 
+  // return the size of a supernode that has been serialized using write_binary()
+  static inline size_t get_serialized_size() {
+    return serialized_size;
+  }
+
   inline size_t get_sketch_size() {
     return sketch_size;
   }
diff --git a/include/test/file_graph_verifier.h b/include/test/file_graph_verifier.h
index 1f8cb24b..cf416c4e 100644
--- a/include/test/file_graph_verifier.h
+++ b/include/test/file_graph_verifier.h
@@ -12,11 +12,10 @@
 class FileGraphVerifier : public GraphVerifier {
   std::vector<std::set<node_id_t>> kruskal_ref;
   std::vector<std::set<node_id_t>> boruvka_cc;
-  std::vector<std::set<node_id_t>> det_graph;
   DisjointSetUnion<node_id_t> sets;
 
 public:
-  FileGraphVerifier(const std::string& input_file = "./cumul_sample.txt");
+  FileGraphVerifier(node_id_t n, const std::string& input_file);
 
   void verify_edge(Edge edge);
   void verify_cc(node_id_t node);
diff --git a/include/test/graph_verifier.h b/include/test/graph_verifier.h
index e0ad76a2..d318c6e3 100644
--- a/include/test/graph_verifier.h
+++ b/include/test/graph_verifier.h
@@ -8,6 +8,9 @@
  * generates.
  */
 class GraphVerifier {
+protected:
+  std::vector<std::vector<bool>> adj_matrix;
+
 public:
   /**
    * Verifies an edge exists in the graph. Verifies that the edge is in the cut
@@ -32,19 +35,28 @@ class GraphVerifier {
    */
   virtual void verify_soln(std::vector<std::set<node_id_t>> &retval) = 0;
 
+  std::vector<std::vector<bool>> extract_adj_matrix() {return adj_matrix;}
+
+  GraphVerifier() = default;
+  GraphVerifier(std::vector<std::vector<bool>> _adj) : adj_matrix(std::move(_adj)) {};
+
   virtual ~GraphVerifier() {};
 };
 
 class BadEdgeException : public std::exception {
   virtual const char* what() const throw() {
-    return "The edge is not in the cut of the sample! (standard)";
+    return "The edge is not in the cut of the sample!";
   }
 };
 
-
 class NotCCException : public std::exception {
   virtual const char* what() const throw() {
-    return "The supernode is not a connected component. It has edges in its "
-           "cut!";
+    return "The supernode is not a connected component. It has edges in its cut!";
+  }
+};
+
+class IncorrectCCException : public std::exception {
+  virtual const char* what() const throw() {
+    return "The connected components are incorrect!";
   }
 };
diff --git a/include/test/mat_graph_verifier.h b/include/test/mat_graph_verifier.h
index 5ab2d7bf..07aeeb16 100644
--- a/include/test/mat_graph_verifier.h
+++ b/include/test/mat_graph_verifier.h
@@ -12,10 +12,9 @@
 class MatGraphVerifier : public GraphVerifier {
   std::vector<std::set<node_id_t>> kruskal_ref;
   std::vector<std::set<node_id_t>> boruvka_cc;
-  std::vector<std::vector<bool>> adj_graph;
-  DisjointSetUnion<node_id_t> sets;
 
   node_id_t n;
+  DisjointSetUnion<node_id_t> sets;
 
   /**
    * Runs Kruskal's (deterministic) CC algo.
@@ -25,6 +24,10 @@ class MatGraphVerifier : public GraphVerifier {
   std::vector<std::set<node_id_t>> kruskal();
 public:
   MatGraphVerifier(node_id_t n);
+
+  // When we want to build a MatGraphVerifier without iterative edge_updates
+  MatGraphVerifier(node_id_t n, std::vector<std::vector<bool>> _adj)
+   : GraphVerifier(_adj), n(n), sets(n) { reset_cc_state(); };
   
   void reset_cc_state();       // run this function before using as a verifier in CC
   void edge_update(node_id_t src, node_id_t dst);
@@ -32,4 +35,4 @@ class MatGraphVerifier : public GraphVerifier {
   void verify_edge(Edge edge);
   void verify_cc(node_id_t node);
   void verify_soln(std::vector<std::set<node_id_t>> &retval);
-};
\ No newline at end of file
+};
diff --git a/include/types.h b/include/types.h
index 510c0198..725d6463 100644
--- a/include/types.h
+++ b/include/types.h
@@ -4,14 +4,23 @@
 
 typedef uint64_t col_hash_t;
 static const auto& vec_hash = XXH32;
-static const auto& col_hash = XXH64;
+static const auto& col_hash = XXH3_64bits_withSeed;
 
 // Is a stream update an insertion or a deletion
-// NXT_QUERY: special type to indicate getting more data would cross a query boundary
-// END_OF_FILE: special type to indicate that there is no more data in the stream
+// BREAKPOINT: special type that indicates that a break point has been reached
+// a break point may be either the end of the stream or the index of a query
 enum UpdateType {
   INSERT = 0,
   DELETE = 1,
-  NXT_QUERY = 2,
-  END_OF_FILE = 3
-}; 
+  BREAKPOINT = 2
+};
+
+struct Edge {
+  node_id_t src = 0;
+  node_id_t dst = 0;
+};
+
+struct GraphUpdate {
+  Edge edge;
+  UpdateType type;
+};
diff --git a/include/util.h b/include/util.h
index 7fb64b08..eccbeecf 100644
--- a/include/util.h
+++ b/include/util.h
@@ -2,6 +2,8 @@
 #include <string>
 #include <tuple>
 
+#include "types.h"
+
 /**
  * Cast a double to unsigned long long with epsilon adjustment.
  * @param d         the double to cast.
@@ -16,27 +18,27 @@ unsigned long long int double_to_ull(double d, double epsilon = 0.00000001);
  * identical inputs.
  * @return i + j*(j-1)/2
  */
-uint64_t nondirectional_non_self_edge_pairing_fn(uint32_t i, uint32_t j);
+edge_id_t nondirectional_non_self_edge_pairing_fn(node_id_t i, node_id_t j);
 
 /**
  * Inverts the nondirectional non-SE pairing function.
  * @param idx
  * @return the pair, with left and right ordered lexicographically.
  */
-std::pair<uint32_t , uint32_t> inv_nondir_non_self_edge_pairing_fn(uint64_t idx);
+Edge inv_nondir_non_self_edge_pairing_fn(edge_id_t idx);
 
 /**
  * Concatenates two node ids to form an edge ids
  * @return (i << 32) & j
  */
-uint64_t concat_pairing_fn(uint32_t i, uint32_t j);
+edge_id_t concat_pairing_fn(node_id_t i, node_id_t j);
 
 /**
  * Inverts the concat pairing function.
  * @param idx
  * @return the pair, with left and right ordered lexicographically.
  */
-std::pair<uint32_t , uint32_t> inv_concat_pairing_fn(uint64_t idx);
+Edge inv_concat_pairing_fn(edge_id_t idx);
 
 /**
  * Configures the system using the configuration file streaming.conf
diff --git a/src/graph.cpp b/src/graph.cpp
index 67865eac..bb710170 100644
--- a/src/graph.cpp
+++ b/src/graph.cpp
@@ -177,8 +177,8 @@ inline std::vector<std::vector<node_id_t>> Graph::supernodes_to_merge(std::pair<
     }
 
     // query dsu
-    node_id_t a = get_parent(edge.first);
-    node_id_t b = get_parent(edge.second);
+    node_id_t a = get_parent(edge.src);
+    node_id_t b = get_parent(edge.dst);
     if (a == b) continue;
 
 #ifdef VERIFY_SAMPLES_F
@@ -197,8 +197,8 @@ inline std::vector<std::vector<node_id_t>> Graph::supernodes_to_merge(std::pair<
     modified = true;
 
     // Update spanning forest
-    auto src = std::min(edge.first, edge.second);
-    auto dst = std::max(edge.first, edge.second);
+    auto src = std::min(edge.src, edge.dst);
+    auto dst = std::max(edge.src, edge.dst);
     spanning_forest[src].insert(dst);
   }
 
@@ -280,7 +280,6 @@ std::vector<std::set<node_id_t>> Graph::boruvka_emulation(bool make_copy) {
     }
   };
 
-  std::cout << "~ Reconstructing DSU" << std::endl;
   for (node_id_t i = 0; i < num_nodes; ++i) {
     parent[i] = i;
     spanning_forest[i].clear();
@@ -308,9 +307,7 @@ std::vector<std::set<node_id_t>> Graph::boruvka_emulation(bool make_copy) {
     std::rethrow_exception(std::current_exception());
   }
   cleanup_copy();
-#ifdef USE_EAGER_DSU
   dsu_valid = true;
-#endif // USE_EAGER_DSU
 
   auto retval = cc_from_dsu();
   cc_alg_end = std::chrono::steady_clock::now();
@@ -346,7 +343,6 @@ void Graph::restore_from_disk(const std::vector<node_id_t>& ids_to_restore) {
 }
 
 std::vector<std::set<node_id_t>> Graph::connected_components(bool cont) {
-#ifdef USE_EAGER_DSU
   // DSU check before calling force_flush()
   if (dsu_valid && cont
 #ifdef VERIFY_SAMPLES_F
@@ -354,7 +350,6 @@ std::vector<std::set<node_id_t>> Graph::connected_components(bool cont) {
 #endif // VERIFY_SAMPLES_F
       ) {
     cc_alg_start = flush_start = flush_end = std::chrono::steady_clock::now();
-    std::cout << "~ Used existing DSU" << std::endl;
 #ifdef VERIFY_SAMPLES_F
     for (node_id_t src = 0; src < num_nodes; ++src) {
       for (const auto& dst : spanning_forest[src]) {
@@ -363,10 +358,12 @@ std::vector<std::set<node_id_t>> Graph::connected_components(bool cont) {
     }
 #endif
     auto retval = cc_from_dsu();
+#ifdef VERIFY_SAMPLES_F
+    verifier->verify_soln(retval);
+#endif
     cc_alg_end = std::chrono::steady_clock::now();
     return retval;
   }
-#endif // USE_EAGER_DSU
 
   flush_start = std::chrono::steady_clock::now();
   gts->force_flush(); // flush everything in guttering system to make final updates
@@ -374,15 +371,23 @@ std::vector<std::set<node_id_t>> Graph::connected_components(bool cont) {
   flush_end = std::chrono::steady_clock::now();
   // after this point all updates have been processed from the buffer tree
 
-  if (!cont)
-    return boruvka_emulation(false); // merge in place
+  std::vector<std::set<node_id_t>> ret;
+  if (!cont) {
+    ret = boruvka_emulation(false); // merge in place
+#ifdef VERIFY_SAMPLES_F
+    verifier->verify_soln(ret);
+#endif
+    return ret;
+  }
   
   // if backing up in memory then perform copying in boruvka
   bool except = false;
   std::exception_ptr err;
-  std::vector<std::set<node_id_t>> ret;
   try {
     ret = boruvka_emulation(true);
+#ifdef VERIFY_SAMPLES_F
+    verifier->verify_soln(ret);
+#endif
   } catch (...) {
     except = true;
     err = std::current_exception();
@@ -410,18 +415,13 @@ std::vector<std::set<node_id_t>> Graph::cc_from_dsu() {
   std::vector<std::set<node_id_t>> retval;
   retval.reserve(temp.size());
   for (const auto& it : temp) retval.push_back(it.second);
-#ifdef VERIFY_SAMPLES_F
-  verifier->verify_soln(retval);
-#endif
   return retval;
 }
 
 bool Graph::point_query(node_id_t a, node_id_t b) {
-#ifdef USE_EAGER_DSU
   // DSU check before calling force_flush()
   if (dsu_valid) {
     cc_alg_start = flush_start = flush_end = std::chrono::steady_clock::now();
-    std::cout << "~ Used existing DSU" << std::endl;
 #ifdef VERIFY_SAMPLES_F
     for (node_id_t src = 0; src < num_nodes; ++src) {
       for (const auto& dst : spanning_forest[src]) {
@@ -433,7 +433,6 @@ bool Graph::point_query(node_id_t a, node_id_t b) {
     cc_alg_end = std::chrono::steady_clock::now();
     return retval;
   }
-#endif // USE_EAGER_DSU
 
 
   flush_start = std::chrono::steady_clock::now();
diff --git a/src/supernode.cpp b/src/supernode.cpp
index 3b2edf2e..d04784e7 100644
--- a/src/supernode.cpp
+++ b/src/supernode.cpp
@@ -4,6 +4,7 @@
 #include "../include/graph_worker.h"
 
 size_t Supernode::bytes_size;
+size_t Supernode::serialized_size;
 
 Supernode::Supernode(uint64_t n, uint64_t seed): idx(0), num_sketches(log2(n)/(log2(3)-1)),
                n(n), seed(seed), sketch_size(Sketch::sketchSizeof()) {
diff --git a/src/util.cpp b/src/util.cpp
index b828c351..6c62db2b 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -7,14 +7,14 @@
 typedef uint32_t ul;
 typedef uint64_t ull;
 
-const ull ULLMAX = std::numeric_limits<ull>::max();
-const uint8_t num_bits = sizeof(node_id_t) * 8;
+constexpr ull ULLMAX = std::numeric_limits<ull>::max();
+constexpr uint8_t num_bits = sizeof(node_id_t) * 8;
 
 unsigned long long int double_to_ull(double d, double epsilon) {
   return (unsigned long long) (d + epsilon);
 }
 
-uint64_t nondirectional_non_self_edge_pairing_fn(uint32_t i, uint32_t j) {
+edge_id_t nondirectional_non_self_edge_pairing_fn(node_id_t i, node_id_t j) {
   // swap i,j if necessary
   if (i > j) {
     std::swap(i,j);
@@ -30,7 +30,7 @@ uint64_t nondirectional_non_self_edge_pairing_fn(uint32_t i, uint32_t j) {
   return i+j;
 }
 
-std::pair<uint32_t , uint32_t> inv_nondir_non_self_edge_pairing_fn(uint64_t idx) {
+Edge inv_nondir_non_self_edge_pairing_fn(uint64_t idx) {
   // we ignore possible overflow
   ull eidx = 8ull*idx + 1ull;
   eidx = sqrt(eidx)+1ull;
@@ -38,19 +38,19 @@ std::pair<uint32_t , uint32_t> inv_nondir_non_self_edge_pairing_fn(uint64_t idx)
   ull i,j = (ull) eidx;
   if ((j & 1ull) == 0ull) i = idx-(j>>1ull)*(j-1ull);
   else i = idx-j*((j-1ull)>>1ull);
-  return {i, j};
+  return {(node_id_t)i, (node_id_t)j};
 }
 
-ull concat_pairing_fn(ul i, ul j) {
+edge_id_t concat_pairing_fn(node_id_t i, node_id_t j) {
   // swap i,j if necessary
   if (i > j) {
     std::swap(i,j);
   }
-  return ((ull)i << num_bits) | j;
+  return ((edge_id_t)i << num_bits) | j;
 }
 
-std::pair<ul, ul> inv_concat_pairing_fn(ull idx) {
-  ul j = idx & 0xFFFFFFFF;
-  ul i = idx >> num_bits;
+Edge inv_concat_pairing_fn(ull idx) {
+  node_id_t j = idx & 0xFFFFFFFF;
+  node_id_t i = idx >> num_bits;
   return {i, j};
 }
diff --git a/test/graph_test.cpp b/test/graph_test.cpp
index 35c2a05a..9dc8954a 100644
--- a/test/graph_test.cpp
+++ b/test/graph_test.cpp
@@ -41,7 +41,7 @@ TEST_P(GraphTest, SmallGraphConnectivity) {
     in >> a >> b;
     g.update({{a, b}, INSERT});
   }
-  g.set_verifier(std::make_unique<FileGraphVerifier>(curr_dir + "/res/multiples_graph_1024.txt"));
+  g.set_verifier(std::make_unique<FileGraphVerifier>(1024, curr_dir + "/res/multiples_graph_1024.txt"));
   ASSERT_EQ(78, g.connected_components().size());
 }
 
@@ -61,7 +61,7 @@ TEST(GraphTest, IFconnectedComponentsAlgRunTHENupdateLocked) {
     in >> a >> b;
     g.update({{a, b}, INSERT});
   }
-  g.set_verifier(std::make_unique<FileGraphVerifier>(curr_dir + "/res/multiples_graph_1024.txt"));
+  g.set_verifier(std::make_unique<FileGraphVerifier>(1024, curr_dir + "/res/multiples_graph_1024.txt"));
   g.connected_components();
   ASSERT_THROW(g.update({{1,2}, INSERT}), UpdateLockedException);
   ASSERT_THROW(g.update({{1,2}, DELETE}), UpdateLockedException);
@@ -84,7 +84,7 @@ TEST(GraphTest, TestSupernodeRestoreAfterCCFailure) {
       in >> a >> b;
       g.update({{a, b}, INSERT});
     }
-    g.set_verifier(std::make_unique<FileGraphVerifier>(curr_dir + "/res/multiples_graph_1024.txt"));
+    g.set_verifier(std::make_unique<FileGraphVerifier>(1024, curr_dir + "/res/multiples_graph_1024.txt"));
     g.should_fail_CC();
 
     // flush to make sure copy supernodes is consistent with graph supernodes
@@ -115,7 +115,8 @@ TEST_P(GraphTest, TestCorrectnessOnSmallRandomGraphs) {
     edge_id_t m;
     in >> n >> m;
     Graph g{n, config};
-    int type, a, b;
+    int type;
+    node_id_t a, b;
     while (m--) {
       in >> type >> a >> b;
       if (type == INSERT) {
@@ -123,7 +124,7 @@ TEST_P(GraphTest, TestCorrectnessOnSmallRandomGraphs) {
       } else g.update({{a, b}, DELETE});
     }
 
-    g.set_verifier(std::make_unique<FileGraphVerifier>("./cumul_sample.txt"));
+    g.set_verifier(std::make_unique<FileGraphVerifier>(n, "./cumul_sample.txt"));
     g.connected_components();
   }
 }
@@ -138,7 +139,8 @@ TEST_P(GraphTest, TestCorrectnessOnSmallSparseGraphs) {
     edge_id_t m;
     in >> n >> m;
     Graph g{n, config};
-    int type, a, b;
+    int type;
+    node_id_t a, b;
     while (m--) {
       in >> type >> a >> b;
       if (type == INSERT) {
@@ -146,7 +148,7 @@ TEST_P(GraphTest, TestCorrectnessOnSmallSparseGraphs) {
       } else g.update({{a, b}, DELETE});
     }
 
-    g.set_verifier(std::make_unique<FileGraphVerifier>("./cumul_sample.txt"));
+    g.set_verifier(std::make_unique<FileGraphVerifier>(1024, "./cumul_sample.txt"));
     g.connected_components();
   } 
 }
@@ -161,7 +163,8 @@ TEST_P(GraphTest, TestCorrectnessOfReheating) {
     edge_id_t m;
     in >> n >> m;
     Graph *g = new Graph (n, config);
-    int type, a, b;
+    int type;
+    node_id_t a, b;
     printf("number of updates = %lu\n", m);
     while (m--) {
       in >> type >> a >> b;
@@ -169,14 +172,14 @@ TEST_P(GraphTest, TestCorrectnessOfReheating) {
       else g->update({{a, b}, DELETE});
     }
     g->write_binary("./out_temp.txt");
-    g->set_verifier(std::make_unique<FileGraphVerifier>("./cumul_sample.txt"));
+    g->set_verifier(std::make_unique<FileGraphVerifier>(1024, "./cumul_sample.txt"));
     std::vector<std::set<node_id_t>> g_res;
     g_res = g->connected_components();
     printf("number of CC = %lu\n", g_res.size());
     delete g; // delete g to avoid having multiple graphs open at once. Which is illegal.
 
     Graph reheated {"./out_temp.txt"};
-    reheated.set_verifier(std::make_unique<FileGraphVerifier>("./cumul_sample.txt"));
+    reheated.set_verifier(std::make_unique<FileGraphVerifier>(1024, "./cumul_sample.txt"));
     auto reheated_res = reheated.connected_components();
     printf("number of reheated CC = %lu\n", reheated_res.size());
     ASSERT_EQ(g_res.size(), reheated_res.size());
@@ -205,7 +208,8 @@ TEST_P(GraphTest, MultipleWorkers) {
     edge_id_t m;
     in >> n >> m;
     Graph g{n, config};
-    int type, a, b;
+    int type;
+    node_id_t a, b;
     while (m--) {
       in >> type >> a >> b;
       if (type == INSERT) {
@@ -213,7 +217,7 @@ TEST_P(GraphTest, MultipleWorkers) {
       } else g.update({{a, b}, DELETE});
     }
 
-    g.set_verifier(std::make_unique<FileGraphVerifier>("./cumul_sample.txt"));
+    g.set_verifier(std::make_unique<FileGraphVerifier>(1024, "./cumul_sample.txt"));
     g.connected_components();
   } 
 }
@@ -234,7 +238,7 @@ TEST_P(GraphTest, TestPointQuery) {
     in >> a >> b;
     g.update({{a, b}, INSERT});
   }
-  g.set_verifier(std::make_unique<FileGraphVerifier>(curr_dir + "/res/multiples_graph_1024.txt"));
+  g.set_verifier(std::make_unique<FileGraphVerifier>(1024, curr_dir + "/res/multiples_graph_1024.txt"));
   std::vector<std::set<node_id_t>> ret = g.connected_components(true);
   std::vector<node_id_t> ccid (num_nodes);
   for (node_id_t i = 0; i < ret.size(); ++i) {
@@ -244,7 +248,7 @@ TEST_P(GraphTest, TestPointQuery) {
   }
   for (node_id_t i = 0; i < std::min(10u, num_nodes); ++i) {
     for (node_id_t j = 0; j < std::min(10u, num_nodes); ++j) {
-      g.set_verifier(std::make_unique<FileGraphVerifier>(curr_dir + "/res/multiples_graph_1024.txt"));
+      g.set_verifier(std::make_unique<FileGraphVerifier>(1024, curr_dir + "/res/multiples_graph_1024.txt"));
       ASSERT_EQ(g.point_query(i, j), ccid[i] == ccid[j]);
     }
   }
@@ -412,12 +416,12 @@ TEST(GraphTest, MultipleInsertThreads) {
     threads[i].join();
   }
 
-  g.set_verifier(std::make_unique<FileGraphVerifier>("./cumul_sample.txt"));
+  g.set_verifier(std::make_unique<FileGraphVerifier>(1024, "./cumul_sample.txt"));
   g.connected_components();
 }
 
 TEST(GraphTest, MTStreamWithMultipleQueries) {
-  for(int i = 1; i <= 10; i++) {
+  for(int i = 1; i <= 3; i++) {
     auto config = GraphConfiguration().gutter_sys(STANDALONE);
 
     const std::string fname = __FILE__;
@@ -441,7 +445,8 @@ TEST(GraphTest, MTStreamWithMultipleQueries) {
     std::mutex q_lock;
 
     // prepare evenly spaced queries
-    int num_queries = 10;
+    std::atomic<int> num_queries;
+    num_queries = 10;
     int upd_per_query = num_edges / num_queries;
     int query_idx = upd_per_query;
     ASSERT_TRUE(stream.register_query(query_idx)); // register first query
@@ -452,8 +457,8 @@ TEST(GraphTest, MTStreamWithMultipleQueries) {
       GraphUpdate upd;
       while(true) {
         upd = reader.get_edge();
-        if (upd.second == END_OF_FILE) return;
-        else if (upd.second == NXT_QUERY) {
+        if (upd.type == BREAKPOINT && num_queries == 0) return;
+        else if (upd.type == BREAKPOINT) {
           query_done = false;
           if (thr_id > 0) {
             // pause this thread and wait for query to be done
@@ -479,7 +484,7 @@ TEST(GraphTest, MTStreamWithMultipleQueries) {
             // add updates to verifier and perform query
             for (int j = 0; j < upd_per_query; j++) {
               GraphUpdate upd = verify_stream.get_edge();
-              verify.edge_update(upd.first.first, upd.first.second);
+              verify.edge_update(upd.edge.src, upd.edge.dst);
             }
             verify.reset_cc_state();
             g.set_verifier(std::make_unique<MatGraphVerifier>(verify));
@@ -491,15 +496,15 @@ TEST(GraphTest, MTStreamWithMultipleQueries) {
               // prepare next query
               query_idx += upd_per_query;
               ASSERT_TRUE(stream.register_query(query_idx));
-              num_queries--;
             }
+            num_queries--;
             num_query_ready--;
             query_done = true;
             lk.unlock();
             q_done_cond.notify_all();
           }
         }
-        else if (upd.second == INSERT || upd.second == DELETE)
+        else if (upd.type == INSERT || upd.type == DELETE)
           g.update(upd, thr_id);
         else
           throw std::invalid_argument("Did not recognize edge code!");
@@ -518,7 +523,7 @@ TEST(GraphTest, MTStreamWithMultipleQueries) {
     // process the rest of the stream into the MatGraphVerifier
     for(size_t i = query_idx; i < num_edges; i++) {
       GraphUpdate upd = verify_stream.get_edge();
-      verify.edge_update(upd.first.first, upd.first.second);
+      verify.edge_update(upd.edge.src, upd.edge.dst);
     }
 
     // perform final query
diff --git a/test/sketch_test.cpp b/test/sketch_test.cpp
index cefb6602..fc83ef9c 100644
--- a/test/sketch_test.cpp
+++ b/test/sketch_test.cpp
@@ -221,9 +221,8 @@ TEST(SketchTestSuite, TestSketchAddition){
  * Large sketch test
  */
 void test_sketch_large(unsigned long vec_size, unsigned long num_updates) {
-  Sketch::configure(vec_size, fail_factor);
+  Sketch::configure(vec_size, vec_size);
 
-  // TEMPORARY FIX
   // we use an optimization in our sketching that is valid when solving CC
   // we assume that max number of non-zeroes in vector is vec_size / 4
   // therefore we need to ensure that in this test that we don't do more than that
@@ -277,13 +276,13 @@ void test_sketch_large(unsigned long vec_size, unsigned long num_updates) {
 
 TEST(SketchTestSuite, TestSketchLarge) {
   constexpr uint64_t upper_bound = 1e18;
-  for (uint64_t i = 1e6; i <= upper_bound; i *= 10) {
+  for (uint64_t i = 1e7; i <= upper_bound; i *= 10) {
     test_sketch_large(i, 1000000);
   }
 }
 
 TEST(SketchTestSuite, TestBatchUpdate) {
-  unsigned long vec_size = 1000000000, num_updates = 10000;
+  unsigned long vec_size = 1000000000, num_updates = 1000000;
   Sketch::configure(vec_size, fail_factor);
   srand(time(nullptr));
   std::vector<vec_t> updates(num_updates);
diff --git a/test/supernode_test.cpp b/test/supernode_test.cpp
index cdafee0b..7ce8c5dd 100644
--- a/test/supernode_test.cpp
+++ b/test/supernode_test.cpp
@@ -1,8 +1,11 @@
+#include "../include/supernode.h"
+
 #include <gtest/gtest.h>
-#include <cmath>
+
 #include <chrono>
+#include <cmath>
 #include <thread>
-#include "../include/supernode.h"
+
 #include "../include/graph_worker.h"
 
 const long seed = 7000000001;
@@ -10,23 +13,22 @@ const unsigned long long int num_nodes = 2000;
 
 class SupernodeTestSuite : public testing::Test {
 protected:
-  static std::vector<Edge>* graph_edges;
-  static std::vector<Edge>* odd_graph_edges;
-  static bool* prime;
+  static std::vector<Edge> graph_edges;
+  static std::vector<Edge> odd_graph_edges;
+  static std::vector<bool> prime;
   static void SetUpTestSuite() {
     srand(1000000007);
-    graph_edges = new std::vector<Edge>();
-    odd_graph_edges = new std::vector<Edge>();
-    for (unsigned i=2;i<num_nodes;++i) {
-      for (unsigned j = i*2; j < num_nodes; j+=i) {
-        graph_edges->push_back({i,j});
-        if ((j/i)%2) odd_graph_edges->push_back({i,j});
+    graph_edges = std::vector<Edge>();
+    odd_graph_edges = std::vector<Edge>();
+    for (unsigned i = 2; i < num_nodes; ++i) {
+      for (unsigned j = i * 2; j < num_nodes; j += i) {
+        graph_edges.push_back({i, j});
+        if ((j / i) % 2) odd_graph_edges.push_back({i, j});
       }
     }
 
     // sieve
-    prime = (bool*) malloc(num_nodes*sizeof(bool));
-    std::fill(prime,prime+num_nodes,true);
+    prime = std::vector<bool>(num_nodes, true);
     for (unsigned i = 2; i < num_nodes; i++) {
       if (prime[i]) {
         for (unsigned j = i * i; j < num_nodes; j += i) prime[j] = false;
@@ -34,18 +36,18 @@ class SupernodeTestSuite : public testing::Test {
     }
   }
   static void TearDownTestSuite() {
-    delete graph_edges;
-    delete odd_graph_edges;
-    free(prime);
+    graph_edges = std::vector<Edge>();
+    odd_graph_edges = std::vector<Edge>();
+    prime = std::vector<bool>();
   }
 
-  void SetUp() override {Supernode::configure(num_nodes);}
+  void SetUp() override { Supernode::configure(num_nodes); }
   void TearDown() override {}
 };
 
-std::vector<Edge>* SupernodeTestSuite::graph_edges;
-std::vector<Edge>* SupernodeTestSuite::odd_graph_edges;
-bool* SupernodeTestSuite::prime;
+std::vector<Edge> SupernodeTestSuite::graph_edges;
+std::vector<Edge> SupernodeTestSuite::odd_graph_edges;
+std::vector<bool> SupernodeTestSuite::prime;
 
 TEST_F(SupernodeTestSuite, GIVENnoEdgeUpdatesIFsampledTHENnoEdgeIsReturned) {
   Supernode* s = Supernode::makeSupernode(num_nodes, seed);
@@ -64,15 +66,13 @@ TEST_F(SupernodeTestSuite, IFsampledTooManyTimesTHENthrowOutOfQueries) {
 TEST_F(SupernodeTestSuite, TestSampleInsertGrinder) {
   std::vector<Supernode*> snodes;
   snodes.reserve(num_nodes);
-  for (unsigned i = 0; i < num_nodes; ++i)
-    snodes[i] = Supernode::makeSupernode(num_nodes, seed);
+  for (unsigned i = 0; i < num_nodes; ++i) snodes[i] = Supernode::makeSupernode(num_nodes, seed);
 
   // insert all edges
-  for (auto edge : *graph_edges) {
-    vec_t encoded = concat_pairing_fn(edge.first, edge
-          .second);
-    snodes[edge.first]->update(encoded);
-    snodes[edge.second]->update(encoded);
+  for (auto edge : graph_edges) {
+    vec_t encoded = concat_pairing_fn(edge.src, edge.dst);
+    snodes[edge.src]->update(encoded);
+    snodes[edge.dst]->update(encoded);
   }
 
   // must have at least logn successes per supernode
@@ -80,49 +80,44 @@ TEST_F(SupernodeTestSuite, TestSampleInsertGrinder) {
 
   Edge sampled;
   for (unsigned i = 2; i < num_nodes; ++i) {
-    for (int j = 0; j < (int) snodes[i]->get_num_sktch(); ++j) {
+    for (int j = 0; j < (int)snodes[i]->get_num_sktch(); ++j) {
       std::pair<Edge, SampleSketchRet> sample_ret = snodes[i]->sample();
       sampled = sample_ret.first;
       SampleSketchRet ret_code = sample_ret.second;
       if (ret_code == FAIL) continue;
-    
+
       successes++;
-      if (i >= num_nodes/2 && prime[i]) {
+      if (i >= num_nodes / 2 && prime[i]) {
         ASSERT_EQ(ret_code, ZERO) << "False positive in sample " << i;
       } else {
         ASSERT_NE(ret_code, ZERO) << "False negative in sample " << i;
-        ASSERT_TRUE(std::max(sampled.first, sampled.second) % std::min
-              (sampled.first, sampled.second) == 0
-                    && (i == sampled.first || i == sampled.second)) <<
-                    "Failed on {" << sampled.first << "," <<
-                    sampled.second << "} with i = " << i;
+        ASSERT_TRUE(std::max(sampled.src, sampled.dst) % std::min(sampled.src, sampled.dst) == 0 &&
+                    (i == sampled.src || i == sampled.dst))
+            << "Failed on {" << sampled.src << "," << sampled.dst << "} with i = " << i;
       }
     }
-    ASSERT_GE(successes, (int) log2(num_nodes)) << "Fewer than logn successful queries: supernode " << i;
+    ASSERT_GE(successes, (int)log2(num_nodes))
+        << "Fewer than logn successful queries: supernode " << i;
   }
-  for (unsigned i = 0; i < num_nodes; ++i)
-    delete snodes[i];
+  for (unsigned i = 0; i < num_nodes; ++i) delete snodes[i];
 }
 
 TEST_F(SupernodeTestSuite, TestSampleDeleteGrinder) {
   std::vector<Supernode*> snodes;
   snodes.reserve(num_nodes);
-  for (unsigned i = 0; i < num_nodes; ++i)
-    snodes[i] = Supernode::makeSupernode(num_nodes, seed);
+  for (unsigned i = 0; i < num_nodes; ++i) snodes[i] = Supernode::makeSupernode(num_nodes, seed);
 
   // insert all edges
-  for (auto edge : *graph_edges) {
-    vec_t encoded = concat_pairing_fn(edge.first, edge
-          .second);
-    snodes[edge.first]->update(encoded);
-    snodes[edge.second]->update(encoded);
+  for (auto edge : graph_edges) {
+    vec_t encoded = concat_pairing_fn(edge.src, edge.dst);
+    snodes[edge.src]->update(encoded);
+    snodes[edge.dst]->update(encoded);
   }
   // then remove half of them (odds)
-  for (auto edge : *odd_graph_edges) {
-    vec_t encoded = concat_pairing_fn(edge.first, edge
-          .second);
-    snodes[edge.first]->update(encoded);
-    snodes[edge.second]->update(encoded);
+  for (auto edge : odd_graph_edges) {
+    vec_t encoded = concat_pairing_fn(edge.src, edge.dst);
+    snodes[edge.src]->update(encoded);
+    snodes[edge.dst]->update(encoded);
   }
 
   // must have at least logn successes per supernode
@@ -130,34 +125,32 @@ TEST_F(SupernodeTestSuite, TestSampleDeleteGrinder) {
 
   Edge sampled;
   for (unsigned i = 2; i < num_nodes; ++i) {
-    for (int j = 0; j < (int) snodes[i]->get_num_sktch(); ++j) {
+    for (int j = 0; j < (int)snodes[i]->get_num_sktch(); ++j) {
       std::pair<Edge, SampleSketchRet> sample_ret = snodes[i]->sample();
       sampled = sample_ret.first;
       SampleSketchRet ret_code = sample_ret.second;
       if (ret_code == FAIL) continue;
-    
+
       successes++;
-      if (i >= num_nodes/2 && i % 2) {
+      if (i >= num_nodes / 2 && i % 2) {
         ASSERT_EQ(ret_code, ZERO) << "False positive in sample " << i;
       } else {
         ASSERT_NE(ret_code, ZERO) << "False negative in sample " << i;
-        ASSERT_TRUE(std::max(sampled.first, sampled.second) % std::min
-              (sampled.first, sampled.second) == 0
-                    && (std::max(sampled.first, sampled.second) / std::min
-              (sampled.first, sampled.second)) % 2 == 0
-                    && (i == sampled.first || i == sampled.second)) <<
-                    "Failed on {" << sampled.first << "," <<
-                    sampled.second << "} with i = " << i;
+        ASSERT_TRUE(std::max(sampled.src, sampled.dst) % std::min(sampled.src, sampled.dst) == 0 &&
+                    (std::max(sampled.src, sampled.dst) / std::min(sampled.src, sampled.dst)) % 2 ==
+                        0 &&
+                    (i == sampled.src || i == sampled.dst))
+            << "Failed on {" << sampled.src << "," << sampled.dst << "} with i = " << i;
       }
     }
-    ASSERT_GE(successes, (int) log2(num_nodes)) << "Fewer than logn successful queries: supernode " << i;
+    ASSERT_GE(successes, (int)log2(num_nodes))
+        << "Fewer than logn successful queries: supernode " << i;
   }
-  for (unsigned i = 0; i < num_nodes; ++i)
-    delete snodes[i];
+  for (unsigned i = 0; i < num_nodes; ++i) delete snodes[i];
 }
 
 void inline apply_delta_to_node(Supernode* node, const std::vector<vec_t>& updates) {
-  auto* loc = (Supernode*) malloc(Supernode::get_size());
+  auto* loc = (Supernode*)malloc(Supernode::get_size());
   Supernode::delta_supernode(node->n, node->seed, updates, loc);
   node->apply_delta_update(loc);
   free(loc);
@@ -178,14 +171,22 @@ TEST_F(SupernodeTestSuite, TestBatchUpdate) {
   for (const auto& update : updates) {
     supernode->update(update);
   }
-  std::cout << "One by one updates took " << static_cast<std::chrono::duration<long double>>(std::chrono::steady_clock::now() - start_time).count() << std::endl;
+  std::cout << "One by one updates took "
+            << static_cast<std::chrono::duration<long double>>(std::chrono::steady_clock::now() -
+                                                               start_time)
+                   .count()
+            << std::endl;
   start_time = std::chrono::steady_clock::now();
   apply_delta_to_node(supernode_batch, updates);
-  std::cout << "Batched updates took " << static_cast<std::chrono::duration<long double>>(std::chrono::steady_clock::now() - start_time).count() << std::endl;
+  std::cout << "Batched updates took "
+            << static_cast<std::chrono::duration<long double>>(std::chrono::steady_clock::now() -
+                                                               start_time)
+                   .count()
+            << std::endl;
 
   ASSERT_EQ(supernode->get_num_sktch(), supernode_batch->get_num_sktch());
   ASSERT_EQ(supernode->idx, supernode_batch->idx);
-  for (int i=0;i<supernode->get_num_sktch();++i) {
+  for (int i = 0; i < supernode->get_num_sktch(); ++i) {
     ASSERT_EQ(*supernode->get_sketch(i), *supernode_batch->get_sketch(i));
   }
 }
@@ -193,13 +194,12 @@ TEST_F(SupernodeTestSuite, TestBatchUpdate) {
 TEST_F(SupernodeTestSuite, TestConcurrency) {
   int num_threads_per_group = 2;
   unsigned num_threads =
-       std::thread::hardware_concurrency() / num_threads_per_group - 1; // hyperthreading?
+      std::thread::hardware_concurrency() / num_threads_per_group - 1;  // hyperthreading?
   unsigned vec_len = 1000000;
   unsigned num_updates = 100000;
   Supernode::configure(vec_len);
 
-  std::vector<std::vector<vec_t>> test_vec(num_threads,
-                                          std::vector<vec_t>(num_updates));
+  std::vector<std::vector<vec_t>> test_vec(num_threads, std::vector<vec_t>(num_updates));
   for (unsigned i = 0; i < num_threads; ++i) {
     for (unsigned long j = 0; j < num_updates; ++j) {
       test_vec[i][j] = static_cast<vec_t>(rand() % vec_len);
@@ -207,10 +207,10 @@ TEST_F(SupernodeTestSuite, TestConcurrency) {
   }
   int seed = rand();
 
-  Supernode *supernode = Supernode::makeSupernode(vec_len, seed);
-  Supernode *piecemeal = Supernode::makeSupernode(vec_len, seed);
+  Supernode* supernode = Supernode::makeSupernode(vec_len, seed);
+  Supernode* piecemeal = Supernode::makeSupernode(vec_len, seed);
 
-  GraphWorker::set_config(0,num_threads_per_group); // set number of threads per omp parallel
+  GraphWorker::set_config(0, num_threads_per_group);  // set number of threads per omp parallel
 
   // concurrently run batch_updates
   std::thread thd[num_threads];
@@ -237,22 +237,19 @@ TEST_F(SupernodeTestSuite, TestConcurrency) {
 TEST_F(SupernodeTestSuite, TestSerialization) {
   std::vector<Supernode*> snodes;
   snodes.reserve(num_nodes);
-  for (unsigned i = 0; i < num_nodes; ++i)
-    snodes[i] = Supernode::makeSupernode(num_nodes, seed);
+  for (unsigned i = 0; i < num_nodes; ++i) snodes[i] = Supernode::makeSupernode(num_nodes, seed);
 
   // insert all edges
-  for (auto edge : *graph_edges) {
-    vec_t encoded = concat_pairing_fn(edge.first, edge
-          .second);
-    snodes[edge.first]->update(encoded);
-    snodes[edge.second]->update(encoded);
+  for (auto edge : graph_edges) {
+    vec_t encoded = concat_pairing_fn(edge.src, edge.dst);
+    snodes[edge.src]->update(encoded);
+    encoded = concat_pairing_fn(edge.dst, edge.src);
+    snodes[edge.dst]->update(encoded);
   }
 
-  std::cout << "Finished inserting" << std::endl;
   auto file = std::fstream("./out_supernode.txt", std::ios::out | std::ios::binary);
-  snodes[num_nodes/2]->write_binary(file);
+  snodes[num_nodes / 2]->write_binary(file);
   file.close();
-  std::cout << "Finished writing" << std::endl;
 
   auto in_file = std::fstream("./out_supernode.txt", std::ios::in | std::ios::binary);
 
diff --git a/test/util/efficient_gen/edge_gen.cpp b/test/util/efficient_gen/edge_gen.cpp
index 782ca5bf..5187a756 100644
--- a/test/util/efficient_gen/edge_gen.cpp
+++ b/test/util/efficient_gen/edge_gen.cpp
@@ -36,7 +36,8 @@ void write_edges(ul n, double p, const std::string& out_f) {
 
   std::cout << "Writing edges to file" << std::endl;
   while (m--) {
-    out << inv_concat_pairing_fn(arr[m]) << std::endl;
+    Edge e = inv_concat_pairing_fn(arr[m]);
+    out << e.src << " " << e.dst << std::endl;
   }
 
   out.close();
@@ -78,7 +79,8 @@ void insert_delete(double p, const std::string& in_file, const std::string& out_
     int temp = i%2;
     for (unsigned j=0;j<ins_del_arr[i];++j) {
       out << temp << " ";
-      out << inv_concat_pairing_fn(memoized[j]) << std::endl;
+      Edge e = inv_concat_pairing_fn(memoized[j]);
+      out << e.src << " " << e.dst << std::endl;
     }
   }
   free(memoized);
diff --git a/test/util/file_graph_verifier.cpp b/test/util/file_graph_verifier.cpp
index 702c1a09..dc40103f 100644
--- a/test/util/file_graph_verifier.cpp
+++ b/test/util/file_graph_verifier.cpp
@@ -5,22 +5,28 @@
 #include <algorithm>
 #include <cassert>
 
-FileGraphVerifier::FileGraphVerifier(const std::string &input_file) {
-  kruskal_ref = kruskal(input_file);
+FileGraphVerifier::FileGraphVerifier(node_id_t n, const std::string &input_file) : sets(n) {
   std::ifstream in(input_file);
-  node_id_t n;
+  if (!in) {
+    throw std::invalid_argument("FileGraphVerifier: Could not open: " + input_file);
+  }
+
+  kruskal_ref = kruskal(input_file);
+  node_id_t num_nodes;
   edge_id_t m;
   node_id_t a, b;
-  in >> n >> m;
-  sets = DisjointSetUnion<node_id_t>(n);
+  in >> num_nodes >> m;
+  if (num_nodes != n) throw std::invalid_argument("num_nodes != n in FileGraphVerifier");
+
   for (unsigned i = 0; i < n; ++i) {
     boruvka_cc.push_back({i});
-    det_graph.emplace_back();
+    adj_matrix.emplace_back(n - i);
   }
   while (m--) {
     in >> a >> b;
-    det_graph[a].insert(b);
-    det_graph[b].insert(a);
+    if (a > b) std::swap(a, b);
+    b = b - a;
+    adj_matrix[a][b] = !adj_matrix[a][b];
   }
   in.close();
 }
@@ -30,17 +36,17 @@ std::vector<std::set<node_id_t>> FileGraphVerifier::kruskal(const std::string& i
   node_id_t n;
   edge_id_t m;
   in >> n >> m;
-  DisjointSetUnion<node_id_t> sets(n);
+  DisjointSetUnion<node_id_t> kruskal_sets(n);
   int a,b;
   while (m--) {
     in >> a >> b;
-    sets.union_set(a,b);
+    kruskal_sets.merge(a,b);
   }
   in.close();
 
   std::map<node_id_t, std::set<node_id_t>> temp;
   for (unsigned i = 0; i < n; ++i) {
-    temp[sets.find_set(i)].insert(i);
+    temp[kruskal_sets.find_root(i)].insert(i);
   }
 
   std::vector<std::set<node_id_t>> retval;
@@ -52,27 +58,24 @@ std::vector<std::set<node_id_t>> FileGraphVerifier::kruskal(const std::string& i
 }
 
 void FileGraphVerifier::verify_edge(Edge edge) {
-  auto f = sets.find_set(edge.first);
-  auto s = sets.find_set(edge.second);
-  if (boruvka_cc[f].find(edge.second) != boruvka_cc[f].end()
-  || boruvka_cc[s].find(edge.first) != boruvka_cc[s].end()) {
-    printf("Got an error of node %u to node (1)%u\n", edge.first, edge.second);
+  if (edge.src > edge.dst) std::swap(edge.src, edge.dst);
+  if (!adj_matrix[edge.src][edge.dst - edge.src]) {
+    printf("Got an error on edge (%u, %u): edge is not in graph!\n", edge.src, edge.dst);
     throw BadEdgeException();
   }
-  if (det_graph[edge.first].find(edge.second) == det_graph[edge.first].end()) {
-    printf("Got an error of node %u to node (2)%u\n", edge.first, edge.second);
+
+  DSUMergeRet<node_id_t> ret = sets.merge(edge.src, edge.dst);
+  if (!ret.merged) {
+    printf("Got an error of node (%u, %u): components already joined!\n", edge.src, edge.dst);
     throw BadEdgeException();
   }
 
   // if all checks pass, merge supernodes
-  sets.link(f, s);
-  if (s == sets.find_set(s))
-    std::swap(f,s);
-  for (auto& i : boruvka_cc[s]) boruvka_cc[f].insert(i);
+  for (auto& i : boruvka_cc[ret.child]) boruvka_cc[ret.root].insert(i);
 }
 
 void FileGraphVerifier::verify_cc(node_id_t node) {
-  node = sets.find_set(node);
+  node = sets.find_root(node);
   for (const auto& cc : kruskal_ref) {
     if (boruvka_cc[node] == cc) return;
   }
@@ -83,6 +86,8 @@ void FileGraphVerifier::verify_soln(std::vector<std::set<node_id_t>> &retval) {
   auto temp {retval};
   std::sort(temp.begin(),temp.end());
   std::sort(kruskal_ref.begin(),kruskal_ref.end());
-  assert(kruskal_ref == temp);
+  if (kruskal_ref != temp)
+    throw IncorrectCCException();
+
   std::cout << "Solution ok: " << retval.size() << " CCs found." << std::endl;
 }
diff --git a/test/util/graph_gen.cpp b/test/util/graph_gen.cpp
index b5bac3b7..a51529e1 100644
--- a/test/util/graph_gen.cpp
+++ b/test/util/graph_gen.cpp
@@ -33,7 +33,8 @@ void write_edges(long n, double p, const std::string& out_f) {
   out << n << " " << m << endl;
 
   while (m--) {
-    out << inv_concat_pairing_fn(arr[m]) << endl;
+    Edge e = inv_concat_pairing_fn(arr[m]);
+    out << e.src << " " << e.dst << endl;
   }
   out.flush();
   out.close();
@@ -89,7 +90,8 @@ void insert_delete(double p, int max_appearances, const std::string& in_file,
     int temp = i % 2;
     for (unsigned j = 0; j < ins_del_arr[i]; ++j) {
       out << temp << " ";
-      out << inv_concat_pairing_fn(memoized[j]) << endl;
+      Edge e = inv_concat_pairing_fn(memoized[j]);
+      out << e.src << " " << e.dst << endl;
     }
   }
   out.flush();
diff --git a/test/util/graph_verifier_test.cpp b/test/util/graph_verifier_test.cpp
index 7021c13a..1646c9bc 100644
--- a/test/util/graph_verifier_test.cpp
+++ b/test/util/graph_verifier_test.cpp
@@ -10,7 +10,7 @@ TEST(DeterministicToolsTestSuite, TestKruskal) {
 }
 
 TEST(DeterministicToolsTestSuite, TestEdgeVerifier) {
-  FileGraphVerifier verifier(curr_dir+"/../res/multiples_graph_1024.txt");
+  FileGraphVerifier verifier(1024, curr_dir+"/../res/multiples_graph_1024.txt");
   // add edges of the form {i,2i}
   for (int i = 2; i < 512; ++i) {
     verifier.verify_edge({i,i*2});
@@ -27,7 +27,7 @@ TEST(DeterministicToolsTestSuite, TestEdgeVerifier) {
 }
 
 TEST(DeterministicToolsTestSuite, TestCCVerifier) {
-  FileGraphVerifier verifier (curr_dir+"/../res/multiples_graph_1024.txt");
+  FileGraphVerifier verifier (1024, curr_dir+"/../res/multiples_graph_1024.txt");
   // {0}, {1}, and primes \in [521,1021] are CCs
   verifier.verify_cc(0);
   verifier.verify_cc(1);
diff --git a/test/util/mat_graph_verifier.cpp b/test/util/mat_graph_verifier.cpp
index 866fd7c1..2ceba25c 100644
--- a/test/util/mat_graph_verifier.cpp
+++ b/test/util/mat_graph_verifier.cpp
@@ -5,10 +5,10 @@
 #include <algorithm>
 #include <cassert>
 
-MatGraphVerifier::MatGraphVerifier(node_id_t n) : n(n) {
-  adj_graph = std::vector<std::vector<bool>>(n);
+MatGraphVerifier::MatGraphVerifier(node_id_t n) : n(n), sets(n) {
+  adj_matrix = std::vector<std::vector<bool>>(n);
   for (node_id_t i = 0; i < n; ++i)
-    adj_graph[i] = std::vector<bool>(n - i);
+    adj_matrix[i] = std::vector<bool>(n - i);
 }
 
 void MatGraphVerifier::edge_update(node_id_t src, node_id_t dst) {
@@ -17,30 +17,30 @@ void MatGraphVerifier::edge_update(node_id_t src, node_id_t dst) {
   dst = dst - src;
   
   // update adj_matrix entry
-  adj_graph[src][dst] = !adj_graph[src][dst];
+  adj_matrix[src][dst] = !adj_matrix[src][dst];
 }
   
 
 void MatGraphVerifier::reset_cc_state() {
   kruskal_ref = kruskal();
-  sets = DisjointSetUnion<node_id_t>(n);
+  sets.reset();
   boruvka_cc.clear();
   for (node_id_t i = 0; i < n; ++i)
     boruvka_cc.push_back({i});
 }
 
 std::vector<std::set<node_id_t>> MatGraphVerifier::kruskal() {
-  DisjointSetUnion<node_id_t> sets(n);
+  DisjointSetUnion<node_id_t> kruskal_dsu(n);
 
   for (node_id_t i = 0; i < n; i++) {
-    for (node_id_t j = 0; j < adj_graph[i].size(); j++) {
-      if (adj_graph[i][j]) sets.union_set(i, i + j);
+    for (node_id_t j = 0; j < adj_matrix[i].size(); j++) {
+      if (adj_matrix[i][j]) kruskal_dsu.merge(i, i + j);
     }
   }
 
   std::map<node_id_t, std::set<node_id_t>> temp;
   for (unsigned i = 0; i < n; ++i) {
-    temp[sets.find_set(i)].insert(i);
+    temp[kruskal_dsu.find_root(i)].insert(i);
   }
 
   std::vector<std::set<node_id_t>> retval;
@@ -52,28 +52,25 @@ std::vector<std::set<node_id_t>> MatGraphVerifier::kruskal() {
 }
 
 void MatGraphVerifier::verify_edge(Edge edge) {
-  auto f = sets.find_set(edge.first);
-  auto s = sets.find_set(edge.second);
-  if (boruvka_cc[f].find(edge.second) != boruvka_cc[f].end()
-  || boruvka_cc[s].find(edge.first) != boruvka_cc[s].end()) {
-    printf("Got an error of node %u to node (1)%u\n", edge.first, edge.second);
+  // verify that the edge in question actually exists
+  if (edge.src > edge.dst) std::swap(edge.src, edge.dst);
+  if (!adj_matrix[edge.src][edge.dst - edge.src]) {
+    printf("Got an error on edge (%u, %u): edge is not in adj_matrix\n", edge.src, edge.dst);
     throw BadEdgeException();
   }
-  if (edge.first > edge.second) std::swap(edge.first, edge.second);
-  if (!adj_graph[edge.first][edge.second - edge.first]) {
-    printf("Got an error of node %u to node (2)%u\n", edge.first, edge.second);
+
+  DSUMergeRet<node_id_t> ret = sets.merge(edge.src, edge.dst); // perform the merge
+  if (!ret.merged) {
+    printf("Got an error on edge (%u, %u): components already joined!\n", edge.src, edge.dst);
     throw BadEdgeException();
   }
 
-  // if all checks pass, merge supernodes
-  sets.link(f, s);
-  if (s == sets.find_set(s))
-    std::swap(f,s);
-  for (auto& i : boruvka_cc[s]) boruvka_cc[f].insert(i);
+  // if all checks pass, update boruvka_cc by merging in set elements of child
+  for (auto& i : boruvka_cc[ret.child]) boruvka_cc[ret.root].insert(i);
 }
 
 void MatGraphVerifier::verify_cc(node_id_t node) {
-  node = sets.find_set(node);
+  node = sets.find_root(node);
   for (const auto& cc : kruskal_ref) {
     if (boruvka_cc[node] == cc) return;
   }
@@ -84,6 +81,8 @@ void MatGraphVerifier::verify_soln(std::vector<std::set<node_id_t>> &retval) {
   auto temp {retval};
   std::sort(temp.begin(),temp.end());
   std::sort(kruskal_ref.begin(),kruskal_ref.end());
-  assert(kruskal_ref == temp);
+  if (kruskal_ref != temp)
+    throw IncorrectCCException();
+
   std::cout << "Solution ok: " << retval.size() << " CCs found." << std::endl;
-}
\ No newline at end of file
+}
diff --git a/test/util_test.cpp b/test/util_test.cpp
index 6a5c2fa3..edbf392b 100644
--- a/test/util_test.cpp
+++ b/test/util_test.cpp
@@ -2,13 +2,14 @@
 #include "../include/util.h"
 
 TEST(UtilTestSuite, TestConcatPairingFn) {
-  std::pair<uint32_t,uint32_t> exp;
+  Edge exp;
   for (int i = 0; i < 1000; ++i) {
     for (int j = 0; j < 1000; ++j) {
       if (i==j) continue;
-      exp = {std::min(i,j),std::max(i,j)};
-      ASSERT_EQ(exp, inv_concat_pairing_fn
-            (concat_pairing_fn(i, j)));
+      exp.src = std::min(i,j);
+      exp.dst = std::max(i,j);
+      ASSERT_EQ(exp.src, inv_concat_pairing_fn(concat_pairing_fn(i, j)).src);
+      ASSERT_EQ(exp.dst, inv_concat_pairing_fn(concat_pairing_fn(i, j)).dst);
     }
   }
 }
diff --git a/tools/benchmark/BENCH.md b/tools/benchmark/BENCH.md
index fa3bb5d4..7e233d8a 100644
--- a/tools/benchmark/BENCH.md
+++ b/tools/benchmark/BENCH.md
@@ -1,5 +1,5 @@
 # GraphStreamingCC Benchmarking
-This file provides documentation for the various benchmarks that measure the performance of the GraphStreamingCC library. 
+This file provides documentation for the various benchmarks that measure the performance of the GraphZeppelin library. 
 These benchmarks use [Google Benchmark](https://github.com/google/benchmark) and are built when the cmake variable BUILD_BENCH is true.
 
 ## Usage
@@ -8,14 +8,15 @@ The optional google benchmark options control which benchmarks are executed, how
 See their documentation for more details or type `./bench_cc --help` for more usage information.
 
 ## Output
-`bench_cc` first outputs the name of the benchmark followed by arguments `{BenchName}/{Arg1}/{Arg2}...`. 
+`bench_cc` first outputs the name of the benchmark followed by arguments `{BenchName}/{Arg1}/{Arg2}...`.
 Then the absolute `Time` and `CPU` time per iteration in addition to the number of iterations performed.
 Finally, `UserCounters` gives performance information unique to each benchmark.
 
 ## Benchmarks
 ### Hashing
 Measures the performance of a variety of hashing methods against the current method used by `Bucket_Boruvka`. 
-All methods hash a single 64 bit input 8 times using different hash seeds.  
+All methods hash a single 64 bit input 8 times using different hash seeds.
+
 Example output:
 ```
 --------------------------------------------------------------------------------------
@@ -26,10 +27,11 @@ BM_Hash_XXH64/100                 1943 ns         1943 ns       361388 Hashes=51
 BM_Hash_XXH64/10000             191324 ns       191322 ns         3632 Hashes=52.2679M/s
 ```
 These results indicate that XXH64 can perform 48.2 million hashes per second when hashing 1 update per hash seed.
-Additionally they tell us that better hash performance is found when hashing 100 or 10000 updates serially per hash seed.
+Additionally they tell us that better hash performance is found when hashing many updates, that all share a hash seed, one after the other.
 
 ### Sketch Updates
-This benchmark tests the performance of performing sketch updates serially or batched with vectors of different sizes.  
+This benchmark tests the performance of performing sketch updates serially or batched with vectors of different sizes.
+
 Example output:
 ```
 --------------------------------------------------------------------------------------
@@ -44,8 +46,10 @@ As expected performing updates upon a vector of size 16384 is faster than that o
 However, the size of the vector seems not to make the same impact when batching updates to the sketches(1).
 
 ### Sketch Queries
-Tests the performance of sketch queries with different numbers of updates applied. The minimum number of updates per sketch is 1.
+Tests the performance of sketch queries with different numbers of updates applied. 
+The minimum number of updates per sketch is 1.
 In this benchmark 100 sketches with a vector size of approximately 32 thousand each are queried.
+
 Example output:
 ```
 --------------------------------------------------------------------------------------
@@ -57,9 +61,33 @@ BM_Sketch_Query/10                5365 ns         5365 ns       131777 Query_Rat
 These results indicate that somewhat small and sparsely populated sketches can be queried with relatively low latency (5ns).
 Once the number of non-zero elements grows this query latency grows quickly.
 
+### DSU Merging
+In this test we merge elements in a DSU in a binary tree pattern. 
+We first merge singletons, then groups of two, then 4, ...
+The number of elements in the DSU is approximately 16 million.
+
+To test the performance of the DSU we create this tree by following two different update patterns. 
+The first is 'Adversarial' in that we purposefully join elements that are not roots.
+The other one 'Root' is friendly and only joins directly by root.
+
+Example output:
+```
+------------------------------------------------------------------------------------
+Benchmark                          Time             CPU   Iterations UserCounters...
+------------------------------------------------------------------------------------
+BM_DSU_Adversarial        2494977485 ns   2494916611 ns            1 Merge_Latency=148.709ns
+BM_DSU_Root               1859009603 ns   1858916553 ns            1 Merge_Latency=110.8ns
+```
+This result tells us that a friendly access pattern that only joins by roots results in an average merge latency reduction of roughly 30%.
+This latency is the time required, on average, to merge two sets in the DSU.
+
 ### File Ingestion
 Tests the speed of reading a graph stream from a file with a variety of buffer sizes.
+By default these benchmarks are not enabled. 
+There is a flag at the top of `graphcc_bench.cpp` called `FILE_INGEST_F`. 
+Defining this flag enables this tests.
 This benchmark requires root privileges to flush the file system cache between iterations.
+
 Example output:
 ```
 --------------------------------------------------------------------------------------
diff --git a/tools/benchmark/graphcc_bench.cpp b/tools/benchmark/graphcc_bench.cpp
index e914d98e..76d2b713 100644
--- a/tools/benchmark/graphcc_bench.cpp
+++ b/tools/benchmark/graphcc_bench.cpp
@@ -1,19 +1,27 @@
 #include <benchmark/benchmark.h>
-#include <xxhash.h>
-#include <iostream>
 #include <unistd.h>
+#include <xxhash.h>
+
+#include <algorithm>
 #include <fstream>
-#include <vector>
+#include <iostream>
+#include <random>
 #include <thread>
+#include <vector>
 
 #include "binary_graph_stream.h"
 #include "bucket.h"
+#include "dsu.h"
 #include "test/sketch_constructors.h"
 
-constexpr uint64_t KB   = 1024;
-constexpr uint64_t MB   = KB * KB;
+constexpr uint64_t KB = 1024;
+constexpr uint64_t MB = KB * KB;
 constexpr uint64_t seed = 374639;
 
+// If this flag is uncommented then run the FileIngestion benchmarks
+// #define FILE_INGEST_F
+
+#ifdef FILE_INGEST_F
 // Linux-only, flush the filesystem cache
 // requires sudo privileges :(
 static void flush_filesystem_cache() {
@@ -21,8 +29,7 @@ static void flush_filesystem_cache() {
   std::ofstream drop("/proc/sys/vm/drop_caches");
   if (drop.is_open()) {
     drop << "3" << std::endl;
-  }
-  else {
+  } else {
     std::cout << "WARNING: could not drop filesystem cache. BM_FileIngest will be inaccurate. ";
     std::cout << "Running as root may be required." << std::endl;
   }
@@ -33,16 +40,16 @@ static void BM_FileIngest(benchmark::State &state) {
   // determine the number of edges in the graph
   uint64_t num_edges;
   {
-    BinaryGraphStream stream("/mnt/ssd2/binary_streams/kron_16_stream_binary", 1024);
+    BinaryGraphStream stream("/mnt/ssd2/binary_streams/kron_15_stream_binary", 1024);
     num_edges = stream.edges();
   }
 
   // flush fs cache
   flush_filesystem_cache();
-  
+
   // perform benchmark
   for (auto _ : state) {
-    BinaryGraphStream stream("/mnt/ssd2/binary_streams/kron_16_stream_binary", state.range(0));
+    BinaryGraphStream stream("/mnt/ssd2/binary_streams/kron_15_stream_binary", state.range(0));
 
     uint64_t m = stream.edges();
     GraphUpdate upd;
@@ -50,7 +57,8 @@ static void BM_FileIngest(benchmark::State &state) {
       benchmark::DoNotOptimize(upd = stream.get_edge());
     }
   }
-  state.counters["Ingestion_Rate"] = benchmark::Counter(state.iterations() * num_edges, benchmark::Counter::kIsRate);
+  state.counters["Ingestion_Rate"] = benchmark::Counter(
+    state.iterations() * num_edges, benchmark::Counter::kIsRate);
 }
 BENCHMARK(BM_FileIngest)->RangeMultiplier(2)->Range(KB << 2, MB / 4)->UseRealTime();
 
@@ -59,41 +67,41 @@ static void BM_MTFileIngest(benchmark::State &state) {
   // determine the number of edges in the graph
   uint64_t num_edges;
   {
-    BinaryGraphStream_MT stream("/mnt/ssd2/binary_streams/kron_16_stream_binary", 1024);
+    BinaryGraphStream_MT stream("/mnt/ssd2/binary_streams/kron_15_stream_binary", 1024);
     num_edges = stream.edges();
   }
 
   // flush fs cache
   flush_filesystem_cache();
-  
+
   // perform benchmark
   for (auto _ : state) {
     std::vector<std::thread> threads;
     threads.reserve(state.range(0));
 
-    BinaryGraphStream_MT stream("/mnt/ssd2/binary_streams/kron_16_stream_binary", 32 * 1024);
+    BinaryGraphStream_MT stream("/mnt/ssd2/binary_streams/kron_15_stream_binary", 32 * 1024);
 
-    auto task = [&](){
+    auto task = [&]() {
       MT_StreamReader reader(stream);
       GraphUpdate upd;
       do {
-	upd = reader.get_edge(); 
-      } while (upd.second != END_OF_FILE);
+        upd = reader.get_edge();
+      } while (upd.type != BREAKPOINT);
     };
 
-    for (int i = 0; i < state.range(0); i++)
-      threads.emplace_back(task);
-    for (int i = 0; i < state.range(0); i++)
-      threads[i].join();
+    for (int i = 0; i < state.range(0); i++) threads.emplace_back(task);
+    for (int i = 0; i < state.range(0); i++) threads[i].join();
   }
-  state.counters["Ingestion_Rate"] = benchmark::Counter(state.iterations() * num_edges, benchmark::Counter::kIsRate);
+  state.counters["Ingestion_Rate"] = benchmark::Counter(
+    state.iterations() * num_edges, benchmark::Counter::kIsRate);
 }
 BENCHMARK(BM_MTFileIngest)->RangeMultiplier(4)->Range(1, 20)->UseRealTime();
+#endif // FILE_INGEST_F
 
-// Test the speed of hashing using a method that loops over seeds and a method that 
+// Test the speed of hashing using a method that loops over seeds and a method that
 // batches by seed
 // The argument to this benchmark is the number of hashes to batch
-static void BM_Hash_XXH64(benchmark::State &state) { 
+static void BM_Hash_XXH64(benchmark::State &state) {
   uint64_t num_seeds = 8;
   uint64_t num_hashes = state.range(0);
   uint64_t output;
@@ -104,11 +112,12 @@ static void BM_Hash_XXH64(benchmark::State &state) {
       }
     }
   }
-  state.counters["Hashes"] = benchmark::Counter(state.iterations() * num_hashes, benchmark::Counter::kIsRate);
+  state.counters["Hash Rate"] = benchmark::Counter(
+    state.iterations() * num_hashes, benchmark::Counter::kIsRate);
 }
 BENCHMARK(BM_Hash_XXH64)->Arg(1)->Arg(100)->Arg(10000);
 
-static void BM_Hash_XXH3_64(benchmark::State &state) { 
+static void BM_Hash_XXH3_64(benchmark::State &state) {
   uint64_t num_seeds = 8;
   uint64_t num_hashes = state.range(0);
   uint64_t output;
@@ -119,7 +128,8 @@ static void BM_Hash_XXH3_64(benchmark::State &state) {
       }
     }
   }
-  state.counters["Hashes"] = benchmark::Counter(state.iterations() * num_hashes, benchmark::Counter::kIsRate);
+  state.counters["Hash Rate"] = benchmark::Counter(
+    state.iterations() * num_hashes, benchmark::Counter::kIsRate);
 }
 BENCHMARK(BM_Hash_XXH3_64)->Arg(1)->Arg(100)->Arg(10000);
 
@@ -134,14 +144,15 @@ static void BM_Hash_bucket(benchmark::State &state) {
       }
     }
   }
-  state.counters["Hashes"] = benchmark::Counter(state.iterations() * num_hashes, benchmark::Counter::kIsRate);
+  state.counters["Hash Rate"] = benchmark::Counter(
+    state.iterations() * num_hashes, benchmark::Counter::kIsRate);
 }
 BENCHMARK(BM_Hash_bucket)->Arg(1)->Arg(100)->Arg(10000);
 
 // Benchmark the speed of updating sketches both serially and in batch mode
 static void BM_Sketch_Update(benchmark::State &state) {
   constexpr size_t upd_per_sketch = 10000;
-  constexpr size_t num_sketches   = 1000;
+  constexpr size_t num_sketches = 1000;
   size_t vec_size = state.range(0);
   // initialize sketches
   Sketch::configure(vec_size, 100);
@@ -178,16 +189,16 @@ static void BM_Sketch_Update(benchmark::State &state) {
       }
     }
   }
-  state.counters["Update_Rate"] = benchmark::Counter(state.iterations() * upd_per_sketch * num_sketches,
-                                                     benchmark::Counter::kIsRate);
-  state.counters["Hashes"] = benchmark::Counter(state.iterations() * upd_per_sketch * num_sketches * 7,
-                                                benchmark::Counter::kIsRate);
+  state.counters["Update_Rate"] = benchmark::Counter(
+      state.iterations() * upd_per_sketch * num_sketches, benchmark::Counter::kIsRate);
+  state.counters["Hashes"] = benchmark::Counter(
+      state.iterations() * upd_per_sketch * num_sketches * 7, benchmark::Counter::kIsRate);
 }
 BENCHMARK(BM_Sketch_Update)->RangeMultiplier(4)->Ranges({{KB << 4, MB << 4}, {false, true}});
 
 // Benchmark the speed of querying sketches
 static void BM_Sketch_Query(benchmark::State &state) {
-  constexpr size_t vec_size     = KB << 5;
+  constexpr size_t vec_size = KB << 5;
   constexpr size_t num_sketches = 100;
   double density = ((double)state.range(0)) / 100;
 
@@ -213,8 +224,73 @@ static void BM_Sketch_Query(benchmark::State &state) {
       sketches[j]->reset_queried();
     }
   }
-  state.counters["Query_Rate"] = benchmark::Counter(state.iterations() * num_sketches, benchmark::Counter::kIsRate);
+  state.counters["Query_Rate"] = benchmark::Counter(
+    state.iterations() * num_sketches, benchmark::Counter::kIsRate);
 }
 BENCHMARK(BM_Sketch_Query)->DenseRange(0, 90, 10);
 
+// Benchmark speed of DSU merges when the sequence of merges is adversarial
+// This means we avoid joining roots wherever possible
+static void BM_DSU_Adversarial(benchmark::State &state) {
+  constexpr size_t size_of_dsu = 16 * MB;
+
+  auto rng = std::default_random_engine{};
+
+  std::vector<std::pair<node_id_t, node_id_t>> updates;
+  // generate updates
+  for (size_t iter = 0; ((size_t)2 << iter) <= size_of_dsu; iter++) {
+    size_t loc_size = 1 << iter;
+    size_t jump = 2 << iter;
+    std::vector<std::pair<node_id_t, node_id_t>> new_updates;
+    for (size_t i = 0; i < size_of_dsu; i += jump) {
+      new_updates.push_back({i + loc_size - 1, i + loc_size - 1 + jump / 2});
+    }
+    std::shuffle(new_updates.begin(), new_updates.end(), rng);
+    updates.insert(updates.end(), new_updates.begin(), new_updates.end());
+  }
+
+  // Perform merge test
+  for (auto _ : state) {
+    DisjointSetUnion<node_id_t> dsu(size_of_dsu);
+    for (auto upd : updates) {
+      dsu.merge(upd.first, upd.second);
+    }
+  }
+  state.counters["Merge_Latency"] = benchmark::Counter(state.iterations() * updates.size(),
+                         benchmark::Counter::kIsRate | benchmark::Counter::kInvert);
+}
+BENCHMARK(BM_DSU_Adversarial);
+
+// Benchmark speed of DSU merges when the sequence of merges is helpful
+// this means we only join roots
+static void BM_DSU_Root(benchmark::State &state) {
+  constexpr size_t size_of_dsu = 16 * MB;
+
+  auto rng = std::default_random_engine{};
+
+  // generate updates
+  std::vector<std::pair<node_id_t, node_id_t>> updates;
+  // generate updates
+  for (size_t iter = 0; ((size_t)2 << iter) <= size_of_dsu; iter++) {
+    size_t jump = 2 << iter;
+    std::vector<std::pair<node_id_t, node_id_t>> new_updates;
+    for (size_t i = 0; i < size_of_dsu; i += jump) {
+      new_updates.push_back({i, i + jump / 2});
+    }
+    std::shuffle(new_updates.begin(), new_updates.end(), rng);
+    updates.insert(updates.end(), new_updates.begin(), new_updates.end());
+  }
+
+  // Perform merge test
+  for (auto _ : state) {
+    DisjointSetUnion<node_id_t> dsu(size_of_dsu);
+    for (auto upd : updates) {
+      dsu.merge(upd.first, upd.second);
+    }
+  }
+  state.counters["Merge_Latency"] = benchmark::Counter(state.iterations() * updates.size(),
+                         benchmark::Counter::kIsRate | benchmark::Counter::kInvert);
+}
+BENCHMARK(BM_DSU_Root);
+
 BENCHMARK_MAIN();
diff --git a/tools/statistical_testing/graph_testing.cpp b/tools/statistical_testing/graph_testing.cpp
index 3d86539d..d7e6efa1 100644
--- a/tools/statistical_testing/graph_testing.cpp
+++ b/tools/statistical_testing/graph_testing.cpp
@@ -18,7 +18,7 @@ static inline int do_run() {
         g.update({{a, b}, INSERT});
       } else g.update({{a, b}, DELETE});
     }
-    g.set_verifier(std::make_unique<FileGraphVerifier>("./cumul_sample.txt"));
+    g.set_verifier(std::make_unique<FileGraphVerifier>(n, "./cumul_sample.txt"));
     try {
         g.connected_components();
     } catch (std::exception const &err) {
@@ -93,6 +93,4 @@ int main() {
         trial_list.clear();
         out.close();
     }
-    
-    
 }
diff --git a/tools/validate_binary_stream.cpp b/tools/validate_binary_stream.cpp
index dca9f27a..e210c816 100644
--- a/tools/validate_binary_stream.cpp
+++ b/tools/validate_binary_stream.cpp
@@ -28,10 +28,10 @@ int main(int argc, char **argv) {
       std::rethrow_exception(std::current_exception());
       break;
     }
-    Edge edge = upd.first;
-    UpdateType u = upd.second;
-    if (edge.first >= nodes || edge.second >= nodes || (u != INSERT && u != DELETE) || edge.first == edge.second) {
-      std::cerr << "ERROR: edge idx:" << e << "=(" << edge.first << "," << edge.second << "), " << u << std::endl;
+    Edge edge = upd.edge;
+    UpdateType u = upd.type;
+    if (edge.src >= nodes || edge.dst >= nodes || (u != INSERT && u != DELETE) || edge.src == edge.dst) {
+      std::cerr << "ERROR: edge idx:" << e << "=(" << edge.src << "," << edge.dst << "), " << u << std::endl;
       err = true;
     }
     if (e % 1000000000 == 0 && e != 0) std::cout << e << std::endl; 
