]> granicus.if.org Git - graphviz/commitdiff
introduce an abstraction for compacted boolean arrays
authorMatthew Fernandez <matthew.fernandez@gmail.com>
Thu, 16 Dec 2021 01:46:16 +0000 (17:46 -0800)
committerMatthew Fernandez <matthew.fernandez@gmail.com>
Wed, 22 Dec 2021 01:05:23 +0000 (17:05 -0800)
lib/cgraph/CMakeLists.txt
lib/cgraph/Makefile.am
lib/cgraph/bitarray.h [new file with mode: 0644]
lib/cgraph/cgraph.vcxproj
lib/cgraph/cgraph.vcxproj.filters

index 580d9a4179cde7ddf389cd3a7d9be64a282e4c9a..81edb03d78ed47bfe6e6d8cf92b0ca7faaef6a42 100644 (file)
@@ -10,6 +10,7 @@ endif()
 add_library(cgraph SHARED
     # Header files
     agxbuf.h
+    bitarray.h
     cghdr.h
     cgraph.h
     itos.h
index 39e1ff0cac6b0d86c6d67ccd7a5132829fc4566a..795c975cee80c4680fca9d1b28be596de6b3c65a 100644 (file)
@@ -12,8 +12,8 @@ AM_CFLAGS = -DEXPORT_CGRAPH -DEXPORT_AGXBUF -DEXPORT_CGHDR
 endif
 
 pkginclude_HEADERS = cgraph.h
-noinst_HEADERS = agxbuf.h cghdr.h itos.h likely.h prisize_t.h sprint.h \
-  strcasecmp.h unreachable.h
+noinst_HEADERS = agxbuf.h bitarray.h cghdr.h itos.h likely.h prisize_t.h \
+       sprint.h strcasecmp.h unreachable.h
 noinst_LTLIBRARIES = libcgraph_C.la
 lib_LTLIBRARIES = libcgraph.la
 pkgconfig_DATA = libcgraph.pc
diff --git a/lib/cgraph/bitarray.h b/lib/cgraph/bitarray.h
new file mode 100644 (file)
index 0000000..aa58453
--- /dev/null
@@ -0,0 +1,109 @@
+/// \file
+/// \brief API for compacted arrays of booleans
+///
+/// The straightforward way to construct a dynamic array of booleans is to
+/// `calloc` an array of `bool` values. However, this wastes a lot of memory.
+/// Typically 8 bits per byte, which really adds up for large arrays.
+///
+/// The following implements an alternative that stores 8 array elements per
+/// byte. Using this over the `bool` implementation described above decreases
+/// 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.
+///
+/// This is deliberately implemented header-only so even Graphviz components
+/// that do not link against cgraph can use it.
+
+#pragma once
+
+#include <assert.h>
+#include <cgraph/likely.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/// a compressed array of boolean values
+///
+/// Note that this complies with the zero-is-initialization idiom. That is, C99
+/// 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
+  size_t size_bits; ///< used extent in bits
+  size_t capacity; ///< allocated extent in bytes
+} bitarray_t;
+
+/// increase or decrease the element length of this array
+static inline int bitarray_resize(bitarray_t *self, size_t size_bits) {
+  assert(self != NULL);
+
+  // if this is less than the current size, just forget the excess elements
+  if (size_bits <= self->size_bits) {
+    self->size_bits = size_bits;
+    return 0;
+  }
+
+  // do we need to expand the backing buffer?
+  if (self->capacity * 8 < self->size_bits) {
+
+    size_t capacity = self->capacity == 0 ? 128 : self->capacity * 2;
+    if (capacity * 8 < self->size_bits)
+      capacity = self->size_bits / 8 + (self->size_bits % 8 == 0 ? 0 : 1);
+
+    uint8_t *base = realloc(self->base, capacity);
+    if (UNLIKELY(base == NULL))
+      return ENOMEM;
+
+    // clear the newly allocated bytes
+    memset(&base[self->capacity], 0, capacity - self->capacity);
+
+    self->base = base;
+    self->capacity = capacity;
+  }
+
+  self->size_bits = size_bits;
+
+  return 0;
+}
+
+/// `bitarray_resize` for callers who cannot handle failure
+static inline void bitarray_resize_or_exit(bitarray_t *self, size_t size_bits) {
+  assert(self != NULL);
+
+  int error = bitarray_resize(self, size_bits);
+  if (UNLIKELY(error != 0)) {
+    fprintf(stderr, "out of memory\n");
+    exit(EXIT_FAILURE);
+  }
+}
+
+/// get the value of the given element
+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;
+}
+
+/// 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");
+
+  if (value) {
+    self.base[index / 8] |= (uint8_t)(UINT8_C(1) << (index % 8));
+  } else {
+    self.base[index / 8] ^= (uint8_t)(UINT8_C(1) << (index % 8));
+  }
+}
+
+/// free underlying resources and leave a bit array empty
+static inline void bitarray_reset(bitarray_t *self) {
+  assert(self != NULL);
+
+  free(self->base);
+  memset(self, 0, sizeof(*self));
+}
index cdae4885122c21f96e42ce8f4a7e9f1ddbfb65e0..19daa1257ec9549ce5a714b598f079a7adb0f38b 100644 (file)
@@ -98,6 +98,7 @@ win_flex -oscan.c scan.l</Command>
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClInclude Include="agxbuf.h" />
+    <ClInclude Include="bitarray.h" />
     <ClInclude Include="cghdr.h" />
     <ClInclude Include="cgraph.h" />
     <ClInclude Include="itos.h" />
index 936392590e9cefa3c8eaaa34d12e6b7fccc725ce..a20bf8fa77c09c6d50884240926b0e491c0dcd37 100644 (file)
@@ -18,6 +18,9 @@
     <ClInclude Include="agxbuf.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="bitarray.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="cghdr.h">
       <Filter>Header Files</Filter>
     </ClInclude>