]> granicus.if.org Git - graphviz/commitdiff
implement Small String Optimization in the bitarray API
authorMatthew Fernandez <matthew.fernandez@gmail.com>
Wed, 12 Jan 2022 04:59:58 +0000 (20:59 -0800)
committerMatthew Fernandez <matthew.fernandez@gmail.com>
Wed, 12 Jan 2022 15:51:22 +0000 (07:51 -0800)
Following the prior commit, this further reduces memory usage. On e.g. x86-64,
arrays of ≤ 64 bits can be managed fully on the stack.

lib/cgraph/bitarray.h
lib/neatogen/dijkstra.c
lib/neatogen/neatoinit.c
lib/neatogen/sgd.c

index 109d8e172ddd7c1a1925ca6aab4b2a40918c15bb..bf956427501cc01681dbadec4ef6ffabd24f6dc8 100644 (file)
 /// heap pressure and increases locality of reference, at the cost of a few
 /// (inexpensive) shifts and masks.
 ///
-/// This is the same optimization C++’s `std::vector<bool>` does.
+/// As a bonus, short arrays are stored directly inline, avoiding heap
+/// allocation altogether. This is essentially Small String Optimization applied
+/// to a boolean array.
+///
+/// The above design is essentially what C++’s `std::vector<bool>` does.
 ///
 /// This is deliberately implemented header-only so even Graphviz components
 /// that do not link against cgraph can use it.
 /// zero initializing one of these (`bitarray_t b = {0}`) or `memset`ing one of
 /// these to zero gives you a valid zero-length bit array.
 typedef struct {
-  uint8_t *base; ///< start of the underlying allocated buffer
+  union {
+    uint8_t block[sizeof(uint8_t*)]; ///< inline storage for small arrays
+    uint8_t *base; ///< start of the underlying allocated buffer
+  };
   size_t size_bits; ///< extent in bits
 } bitarray_t;
 
