diff --git a/include/l0_sampling/bucket.h b/include/l0_sampling/bucket.h
index 05735589..c70453b5 100644
--- a/include/l0_sampling/bucket.h
+++ b/include/l0_sampling/bucket.h
@@ -50,7 +50,7 @@ inline col_hash_t Bucket_Boruvka::get_index_depth(const vec_t update_idx, const
  const vec_hash_t max_depth) {
   col_hash_t depth_hash = col_hash(&update_idx, sizeof(vec_t), seed_and_col);
   depth_hash |= (1ull << max_depth); // assert not > max_depth by ORing
-  return __builtin_ctzl(depth_hash);
+  return __builtin_ctzll(depth_hash);
 }
 
 inline vec_hash_t Bucket_Boruvka::get_index_hash(const vec_t update_idx, const long sketch_seed) {
diff --git a/include/l0_sampling/sketch.h b/include/l0_sampling/sketch.h
index 16f61f48..fe5cc863 100644
--- a/include/l0_sampling/sketch.h
+++ b/include/l0_sampling/sketch.h
@@ -14,10 +14,6 @@
 #include "../util.h"
 #include "bucket.h"
 
-// max number of non-zeroes in vector is n/2*n/2=n^2/4
-#define guess_gen(x) double_to_ull(log2(x) - 2)
-#define bucket_gen(d) double_to_ull((log2(d) + 1))
-
 enum SampleSketchRet {
   GOOD,  // querying this sketch returned a single non-zero value
   ZERO,  // querying this sketch returned that there are no non-zero values
@@ -34,7 +30,7 @@ class Sketch {
   static vec_t failure_factor;  // Pr(failure) = 1 / factor. Determines number of columns in sketch.
   static vec_t n;               // Length of the vector this is sketching.
   static size_t num_elems;      // length of our actual arrays in number of elements
-  static size_t num_buckets;    // Portion of array length, number of buckets
+  static size_t num_columns;    // Portion of array length, number of columns
   static size_t num_guesses;    // Portion of array length, number of guesses
 
   // Seed used for hashing operations in this sketch.
@@ -52,14 +48,14 @@ class Sketch {
   FRIEND_TEST(EXPR_Parallelism, N10kU100k);
 
   // Buckets of this sketch.
-  // Length is bucket_gen(failure_factor) * guess_gen(n).
+  // Length is column_gen(failure_factor) * guess_gen(n).
   // For buckets[i * guess_gen(n) + j], the bucket has a 1/2^j probability
   // of containing an index. The first two are pointers into the buckets array.
   alignas(vec_t) char buckets[];
 
   // private constructors -- use makeSketch
   Sketch(uint64_t seed);
-  Sketch(uint64_t seed, std::istream& binary_in);
+  Sketch(uint64_t seed, std::istream& binary_in, bool sparse);
   Sketch(const Sketch& s);
 
  public:
@@ -73,7 +69,7 @@ class Sketch {
    * @return           A pointer to a newly constructed sketch
    */
   static Sketch* makeSketch(void* loc, uint64_t seed);
-  static Sketch* makeSketch(void* loc, uint64_t seed, std::istream& binary_in);
+  static Sketch* makeSketch(void* loc, uint64_t seed, std::istream& binary_in, bool sparse=false);
 
   /**
    * Copy constructor to create a sketch from another
@@ -92,9 +88,9 @@ class Sketch {
   inline static void configure(vec_t _n, vec_t _factor) {
     n = _n;
     failure_factor = _factor;
-    num_buckets = bucket_gen(failure_factor);
+    num_columns = column_gen(failure_factor);
     num_guesses = guess_gen(n);
-    num_elems = num_buckets * num_guesses + 1;  // +1 for zero bucket optimization
+    num_elems = num_columns * num_guesses + 1;  // +1 for zero bucket optimization
   }
 
   inline static size_t sketchSizeof() {
@@ -110,6 +106,8 @@ class Sketch {
 
   inline void reset_queried() { already_queried = false; }
 
+  inline static size_t get_columns() { return num_columns; }
+
   /**
    * Update a sketch based on information about one of its indices.
    * @param update the point update.
@@ -124,11 +122,19 @@ class Sketch {
 
   /**
    * Function to query a sketch.
-   * @return   A pair with the result index and a code indicating if the type of result.
+   * @return   A pair with the result index and a code indicating the type of result.
    */
   std::pair<vec_t, SampleSketchRet> query();
 
+  /*
+   * Function to query all columns within a sketch to return 1 or more non-zero indices
+   * @return   A pair with the result indices and a code indicating the type of result.
+   */
+  std::pair<std::vector<vec_t>, SampleSketchRet> exhaustive_query();
+
   inline uint64_t get_seed() const { return seed; }
+  inline size_t column_seed(size_t column_idx) const { return seed + column_idx*5; }
+  inline size_t checksum_seed() const { return seed; }
 
   /**
    * Operator to add a sketch to another one in-place. Guaranteed to be
@@ -144,10 +150,24 @@ class Sketch {
 
   /**
    * Serialize the sketch to a binary output stream.
-   * @param out the stream to write to.
+   * @param binary_out   the stream to write to.
    */
   void write_binary(std::ostream& binary_out);
   void write_binary(std::ostream& binary_out) const;
+
+  /**
+   * Serialize a sketch while optimizing for space
+   * This assumes that the sketch itself sparse
+   * Otherwise, this serialization will use more space
+   * @param binary_out   the stream to write to.
+   */
+  void write_sparse_binary(std::ostream& binary_out);
+  void write_sparse_binary(std::ostream& binary_out) const;
+
+
+  // max number of non-zeroes in vector is n/2*n/2=n^2/4
+  static size_t guess_gen(size_t x) { return double_to_ull(log2(x) - 2); }
+  static size_t column_gen(size_t d) { return double_to_ull((log2(d) + 1)); }
 };
 
 class MultipleQueryException : public std::exception {
diff --git a/include/supernode.h b/include/supernode.h
index 79e16244..8144c8e1 100644
--- a/include/supernode.h
+++ b/include/supernode.h
@@ -5,21 +5,28 @@
 
 #include "l0_sampling/sketch.h"
 
+enum SerialType {
+  FULL,
+  PARTIAL,
+  SPARSE,
+};
+
 /**
  * This interface implements the "supernode" so Boruvka can use it as a black
  * box without needing to worry about implementing l_0.
  */
 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 max_sketches;
+  static size_t bytes_size; // the size of a super-node in bytes including the sketches
   static size_t serialized_size; // the size of a supernode that has been serialized
-  int idx;
-  int num_sketches;
+  size_t sample_idx;
   std::mutex node_mt;
 
   FRIEND_TEST(SupernodeTestSuite, TestBatchUpdate);
   FRIEND_TEST(SupernodeTestSuite, TestConcurrency);
   FRIEND_TEST(SupernodeTestSuite, TestSerialization);
+  FRIEND_TEST(SupernodeTestSuite, TestPartialSparseSerialization);
+  FRIEND_TEST(SupernodeTestSuite, SketchesHaveUniqueSeeds);
   FRIEND_TEST(GraphTestSuite, TestCorrectnessOfReheating);
   FRIEND_TEST(GraphTest, TestSupernodeRestoreAfterCCFailure);
   FRIEND_TEST(EXPR_Parallelism, N10kU100k);
@@ -29,6 +36,7 @@ class Supernode {
   const uint64_t seed; // for creating a copy
   
 private:
+  size_t num_sketches;
   size_t sketch_size;
 
   /* collection of logn sketches to query from, since we can't query from one
@@ -79,10 +87,11 @@ class Supernode {
 
   ~Supernode();
 
-  static inline void configure(uint64_t n, vec_t sketch_fail_factor=100) {
+  static inline void configure(uint64_t n, vec_t sketch_fail_factor=default_fail_factor) {
     Sketch::configure(n*n, sketch_fail_factor);
-    bytes_size = sizeof(Supernode) + size_t(log2(n)/(log2(3)-1)) * Sketch::sketchSizeof();
-    serialized_size = size_t(log2(n)/(log2(3)-1)) * Sketch::serialized_size();
+    max_sketches = log2(n)/(log2(3)-1);
+    bytes_size = sizeof(Supernode) + max_sketches * Sketch::sketchSizeof();
+    serialized_size = max_sketches * Sketch::serialized_size();
   }
 
   static inline size_t get_size() {
@@ -98,28 +107,28 @@ class Supernode {
     return sketch_size;
   }
 
-  // return the number of sketches held in this supernode
-  int get_num_sktch() { return num_sketches; };
+  // return the maximum number of sketches held in by a Supernode
+  // most Supernodes will hold this many sketches
+  static int get_max_sketches() { return max_sketches; };
+
+  // get number of samples remaining in the Supernode
+  int samples_remaining() { return num_sketches - sample_idx; }
 
   inline bool out_of_queries() {
-    return idx == num_sketches;
+    return sample_idx >= num_sketches;
   }
 
   inline int curr_idx() {
-    return idx;
-  }
-
-  inline void incr_idx() {
-    ++idx;
+    return sample_idx;
   }
 
   // reset the supernode query metadata
   // we use this when resuming insertions after CC made copies in memory
   inline void reset_query_state() { 
-    for (int i = 0; i < idx; i++) {
+    for (size_t i = 0; i < sample_idx; i++) {
       get_sketch(i)->reset_queried();
     }
-    idx = 0;
+    sample_idx = 0;
   }
 
   // get the ith sketch in the sketch array as a const object
@@ -135,6 +144,15 @@ class Supernode {
    */
   std::pair<Edge, SampleSketchRet> sample();
 
+  /**
+   * Function to sample 1 or more edges from the cut of a supernode.
+   * This function runs a query that samples from all columns in a single Sketch
+   * @return    an list of edges in the cut, each represented as an Edge with LHS <= RHS,
+   *            if one exists. Additionally, returns a code represnting the sample
+   *            result (good, zero, or fail)
+   */
+  std::pair<std::vector<Edge>, SampleSketchRet> exhaustive_sample();
+
   /**
    * In-place merge function. Guaranteed to update the caller Supernode.
    */
@@ -166,9 +184,21 @@ class Supernode {
 
   /**
    * Serialize the supernode to a binary output stream.
-   * @param out the stream to write to.
+   * @param binary_out   the stream to write to.
+   */
+  void write_binary(std::ostream &binary_out, bool sparse = false);
+
+  /*
+   * Serialize a portion of the supernode to a binary output stream.
+   * @param binary_out  the stream to write to.
+   * @param beg         the index of the first sketch to serialize
+   * @param num         the number of sketches to serialize
    */
-  void write_binary(std::ostream &binary_out);
+  void write_binary_range(std::ostream&binary_out, uint32_t beg, uint32_t num, bool sparse = false);
+
+  // void write_sparse_binary_range(std::ostream&binary_out, uint32_t beg, uint32_t end);
+
+  static constexpr size_t default_fail_factor = 4;
 };
 
 
diff --git a/include/test/sketch_constructors.h b/include/test/sketch_constructors.h
index 7eab19fa..3af819a6 100644
--- a/include/test/sketch_constructors.h
+++ b/include/test/sketch_constructors.h
@@ -14,10 +14,10 @@ SketchUniquePtr makeSketch(long seed) {
   };
 }
 
-SketchUniquePtr makeSketch(long seed, std::fstream &binary_in) {
+SketchUniquePtr makeSketch(long seed, std::fstream &binary_in, bool sparse=false) {
   void* loc = malloc(Sketch::sketchSizeof());
   return {
-    Sketch::makeSketch(loc, seed, binary_in),
+    Sketch::makeSketch(loc, seed, binary_in, sparse),
     [](Sketch* s){ free(s); }
   };
 }
diff --git a/include/types.h b/include/types.h
index b8ab11a4..e65f572d 100644
--- a/include/types.h
+++ b/include/types.h
@@ -18,6 +18,12 @@ enum UpdateType {
 struct Edge {
   node_id_t src = 0;
   node_id_t dst = 0;
+
+  bool operator< (const Edge&oth) const {
+    if (src == oth.src)
+      return dst < oth.dst;
+    return src < oth.src;
+  }
 };
 
 struct GraphUpdate {
diff --git a/src/l0_sampling/sketch.cpp b/src/l0_sampling/sketch.cpp
index 7280f1ad..2c2b3885 100644
--- a/src/l0_sampling/sketch.cpp
+++ b/src/l0_sampling/sketch.cpp
@@ -6,7 +6,7 @@
 vec_t Sketch::failure_factor = 100;
 vec_t Sketch::n;
 size_t Sketch::num_elems;
-size_t Sketch::num_buckets;
+size_t Sketch::num_columns;
 size_t Sketch::num_guesses;
 
 /*
@@ -17,8 +17,8 @@ Sketch* Sketch::makeSketch(void* loc, uint64_t seed) {
   return new (loc) Sketch(seed);
 }
 
-Sketch* Sketch::makeSketch(void* loc, uint64_t seed, std::istream &binary_in) {
-  return new (loc) Sketch(seed, binary_in);
+Sketch* Sketch::makeSketch(void* loc, uint64_t seed, std::istream &binary_in, bool sparse) {
+  return new (loc) Sketch(seed, binary_in, sparse);
 }
 
 Sketch* Sketch::makeSketch(void* loc, const Sketch& s) {
@@ -37,13 +37,31 @@ Sketch::Sketch(uint64_t seed): seed(seed) {
   }
 }
 
-Sketch::Sketch(uint64_t seed, std::istream &binary_in): seed(seed) {
+Sketch::Sketch(uint64_t seed, std::istream &binary_in, bool sparse): seed(seed) {
   // establish the bucket_a and bucket_c locations
   bucket_a = reinterpret_cast<vec_t*>(buckets);
   bucket_c = reinterpret_cast<vec_hash_t*>(buckets + num_elems * sizeof(vec_t));
 
-  binary_in.read((char*)bucket_a, num_elems * sizeof(vec_t));
-  binary_in.read((char*)bucket_c, num_elems * sizeof(vec_hash_t));
+  if (!sparse) {
+    binary_in.read((char*)bucket_a, num_elems * sizeof(vec_t));
+    binary_in.read((char*)bucket_c, num_elems * sizeof(vec_hash_t));
+  } else {
+    for (size_t i = 0; i < num_elems; ++i) {
+      bucket_a[i] = 0;
+      bucket_c[i] = 0;
+    }
+
+    uint16_t idx;
+    binary_in.read((char*)&idx, sizeof(idx));
+    while (idx < num_elems - 1) {
+      binary_in.read((char*)&bucket_a[idx], sizeof(bucket_a[idx]));
+      binary_in.read((char*)&bucket_c[idx], sizeof(bucket_c[idx]));
+      binary_in.read((char*)&idx, sizeof(idx));
+    }
+    // finally handle the level 0 bucket (num_elems - 1)
+    binary_in.read((char*)&bucket_a[idx], sizeof(bucket_a[idx]));
+    binary_in.read((char*)&bucket_c[idx], sizeof(bucket_c[idx]));
+  }
 }
 
 Sketch::Sketch(const Sketch& s) : seed(s.seed) {
@@ -55,14 +73,14 @@ Sketch::Sketch(const Sketch& s) : seed(s.seed) {
 }
 
 void Sketch::update(const vec_t update_idx) {
-  vec_hash_t checksum = Bucket_Boruvka::get_index_hash(update_idx, seed);
+  vec_hash_t checksum = Bucket_Boruvka::get_index_hash(update_idx, checksum_seed());
   
   // Update depth 0 bucket
   Bucket_Boruvka::update(bucket_a[num_elems - 1], bucket_c[num_elems - 1], update_idx, checksum);
 
   // Update higher depth buckets
-  for (unsigned i = 0; i < num_buckets; ++i) {
-    col_hash_t depth = Bucket_Boruvka::get_index_depth(update_idx, seed + i, num_guesses);
+  for (unsigned i = 0; i < num_columns; ++i) {
+    col_hash_t depth = Bucket_Boruvka::get_index_depth(update_idx, column_seed(i), num_guesses);
     size_t bucket_id = i * num_guesses + depth;
     likely_if(depth < num_guesses)
       Bucket_Boruvka::update(bucket_a[bucket_id], bucket_c[bucket_id], update_idx, checksum);
@@ -81,30 +99,61 @@ std::pair<vec_t, SampleSketchRet> Sketch::query() {
   }
   already_queried = true;
 
-  if (bucket_a[num_elems - 1] == 0 && bucket_c[num_elems - 1] == 0) {
+  if (bucket_a[num_elems - 1] == 0 && bucket_c[num_elems - 1] == 0)
     return {0, ZERO}; // the "first" bucket is deterministic so if all zero then no edges to return
-  }
-  if (Bucket_Boruvka::is_good(bucket_a[num_elems - 1], bucket_c[num_elems - 1], seed)) {
+
+  if (Bucket_Boruvka::is_good(bucket_a[num_elems - 1], bucket_c[num_elems - 1], checksum_seed()))
     return {bucket_a[num_elems - 1], GOOD};
-  }
-  for (unsigned i = 0; i < num_buckets; ++i) {
+
+  for (unsigned i = 0; i < num_columns; ++i) {
     for (unsigned j = 0; j < num_guesses; ++j) {
       unsigned bucket_id = i * num_guesses + j;
-      if (Bucket_Boruvka::is_good(bucket_a[bucket_id], bucket_c[bucket_id], seed)) {
+      if (Bucket_Boruvka::is_good(bucket_a[bucket_id], bucket_c[bucket_id], checksum_seed()))
         return {bucket_a[bucket_id], GOOD};
-      }
     }
   }
   return {0, FAIL};
 }
 
+std::pair<std::vector<vec_t>, SampleSketchRet> Sketch::exhaustive_query() {
+  std::vector<vec_t> ret;
+
+  unlikely_if (bucket_a[num_elems - 1] == 0 && bucket_c[num_elems - 1] == 0)
+    return {ret, ZERO}; // the "first" bucket is deterministic so if zero then no edges to return
+
+  unlikely_if (
+  Bucket_Boruvka::is_good(bucket_a[num_elems - 1], bucket_c[num_elems - 1], checksum_seed())) {
+    ret.push_back(bucket_a[num_elems - 1]);
+    return {ret, GOOD};
+  }
+  for (unsigned i = 0; i < num_columns; ++i) {
+    for (unsigned j = 0; j < num_guesses; ++j) {
+      unsigned bucket_id = i * num_guesses + j;
+      unlikely_if (
+      Bucket_Boruvka::is_good(bucket_a[bucket_id], bucket_c[bucket_id], checksum_seed())) {
+        ret.push_back(bucket_a[bucket_id]);
+        update(bucket_a[bucket_id]);
+      }
+    }
+  }
+  unlikely_if (already_queried)
+    throw MultipleQueryException();
+  already_queried = true;
+
+  unlikely_if (ret.size() == 0)
+    return {ret, FAIL};
+  return {ret, GOOD};
+}
+
 Sketch &operator+= (Sketch &sketch1, const Sketch &sketch2) {
   assert (sketch1.seed == sketch2.seed);
+  sketch1.already_queried = sketch1.already_queried || sketch2.already_queried;
+  if (sketch2.bucket_a[Sketch::num_elems-1] == 0 && sketch2.bucket_c[Sketch::num_elems-1] == 0)
+    return sketch1;
   for (unsigned i = 0; i < Sketch::num_elems; i++) {
     sketch1.bucket_a[i] ^= sketch2.bucket_a[i];
     sketch1.bucket_c[i] ^= sketch2.bucket_c[i];
   }
-  sketch1.already_queried = sketch1.already_queried || sketch2.already_queried;
   return sketch1;
 }
 
@@ -126,16 +175,16 @@ bool operator== (const Sketch &sketch1, const Sketch &sketch2) {
 std::ostream& operator<< (std::ostream &os, const Sketch &sketch) {
   vec_t a      = sketch.bucket_a[Sketch::num_elems - 1];
   vec_hash_t c = sketch.bucket_c[Sketch::num_elems - 1];
-  bool good    = Bucket_Boruvka::is_good(a, c, sketch.seed);
+  bool good    = Bucket_Boruvka::is_good(a, c, sketch.checksum_seed());
 
   os << " a:" << a << " c:" << c << (good ? " good" : " bad") << std::endl;
 
-  for (unsigned i = 0; i < Sketch::num_buckets; ++i) {
+  for (unsigned i = 0; i < Sketch::num_columns; ++i) {
     for (unsigned j = 0; j < Sketch::num_guesses; ++j) {
       unsigned bucket_id = i * Sketch::num_guesses + j;
       vec_t a      = sketch.bucket_a[bucket_id];
       vec_hash_t c = sketch.bucket_c[bucket_id];
-      bool good    = Bucket_Boruvka::is_good(a, c, sketch.seed);
+      bool good    = Bucket_Boruvka::is_good(a, c, sketch.checksum_seed());
 
       os << " a:" << a << " c:" << c << (good ? " good" : " bad") << std::endl;
     }
@@ -148,8 +197,27 @@ void Sketch::write_binary(std::ostream& binary_out) {
   const_cast<const Sketch*>(this)->write_binary(binary_out);
 }
 
-void Sketch::write_binary(std::ostream &binary_out) const {
+void Sketch::write_binary(std::ostream& binary_out) const {
   // Write out the bucket values to the stream.
   binary_out.write((char*)bucket_a, num_elems * sizeof(vec_t));
   binary_out.write((char*)bucket_c, num_elems * sizeof(vec_hash_t));
 }
+
+void Sketch::write_sparse_binary(std::ostream& binary_out) {
+  const_cast<const Sketch*>(this)->write_sparse_binary(binary_out);
+}
+
+void Sketch::write_sparse_binary(std::ostream& binary_out) const {
+  for (uint16_t i = 0; i < num_elems - 1; i++) {
+    if (bucket_a[i] == 0 && bucket_c[i] == 0)
+      continue;
+    binary_out.write((char*)&i, sizeof(i));
+    binary_out.write((char*)&bucket_a[i], sizeof(vec_t));
+    binary_out.write((char*)&bucket_c[i], sizeof(vec_hash_t));
+  }
+  // Always write down the deterministic bucket to mark the end of the Sketch
+  uint16_t index = num_elems - 1;
+  binary_out.write((char*)&index, sizeof(index));
+  binary_out.write((char*)&bucket_a[num_elems-1], sizeof(vec_t));
+  binary_out.write((char*)&bucket_c[num_elems-1], sizeof(vec_hash_t));
+}
diff --git a/src/supernode.cpp b/src/supernode.cpp
index d04784e7..c91d5bcc 100644
--- a/src/supernode.cpp
+++ b/src/supernode.cpp
@@ -3,34 +3,65 @@
 #include "../include/supernode.h"
 #include "../include/graph_worker.h"
 
+size_t Supernode::max_sketches;
 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()) {
+Supernode::Supernode(uint64_t n, uint64_t seed): sample_idx(0),
+               n(n), seed(seed), num_sketches(max_sketches), sketch_size(Sketch::sketchSizeof()) {
 
-  size_t sketch_width = guess_gen(Sketch::get_failure_factor());
+  size_t sketch_width = Sketch::column_gen(Sketch::get_failure_factor());
   // generate num_sketches sketches for each supernode (read: node)
-  for (int i = 0; i < num_sketches; ++i) {
+  for (size_t i = 0; i < num_sketches; ++i) {
     Sketch::makeSketch(get_sketch(i), seed);
     seed += sketch_width;
   }
 }
 
 Supernode::Supernode(uint64_t n, uint64_t seed, std::istream &binary_in) :
-  idx(0), num_sketches(log2(n)/(log2(3)-1)), n(n), seed(seed), sketch_size(Sketch::sketchSizeof()) {
+  sample_idx(0), n(n), seed(seed), sketch_size(Sketch::sketchSizeof()) {
 
-  size_t sketch_width = guess_gen(Sketch::get_failure_factor());
-  // read num_sketches sketches from file for each supernode (read: node)
-  for (int i = 0; i < num_sketches; ++i) {
-    Sketch::makeSketch(get_sketch(i), seed, binary_in);
+  size_t sketch_width = Sketch::column_gen(Sketch::get_failure_factor());
+
+  SerialType type;
+  binary_in.read((char*) &type, sizeof(SerialType));
+
+  uint32_t beg = 0;
+  uint32_t num = max_sketches;
+  bool sparse = false;
+  if (type == PARTIAL) {
+    binary_in.read((char*) &beg, sizeof(beg));
+    binary_in.read((char*) &num, sizeof(num));
+  }
+  else if (type == SPARSE) {
+    binary_in.read((char*) &beg, sizeof(beg));
+    binary_in.read((char*) &num, sizeof(num));
+    sparse = true;
+  }
+  // sample in range [beg, beg + num)
+  num_sketches = beg + num;
+  sample_idx = beg;
+
+  // create empty sketches, if any
+  for (size_t i = 0; i < beg; ++i) {
+    Sketch::makeSketch(get_sketch(i), seed);
+    seed += sketch_width;
+  }
+  // build sketches from serialized data
+  for (size_t i = beg; i < beg + num; ++i) {
+    Sketch::makeSketch(get_sketch(i), seed, binary_in, sparse);
+    seed += sketch_width;
+  }
+  // create empty sketches at end, if any
+  for (size_t i = beg + num; i < max_sketches; ++i) {
+    Sketch::makeSketch(get_sketch(i), seed);
     seed += sketch_width;
   }
 }
 
-Supernode::Supernode(const Supernode& s) : idx(s.idx), num_sketches(s.num_sketches), n(s.n),
-    seed(s.seed), sketch_size(s.sketch_size) {
-  for (int i = 0; i < num_sketches; ++i) {
+Supernode::Supernode(const Supernode& s) : sample_idx(s.sample_idx), n(s.n),
+    seed(s.seed), num_sketches(s.num_sketches), sketch_size(s.sketch_size) {
+  for (size_t i = 0; i < num_sketches; ++i) {
     Sketch::makeSketch(get_sketch(i), *s.get_sketch(i));
   }
 }
@@ -51,29 +82,42 @@ Supernode::~Supernode() {
 }
 
 std::pair<Edge, SampleSketchRet> Supernode::sample() {
-  if (idx == num_sketches) throw OutOfQueriesException();
+  if (sample_idx == num_sketches) throw OutOfQueriesException();
 
-  std::pair<vec_t, SampleSketchRet> query_ret = get_sketch(idx++)->query();
-  vec_t idx = query_ret.first;
+  std::pair<vec_t, SampleSketchRet> query_ret = get_sketch(sample_idx++)->query();
+  vec_t non_zero = query_ret.first;
   SampleSketchRet ret_code = query_ret.second;
-  return {inv_concat_pairing_fn(idx), ret_code};
+  return {inv_concat_pairing_fn(non_zero), ret_code};
+}
+
+std::pair<std::vector<Edge>, SampleSketchRet> Supernode::exhaustive_sample() {
+  if (sample_idx == num_sketches) throw OutOfQueriesException();
+
+  std::pair<std::vector<vec_t>, SampleSketchRet> query_ret = get_sketch(sample_idx++)->exhaustive_query();
+  std::vector<Edge> edges(query_ret.first.size());
+  for (size_t i = 0; i < edges.size(); i++)
+    edges[i] = inv_concat_pairing_fn(query_ret.first[i]);
+
+  SampleSketchRet ret_code = query_ret.second;
+  return {edges, ret_code};
 }
 
 void Supernode::merge(Supernode &other) {
-  idx = std::max(idx, other.idx);
-  for (int i=idx;i<num_sketches;++i) {
+  sample_idx = std::max(sample_idx, other.sample_idx);
+  num_sketches = std::min(num_sketches, other.num_sketches);
+  for (size_t i=sample_idx;i<num_sketches;++i) {
     (*get_sketch(i))+=(*other.get_sketch(i));
   }
 }
 
 void Supernode::update(vec_t upd) {
-  for (int i = 0; i < num_sketches; ++i)
+  for (size_t i = 0; i < num_sketches; ++i)
     get_sketch(i)->update(upd);
 }
 
 void Supernode::apply_delta_update(const Supernode* delta_node) {
   std::unique_lock<std::mutex> lk(node_mt);
-  for (int i = 0; i < num_sketches; ++i) {
+  for (size_t i = 0; i < num_sketches; ++i) {
     *get_sketch(i) += *delta_node->get_sketch(i);
   }
   lk.unlock();
@@ -104,13 +148,35 @@ void Supernode::delta_supernode(uint64_t n, uint64_t seed,
                const std::vector<vec_t> &updates, void *loc) {
   auto delta_node = makeSupernode(n, seed, loc);
 #pragma omp parallel for num_threads(GraphWorker::get_group_size()) default(shared)
-  for (int i = 0; i < delta_node->num_sketches; ++i) {
+  for (size_t i = 0; i < delta_node->num_sketches; ++i) {
     delta_node->get_sketch(i)->batch_update(updates);
   }
 }
 
-void Supernode::write_binary(std::ostream& binary_out) {
-  for (int i = 0; i < num_sketches; ++i) {
-    get_sketch(i)->write_binary(binary_out);
+void Supernode::write_binary(std::ostream& binary_out, bool sparse) {
+  SerialType type = FULL;
+  binary_out.write((char*) &type, sizeof(type));
+  for (size_t i = 0; i < num_sketches; ++i) {
+    if (sparse)
+      get_sketch(i)->write_sparse_binary(binary_out);
+    else
+      get_sketch(i)->write_binary(binary_out);
   }
 }
+
+void Supernode::write_binary_range(std::ostream &binary_out, uint32_t beg, uint32_t num,
+                                   bool sparse) {
+  if (beg >= num_sketches) beg = num_sketches - 1;
+  if (beg + num > num_sketches) num = num_sketches - beg;
+  if (num == 0) num = 1;
+
+  SerialType type = sparse ? SPARSE : PARTIAL;
+  binary_out.write((char*) &type, sizeof(type));
+  binary_out.write((char*) &beg, sizeof(beg));
+  binary_out.write((char*) &num, sizeof(num));
+  for (size_t i = beg; i < beg + num; ++i)
+    if (sparse)
+      get_sketch(i)->write_sparse_binary(binary_out);
+    else
+      get_sketch(i)->write_binary(binary_out);
+}
diff --git a/test/graph_test.cpp b/test/graph_test.cpp
index 9dc8954a..6ad7d860 100644
--- a/test/graph_test.cpp
+++ b/test/graph_test.cpp
@@ -97,7 +97,7 @@ TEST(GraphTest, TestSupernodeRestoreAfterCCFailure) {
 
     ASSERT_THROW(g.connected_components(true), OutOfQueriesException);
     for (node_id_t i = 0; i < num_nodes; ++i) {
-      for (int j = 0; j < copy_supernodes[i]->get_num_sktch(); ++j) {
+      for (int j = 0; j < Supernode::get_max_sketches(); ++j) {
         ASSERT_TRUE(*copy_supernodes[i]->get_sketch(j) ==
                   *g.supernodes[i]->get_sketch(j));
       }
diff --git a/test/sketch_test.cpp b/test/sketch_test.cpp
index 172b4110..13de1e0f 100644
--- a/test/sketch_test.cpp
+++ b/test/sketch_test.cpp
@@ -18,10 +18,10 @@ TEST(SketchTestSuite, TestExceptions) {
   Sketch::configure(10000, fail_factor);
   SketchUniquePtr sketch2 = makeSketch(0);
   std::vector<bool> vec_idx(sketch2->n, true);
-  unsigned long long num_buckets = bucket_gen(fail_factor);
-  unsigned long long num_guesses = guess_gen(sketch2->n);
+  unsigned long long num_columns = Sketch::column_gen(fail_factor);
+  unsigned long long num_guesses = Sketch::guess_gen(sketch2->n);
   size_t total_updates = 2;
-  for (unsigned long long i = 0; i < num_buckets;) {
+  for (unsigned long long i = 0; i < num_columns;) {
     size_t depth_1_updates = 0;
     size_t k = 0;
     size_t u = 0;
@@ -31,7 +31,7 @@ TEST(SketchTestSuite, TestExceptions) {
         continue;
       }
 
-      col_hash_t depth = Bucket_Boruvka::get_index_depth(k, sketch2->seed + i, num_guesses);
+      col_hash_t depth = Bucket_Boruvka::get_index_depth(k, sketch2->column_seed(i), num_guesses);
       if (depth >= 2) {
         vec_idx[k] = false; // force all updates to only touch depths <= 1
         i = 0;
@@ -61,7 +61,6 @@ TEST(SketchTestSuite, TestExceptions) {
 
 TEST(SketchTestSuite, GIVENonlyIndexZeroUpdatedTHENitWorks) {
   // GIVEN only the index 0 is updated
-  srand(time(nullptr));
   Sketch::configure(1000, fail_factor);
   SketchUniquePtr sketch = makeSketch(rand());
   sketch->update(0);
@@ -85,7 +84,6 @@ void test_sketch_sample(unsigned long num_sketches,
     double max_sample_fail_prob, double max_bucket_fail_prob) {
   Sketch::configure(vec_size, fail_factor);
 
-  srand(time(nullptr));
   std::chrono::duration<long double> runtime(0);
   unsigned long all_bucket_failures = 0;
   unsigned long sample_incorrect_failures = 0;
@@ -143,7 +141,6 @@ void test_sketch_sample(unsigned long num_sketches,
 }
 
 TEST(SketchTestSuite, TestSketchSample) {
-  srand (time(nullptr));
   test_sketch_sample(10000, 1e3, 100, 0.005, 0.02);
   test_sketch_sample(1000, 1e4, 1000, 0.001, 0.02);
   test_sketch_sample(1000, 1e5, 10000, 0.001, 0.02);
@@ -157,7 +154,6 @@ void test_sketch_addition(unsigned long num_sketches,
     double max_sample_fail_prob, double max_bucket_fail_prob) {
   Sketch::configure(vec_size, fail_factor);
 
-  srand (time(NULL));
   unsigned long all_bucket_failures = 0;
   unsigned long sample_incorrect_failures = 0;
   for (unsigned long i = 0; i < num_sketches; i++){
@@ -229,7 +225,7 @@ void test_sketch_large(unsigned long vec_size, unsigned long num_updates) {
   // therefore we need to ensure that in this test that we don't do more than that
   num_updates = std::min(num_updates, vec_size / 4);
 
-  srand(time(nullptr));
+  
   SketchUniquePtr sketch = makeSketch(rand());
   //Keep seed for replaying update stream later
   unsigned long seed = rand();
@@ -285,7 +281,6 @@ TEST(SketchTestSuite, TestSketchLarge) {
 TEST(SketchTestSuite, TestBatchUpdate) {
   unsigned long vec_size = 1000000000, num_updates = 1000000;
   Sketch::configure(vec_size, fail_factor);
-  srand(time(nullptr));
   std::vector<vec_t> updates(num_updates);
   for (unsigned long i = 0; i < num_updates; i++) {
     updates[i] = static_cast<vec_t>(rand() % vec_size);
@@ -306,7 +301,6 @@ TEST(SketchTestSuite, TestBatchUpdate) {
 }
 
 TEST(SketchTestSuite, TestSerialization) {
-  printf("starting test!\n");
   unsigned long vec_size = 1 << 20;
   unsigned long num_updates = 10000;
   Sketch::configure(vec_size, fail_factor);
@@ -316,7 +310,7 @@ TEST(SketchTestSuite, TestSerialization) {
   for (unsigned long j = 0; j < num_updates; j++){
     sketch->update(test_vec.get_update(j));
   }
-  auto file = std::fstream("./out_sketch.txt", std::ios::out | std::ios::binary);
+  auto file = std::fstream("./out_sketch.txt", std::ios::out | std::ios::binary | std::ios::trunc);
   sketch->write_binary(file);
   file.close();
 
@@ -325,3 +319,59 @@ TEST(SketchTestSuite, TestSerialization) {
 
   ASSERT_EQ(*sketch, *reheated);
 }
+
+TEST(SketchTestSuite, TestSparseSerialization) {
+  unsigned long vec_size = 1 << 20;
+  unsigned long num_updates = 10000;
+  Sketch::configure(vec_size, fail_factor);
+  Testing_Vector test_vec = Testing_Vector(vec_size, num_updates);
+  auto seed = rand();
+  SketchUniquePtr sketch = makeSketch(seed);
+  for (unsigned long j = 0; j < num_updates; j++){
+    sketch->update(test_vec.get_update(j));
+  }
+  auto file = std::fstream("./out_sketch.txt", std::ios::out | std::ios::binary | std::ios::trunc);
+  sketch->write_sparse_binary(file);
+  file.close();
+
+  auto in_file = std::fstream("./out_sketch.txt", std::ios::in | std::ios::binary);
+  SketchUniquePtr reheated = makeSketch(seed, in_file, true);
+
+  ASSERT_EQ(*sketch, *reheated);
+}
+
+TEST(SketchTestSuite, TestExhaustiveQuery) {
+  size_t runs = 10;
+  size_t vec_size = 10;
+  Sketch::configure(vec_size, vec_size*vec_size);
+  for (size_t i = 0; i < runs; i++) {
+    SketchUniquePtr sketch = makeSketch(rand());
+
+    sketch->update(1);
+    sketch->update(2);
+    sketch->update(3);
+    sketch->update(4);
+    sketch->update(5);
+    sketch->update(6);
+    sketch->update(7);
+    sketch->update(8);
+    sketch->update(9);
+    sketch->update(10);
+
+    std::pair<std::vector<vec_t>, SampleSketchRet> query_ret = sketch->exhaustive_query();
+    if (query_ret.second != GOOD) {
+      ASSERT_EQ(query_ret.first.size(), 0) << query_ret.second;
+    }
+
+    // assert everything returned is valid and <= 10 things
+    ASSERT_LE(query_ret.first.size(), 10);
+    for (vec_t non_zero : query_ret.first) {
+      ASSERT_GT(non_zero, 0);
+      ASSERT_LE(non_zero, 10);
+    }
+
+    // assert everything returned is unique
+    std::set<vec_t> unique_elms(query_ret.first.begin(), query_ret.first.end());
+    ASSERT_EQ(unique_elms.size(), query_ret.first.size());
+  }
+}
diff --git a/test/supernode_test.cpp b/test/supernode_test.cpp
index 475db3ab..3b533f91 100644
--- a/test/supernode_test.cpp
+++ b/test/supernode_test.cpp
@@ -17,7 +17,6 @@ class SupernodeTestSuite : public testing::Test {
   static std::vector<Edge> odd_graph_edges;
   static std::vector<bool> prime;
   static void SetUpTestSuite() {
-    srand(1000000007);
     graph_edges = std::vector<Edge>();
     odd_graph_edges = std::vector<Edge>();
     for (unsigned i = 2; i < num_nodes; ++i) {
@@ -57,12 +56,26 @@ TEST_F(SupernodeTestSuite, GIVENnoEdgeUpdatesIFsampledTHENnoEdgeIsReturned) {
 
 TEST_F(SupernodeTestSuite, IFsampledTooManyTimesTHENthrowOutOfQueries) {
   Supernode* s = Supernode::makeSupernode(num_nodes, seed);
-  for (int i = 0; i < s->get_num_sktch(); ++i) {
+  for (int i = 0; i < Supernode::get_max_sketches(); ++i) {
     s->sample();
   }
   ASSERT_THROW(s->sample(), OutOfQueriesException);
 }
 
+TEST_F(SupernodeTestSuite, SketchesHaveUniqueSeeds) {
+  Supernode* s = Supernode::makeSupernode(num_nodes, seed);
+  std::set<size_t> seeds;
+
+  for (int i = 0; i < Supernode::get_max_sketches(); ++i) {
+    Sketch* sketch = s->get_sketch(i);
+    for (size_t i = 0; i < sketch->get_columns(); i++) {
+      size_t seed = sketch->column_seed(i);
+      ASSERT_EQ(seeds.count(seed), 0);
+      seeds.insert(seed);
+    }
+  }
+}
+
 TEST_F(SupernodeTestSuite, TestSampleInsertGrinder) {
   std::vector<Supernode*> snodes;
   snodes.reserve(num_nodes);
@@ -80,7 +93,7 @@ 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)Supernode::get_max_sketches(); ++j) {
       std::pair<Edge, SampleSketchRet> sample_ret = snodes[i]->sample();
       sampled = sample_ret.first;
       SampleSketchRet ret_code = sample_ret.second;
@@ -125,7 +138,7 @@ 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)Supernode::get_max_sketches(); ++j) {
       std::pair<Edge, SampleSketchRet> sample_ret = snodes[i]->sample();
       sampled = sample_ret.first;
       SampleSketchRet ret_code = sample_ret.second;
@@ -146,7 +159,7 @@ TEST_F(SupernodeTestSuite, TestSampleDeleteGrinder) {
     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) free(snodes[i]);
 }
 
 void inline apply_delta_to_node(Supernode* node, const std::vector<vec_t>& updates) {
@@ -184,9 +197,9 @@ TEST_F(SupernodeTestSuite, TestBatchUpdate) {
                    .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) {
+  ASSERT_EQ(supernode->samples_remaining(), supernode_batch->samples_remaining());
+  ASSERT_EQ(supernode->sample_idx, supernode_batch->sample_idx);
+  for (int i = 0; i < supernode->samples_remaining(); ++i) {
     ASSERT_EQ(*supernode->get_sketch(i), *supernode_batch->get_sketch(i));
   }
 }
@@ -229,7 +242,7 @@ TEST_F(SupernodeTestSuite, TestConcurrency) {
     thd[i].join();
   }
 
-  for (int i = 0; i < supernode->get_num_sktch(); ++i) {
+  for (int i = 0; i < Supernode::get_max_sketches(); ++i) {
     ASSERT_EQ(*supernode->get_sketch(i), *piecemeal->get_sketch(i));
   }
 }
@@ -255,7 +268,78 @@ TEST_F(SupernodeTestSuite, TestSerialization) {
 
   Supernode* reheated = Supernode::makeSupernode(num_nodes, seed, in_file);
 
-  for (int i = 0; i < snodes[num_nodes / 2]->get_num_sktch(); ++i) {
+  for (int i = 0; i < Supernode::get_max_sketches(); ++i) {
     ASSERT_EQ(*snodes[num_nodes / 2]->get_sketch(i), *reheated->get_sketch(i));
   }
 }
+
+TEST_F(SupernodeTestSuite, ExhaustiveSample) {
+  size_t runs = 10;
+  size_t vertices = 11;
+  Supernode::configure(vertices);
+  for (size_t i = 0; i < runs; i++) {
+    Supernode* s_node = Supernode::makeSupernode(vertices, seed);
+
+    for (size_t s = 1; s < vertices; s++) {
+      for (size_t d = s+1; d < vertices; d++) {
+        s_node->update(concat_pairing_fn(s, d));
+      }
+    }
+
+    std::pair<std::vector<Edge>, SampleSketchRet> query_ret = s_node->exhaustive_sample();
+    if (query_ret.second != GOOD) {
+      ASSERT_EQ(query_ret.first.size(), 0);
+    }
+
+    // assert everything returned is valid
+    for (Edge e : query_ret.first) {
+      ASSERT_GT(e.src, 0);
+      ASSERT_LE(e.src, 10);
+      ASSERT_GT(e.dst, e.src);
+      ASSERT_LE(e.dst, 10);
+    }
+
+    // assert everything returned is unique
+    std::set<Edge> unique_elms(query_ret.first.begin(), query_ret.first.end());
+    ASSERT_EQ(unique_elms.size(), query_ret.first.size());
+
+    free(s_node);
+  }
+}
+
+TEST_F(SupernodeTestSuite, TestPartialSparseSerialization) {
+  Supernode* s_node = Supernode::makeSupernode(num_nodes, seed);
+  Supernode* empty_node = Supernode::makeSupernode(*s_node);
+  for (size_t i = 0; i < 10000; i++) {
+    node_id_t src = rand() % (num_nodes-1);
+    node_id_t dst = rand() % num_nodes;
+    if (src == dst)
+      ++dst;
+    s_node->update(concat_pairing_fn(src, dst));
+  }
+  int sketches = Supernode::get_max_sketches();
+  for (int beg = 0; beg < sketches / 4; beg++) {
+    for (int num = sketches - beg; num > 3 * sketches / 4; num--) {
+      auto file = std::fstream("./out_supernode.txt", std::ios::out | std::ios::binary);
+      s_node->write_binary_range(file, beg, num, true);
+      file.close();
+
+      auto in_file = std::fstream("./out_supernode.txt", std::ios::in | std::ios::binary);
+      Supernode* reheated = Supernode::makeSupernode(num_nodes, seed, in_file);
+      in_file.close();
+
+      for (int j = 0; j < beg; j++) {
+        ASSERT_EQ(*reheated->get_sketch(j), *empty_node->get_sketch(j));
+      }
+      for (int j = beg; j < beg + num; ++j) {
+        ASSERT_EQ(*reheated->get_sketch(j), *s_node->get_sketch(j));
+      }
+      for (int j = beg + num; j < sketches; ++j) {
+        ASSERT_EQ(*reheated->get_sketch(j), *empty_node->get_sketch(j));
+      }
+      free(reheated);
+    }
+  }
+  free(s_node);
+  free(empty_node);
+}
diff --git a/test/test_runner.cpp b/test/test_runner.cpp
index b936444a..bf8b9316 100644
--- a/test/test_runner.cpp
+++ b/test/test_runner.cpp
@@ -1,6 +1,7 @@
 #include <gtest/gtest.h>
 
 int main(int argc, char** argv) {
+  srand(time(nullptr));
   testing::InitGoogleTest(&argc, argv);
   return RUN_ALL_TESTS();
 }
diff --git a/tools/benchmark/graphcc_bench.cpp b/tools/benchmark/graphcc_bench.cpp
index 44ab9811..d01f9711 100644
--- a/tools/benchmark/graphcc_bench.cpp
+++ b/tools/benchmark/graphcc_bench.cpp
@@ -8,6 +8,7 @@
 #include <random>
 #include <thread>
 #include <vector>
+#include <sstream>
 
 #include "binary_graph_stream.h"
 #include "bucket.h"
@@ -57,8 +58,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();
 
@@ -92,41 +93,41 @@ static void BM_MTFileIngest(benchmark::State& state) {
     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
+#endif  // FILE_INGEST_F
 
-static void BM_builtin_ffsl(benchmark::State& state) {
+static void BM_builtin_ffsll(benchmark::State& state) {
   size_t i = 0;
   size_t j = -1;
   for (auto _ : state) {
-    benchmark::DoNotOptimize(__builtin_ffsl(i++));
-    benchmark::DoNotOptimize(__builtin_ffsl(j--));
+    benchmark::DoNotOptimize(__builtin_ffsll(i++));
+    benchmark::DoNotOptimize(__builtin_ffsll(j--));
   }
 }
-BENCHMARK(BM_builtin_ffsl);
+BENCHMARK(BM_builtin_ffsll);
 
-static void BM_builtin_ctzl(benchmark::State& state) {
+static void BM_builtin_ctzll(benchmark::State& state) {
   size_t i = 0;
   size_t j = -1;
   for (auto _ : state) {
-    benchmark::DoNotOptimize(__builtin_ctzl(i++));
-    benchmark::DoNotOptimize(__builtin_ctzl(j--));
+    benchmark::DoNotOptimize(__builtin_ctzll(i++));
+    benchmark::DoNotOptimize(__builtin_ctzll(j--));
   }
 }
-BENCHMARK(BM_builtin_ctzl);
+BENCHMARK(BM_builtin_ctzll);
 
-static void BM_builtin_clzl(benchmark::State& state) {
+static void BM_builtin_clzll(benchmark::State& state) {
   size_t i = 0;
   size_t j = -1;
   for (auto _ : state) {
-    benchmark::DoNotOptimize(__builtin_clzl(i++));
-    benchmark::DoNotOptimize(__builtin_clzl(j--));
+    benchmark::DoNotOptimize(__builtin_clzll(i++));
+    benchmark::DoNotOptimize(__builtin_clzll(j--));
   }
 }
-BENCHMARK(BM_builtin_clzl);
+BENCHMARK(BM_builtin_clzll);
 
 // Test the speed of hashing using a method that loops over seeds and a method that
 // batches by seed
@@ -192,7 +193,7 @@ static void BM_Sketch_Update(benchmark::State& state) {
   size_t vec_size = state.range(0);
   vec_t input = vec_size / 3;
   // initialize sketches
-  Sketch::configure(vec_size, 100);
+  Sketch::configure(vec_size, Supernode::default_fail_factor);
   SketchUniquePtr skt = makeSketch(seed);
 
   // Test the speed of updating the sketches
@@ -201,8 +202,8 @@ static void BM_Sketch_Update(benchmark::State& state) {
     skt->update(input);
   }
   state.counters["Updates"] = benchmark::Counter(state.iterations(), benchmark::Counter::kIsRate);
-  state.counters["Hashes"] = benchmark::Counter(
-      state.iterations() * (bucket_gen(100) + 1), benchmark::Counter::kIsRate);
+  state.counters["Hashes"] =
+      benchmark::Counter(state.iterations() * (Sketch::get_columns() + 1), benchmark::Counter::kIsRate);
 }
 BENCHMARK(BM_Sketch_Update)->RangeMultiplier(4)->Ranges({{KB << 4, MB << 4}});
 
@@ -234,8 +235,8 @@ 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);
 
@@ -260,9 +261,47 @@ static void BM_Supernode_Merge(benchmark::State& state) {
 }
 BENCHMARK(BM_Supernode_Merge)->RangeMultiplier(10)->Range(1e3, 1e6);
 
+static void BM_Supernode_Serialize(benchmark::State& state) {
+  size_t n = state.range(0);
+  size_t upds = n / 100;
+  Supernode::configure(n);
+  Supernode* s1 = Supernode::makeSupernode(n, seed);
+
+  for (size_t i = 0; i < upds; i++) {
+    s1->update(static_cast<vec_t>(concat_pairing_fn(rand() % n, rand() % n)));
+  }
+
+  for (auto _ : state) {
+    std::stringstream stream;
+    s1->write_binary(stream);
+  }
+
+  free(s1);
+}
+BENCHMARK(BM_Supernode_Serialize)->RangeMultiplier(10)->Range(1e3, 1e6);
+
+static void BM_Supernode_Sparse_Serialize(benchmark::State& state) {
+  size_t n = state.range(0);
+  size_t upds = n / 100;
+  Supernode::configure(n);
+  Supernode* s1 = Supernode::makeSupernode(n, seed);
+
+  for (size_t i = 0; i < upds; i++) {
+    s1->update(static_cast<vec_t>(concat_pairing_fn(rand() % n, rand() % n)));
+  }
+
+  for (auto _ : state) {
+    std::stringstream stream;
+    s1->write_binary(stream, true);
+  }
+
+  free(s1);
+}
+BENCHMARK(BM_Supernode_Sparse_Serialize)->RangeMultiplier(10)->Range(1e3, 1e6);
+
 // 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) {
+static void BM_DSU_Adversarial(benchmark::State& state) {
   constexpr size_t size_of_dsu = 16 * MB;
 
   auto rng = std::default_random_engine{};
@@ -287,14 +326,15 @@ static void BM_DSU_Adversarial(benchmark::State &state) {
       dsu.merge(upd.first, upd.second);
     }
   }
-  state.counters["Merge_Latency"] = benchmark::Counter(state.iterations() * updates.size(),
+  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) {
+static void BM_DSU_Root(benchmark::State& state) {
   constexpr size_t size_of_dsu = 16 * MB;
 
   auto rng = std::default_random_engine{};
@@ -319,7 +359,8 @@ static void BM_DSU_Root(benchmark::State &state) {
       dsu.merge(upd.first, upd.second);
     }
   }
-  state.counters["Merge_Latency"] = benchmark::Counter(state.iterations() * updates.size(),
+  state.counters["Merge_Latency"] =
+      benchmark::Counter(state.iterations() * updates.size(),
                          benchmark::Counter::kIsRate | benchmark::Counter::kInvert);
 }
 BENCHMARK(BM_DSU_Root);
diff --git a/tools/process_stream.cpp b/tools/process_stream.cpp
index 36648708..0f252d30 100644
--- a/tools/process_stream.cpp
+++ b/tools/process_stream.cpp
@@ -1,19 +1,69 @@
 #include <graph.h>
 #include <binary_graph_stream.h>
+#include <thread>
+
+static bool shutdown = false;
+
+/*
+ * Function which is run in a seperate thread and will query
+ * the graph for the number of updates it has processed
+ * @param total       the total number of edge updates
+ * @param g           the graph object to query
+ * @param start_time  the time that we started stream ingestion
+ */
+void track_insertions(uint64_t total, Graph *g, std::chrono::steady_clock::time_point start_time) {
+  total = total * 2;                // we insert 2 edge updates per edge
+
+  printf("Insertions\n");
+  printf("Progress:                    | 0%%\r"); fflush(stdout);
+  std::chrono::steady_clock::time_point start = start_time;
+  std::chrono::steady_clock::time_point prev  = start_time;
+  uint64_t prev_updates = 0;
+
+  while(true) {
+    sleep(1);
+    uint64_t updates = g->num_updates;
+    std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
+    std::chrono::duration<double> total_diff = now - start;
+    std::chrono::duration<double> cur_diff   = now - prev;
+
+    // calculate the insertion rate
+    uint64_t upd_delta = updates - prev_updates;
+    // divide insertions per second by 2 because each edge is split into two updates
+    // we care about edges per second not about stream updates
+    size_t ins_per_sec = (((double)(upd_delta)) / cur_diff.count()) / 2;
+
+    if (updates >= total || shutdown)
+      break;
+
+    // display the progress
+    int progress = updates / (total * .05);
+    printf("Progress:%s%s", std::string(progress, '=').c_str(), std::string(20 - progress, ' ').c_str());
+    printf("| %i%% -- %lu per second\r", progress * 5, ins_per_sec); fflush(stdout);
+  }
+
+
+  printf("Progress:====================| Done                \n");
+  return;
+}
 
 int main(int argc, char **argv) {
-  if (argc != 3) {
+  if (argc != 4) {
     std::cout << "ERROR: Incorrect number of arguments!" << std::endl;
-    std::cout << "Arguments: stream_file, graph_workers" << std::endl;
+    std::cout << "Arguments: stream_file, graph_workers, reader_threads" << std::endl;
+    exit(EXIT_FAILURE);
   }
 
+  shutdown = false;
   std::string stream_file = argv[1];
   int num_threads = std::atoi(argv[2]);
   if (num_threads < 1) {
     std::cout << "ERROR: Invalid number of graph workers! Must be > 0." << std::endl;
+  exit(EXIT_FAILURE);
   }
+  int reader_threads = std::atoi(argv[3]);
 
-  BinaryGraphStream stream(stream_file, 1024*32);
+  BinaryGraphStream_MT stream(stream_file, 1024*32);
   node_id_t num_nodes = stream.nodes();
   size_t num_updates  = stream.edges();
   std::cout << "Processing stream: " << stream_file << std::endl;
@@ -23,19 +73,48 @@ int main(int argc, char **argv) {
 
   auto config = GraphConfiguration().gutter_sys(STANDALONE).num_groups(num_threads);
   config.gutter_conf().gutter_factor(-4);
-  Graph g{num_nodes, config};
+  Graph g{num_nodes, config, reader_threads};
 
   auto ins_start = std::chrono::steady_clock::now();
-  for (size_t e = 0; e < num_updates; e++)
-    g.update(stream.get_edge());
+  std::thread querier(track_insertions, num_updates, &g, ins_start);
+
+  // Do the edge updates
+  std::vector<std::thread> threads;
+  threads.reserve(reader_threads);
+  auto task = [&](const int thr_id) {
+    MT_StreamReader reader(stream);
+    GraphUpdate upd;
+    while(true) {
+      upd = reader.get_edge();
+      if (upd.type == BREAKPOINT) break;
+      g.update(upd, thr_id);
+    }
+  };
+
+  // start inserters
+  for (int t = 0; t < reader_threads; t++) {
+    threads.emplace_back(task, t);
+  }
+  // wait for inserters to be done
+  for (int t = 0; t < reader_threads; t++) {
+    threads[t].join();
+  }
 
   auto cc_start = std::chrono::steady_clock::now();
   auto CC_num = g.connected_components().size();
   std::chrono::duration<double> insert_time = g.flush_end - ins_start;
   std::chrono::duration<double> cc_time = std::chrono::steady_clock::now() - cc_start;
+  std::chrono::duration<double> flush_time = g.flush_end - g.flush_start;
+  std::chrono::duration<double> cc_alg_time = g.cc_alg_end - g.cc_alg_start;
+
+  shutdown = true;
+  querier.join();
+
   double num_seconds = insert_time.count();
-  std::cout << "Total insertion time was: " << num_seconds << std::endl;
-  std::cout << "Insertion rate was:       " << stream.edges() / num_seconds << std::endl;
-  std::cout << "CC query latency:         " << cc_time.count() << std::endl;
-  std::cout << "Connected Components:     " << CC_num << std::endl;
+  std::cout << "Total insertion time(sec):    " << num_seconds << std::endl;
+  std::cout << "Updates per second:           " << stream.edges() / num_seconds << std::endl;
+  std::cout << "Total CC query latency:       " << cc_time.count() << std::endl;
+  std::cout << "  Flush Gutters(sec):           " << flush_time.count() << std::endl;
+  std::cout << "  Boruvka's Algorithm(sec):     " << cc_alg_time.count() << std::endl;
+  std::cout << "Connected Components:         " << CC_num << std::endl;
 }
