/// 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;
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;
/// `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)) {
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));
}
}
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));
}
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;
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++;
}
}
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++;
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);