@@ -42,12 +49,20 @@ static inline int bitarray_new(bitarray_t *self, size_t size_bits) {
   assert(self != NULL);
   assert(self->size_bits == 0);
 
-  size_t capacity = size_bits / 8 + (size_bits % 8 == 0 ? 0 : 1);
-  uint8_t *base = calloc(capacity, sizeof(self->base[0]));
-  if (UNLIKELY(base == NULL))
-    return ENOMEM;
+  // if the array is small enough, we can use inline storage
+  if (size_bits <= sizeof(self->block) * 8) {
+    memset(self->block, 0, sizeof(self->block));
+
+  // otherwise we need to heap-allocate
+  } else {
+    size_t capacity = size_bits / 8 + (size_bits % 8 == 0 ? 0 : 1);
+    uint8_t *base = calloc(capacity, sizeof(self->base[0]));
+    if (UNLIKELY(base == NULL))
+      return ENOMEM;
+
+    self->base = base;
+  }
 
-  self->base = base;
   self->size_bits = size_bits;
 
   return 0;
@@ -56,7 +71,8 @@ static inline int bitarray_new(bitarray_t *self, size_t size_bits) {
 /// `bitarray_new` for callers who cannot handle failure
 static inline bitarray_t bitarray_new_or_exit(size_t size_bits) {
 
-  bitarray_t ba = {0};
+  bitarray_t ba;
+  memset(&ba, 0, sizeof(ba));
 
   int error = bitarray_new(&ba, size_bits);
   if (UNLIKELY(error != 0)) {
@@ -71,17 +87,33 @@ static inline bitarray_t bitarray_new_or_exit(size_t size_bits) {
 static inline bool bitarray_get(bitarray_t self, size_t index) {
   assert(index < self.size_bits && "out of bounds access");
 
-  return (self.base[index / 8] >> (index % 8)) & 1;
+  // determine if this array is stored inline or not
+  const uint8_t *base;
+  if (self.size_bits <= sizeof(self.block) * 8) {
+    base = self.block;
+  } else {
+    base = self.base;
+  }
+
+  return (base[index / 8] >> (index % 8)) & 1;
 }
 
 /// set or clear the value of the given element
-static inline void bitarray_set(bitarray_t self, size_t index, bool value) {
-  assert(index < self.size_bits && "out of bounds access");
+static inline void bitarray_set(bitarray_t *self, size_t index, bool value) {
+  assert(index < self->size_bits && "out of bounds access");
+
+  // determine if this array is stored inline or not
+  uint8_t *base;
+  if (self->size_bits <= sizeof(self->block) * 8) {
+    base = self->block;
+  } else {
+    base = self->base;
+  }
 
   if (value) {
-    self.base[index / 8] |= (uint8_t)(UINT8_C(1) << (index % 8));
+    base[index / 8] |= (uint8_t)(UINT8_C(1) << (index % 8));
   } else {
-    self.base[index / 8] ^= (uint8_t)(UINT8_C(1) << (index % 8));
+    base[index / 8] ^= (uint8_t)(UINT8_C(1) << (index % 8));
   }
 }
 
@@ -89,6 +121,9 @@ static inline void bitarray_set(bitarray_t self, size_t index, bool value) {
 static inline void bitarray_reset(bitarray_t *self) {
   assert(self != NULL);
 
-  free(self->base);
+  // is this array stored out of line?
+  if (self->size_bits > sizeof(self->block) * 8)
+    free(self->base);
+
   memset(self, 0, sizeof(*self));
 }
index 05192c97e66423f8d1e8f89916e1ca934791d9a1..eccaaa775abcc8a67d4848fbd542b26fcaf653ed 100644 (file)
@@ -201,7 +201,7 @@ dijkstra_bounded(int vertex, vtx_data * graph, int n, DistType * dist,
        bfs_bounded(vertex, graph, n, dist, &Q, bound, visited_nodes);
     bitarray_t node_in_neighborhood = bitarray_new_or_exit(n);
     for (i = 0; i < num_visited_nodes; i++) {
-       bitarray_set(node_in_neighborhood, visited_nodes[i], true);
+       bitarray_set(&node_in_neighborhood, visited_nodes[i], true);
     }
 
     int *index = gcalloc(n, sizeof(int));
index e56a43b8f6ad89c53d4d39b23e3ebd244120a9eb..b6ada08a5af64c7b48767d365fcc3d411cb35b4f 100644 (file)
@@ -218,7 +218,7 @@ static cluster_data* cluster_map(graph_t *mastergraph, graph_t *g)
                     ind++;
                 }
                 *c++=ind;
-                bitarray_set(assigned, ind, true);
+                bitarray_set(&assigned, ind, true);
                 cdata->ntoplevel--;
             }
         }
index b109923519322b00c65ffe0e7321f2d025a41cd2..8ee6d26a6128726516483f639b8363776df24e37 100644 (file)
@@ -64,7 +64,7 @@ static graph_sgd * extract_adjacency(graph_t *G, int model) {
     for (np = agfstnode(G); np; np = agnxtnode(G,np)) {
         assert(n_edges <= INT_MAX);
         graph->sources[n_nodes] = n_edges;
-        bitarray_set(graph->pinneds, n_nodes, isFixed(np));
+        bitarray_set(&graph->pinneds, n_nodes, isFixed(np));
         for (ep = agfstedge(G, np); ep; ep = agnxtedge(G, ep, np)) {
             if (agtail(ep) == aghead(ep)) { // ignore self-loops and double edges
                 continue;
@@ -94,7 +94,7 @@ static graph_sgd * extract_adjacency(graph_t *G, int model) {
             for (size_t x = graph->sources[i]; x < graph->sources[i + 1]; x++) {
                 size_t j = graph->targets[x];
                 if (!bitarray_get(neighbours_i, j)) { // ignore multiedges
-                    bitarray_set(neighbours_i, j, true); // set up sort of hashset
+                    bitarray_set(&neighbours_i, j, true); // set up sort of hashset
                     deg_i++;
                 }
             }
@@ -106,7 +106,7 @@ static graph_sgd * extract_adjacency(graph_t *G, int model) {
                      y++) {
                     size_t k = graph->targets[y];
                     if (!bitarray_get(neighbours_j, k)) { // ignore multiedges
-                        bitarray_set(neighbours_j, k, true); // set up sort of hashset
+                        bitarray_set(&neighbours_j, k, true); // set up sort of hashset
                         deg_j++;
                         if (bitarray_get(neighbours_i, k)) {
                             intersect++;
@@ -118,12 +118,12 @@ static graph_sgd * extract_adjacency(graph_t *G, int model) {
                 for (size_t y = graph->sources[j]; y < graph->sources[j + 1];
                      y++) {
                     size_t k = graph->targets[y];
-                    bitarray_set(neighbours_j, k, false); // reset sort of hashset
+                    bitarray_set(&neighbours_j, k, false); // reset sort of hashset
                 }
             }
             for (size_t x = graph->sources[i]; x < graph->sources[i + 1]; x++) {
                 size_t j = graph->targets[x];
-                bitarray_set(neighbours_i, j, false); // reset sort of hashset
+                bitarray_set(&neighbours_i, j, false); // reset sort of hashset
             }
         }
         bitarray_reset(&neighbours_i);