From: Matthew Fernandez Date: Thu, 16 Dec 2021 01:46:16 +0000 (-0800) Subject: introduce an abstraction for compacted boolean arrays X-Git-Tag: 3.0.0~115^2~2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=3ced90bd756bb8e56920620f2686e0ba6b482bbb;p=graphviz introduce an abstraction for compacted boolean arrays --- diff --git a/lib/cgraph/CMakeLists.txt b/lib/cgraph/CMakeLists.txt index 580d9a417..81edb03d7 100644 --- a/lib/cgraph/CMakeLists.txt +++ b/lib/cgraph/CMakeLists.txt @@ -10,6 +10,7 @@ endif() add_library(cgraph SHARED # Header files agxbuf.h + bitarray.h cghdr.h cgraph.h itos.h diff --git a/lib/cgraph/Makefile.am b/lib/cgraph/Makefile.am index 39e1ff0ca..795c975ce 100644 --- a/lib/cgraph/Makefile.am +++ b/lib/cgraph/Makefile.am @@ -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 index 000000000..aa584538f --- /dev/null +++ b/lib/cgraph/bitarray.h @@ -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` does. +/// +/// This is deliberately implemented header-only so even Graphviz components +/// that do not link against cgraph can use it. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/// 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)); +} diff --git a/lib/cgraph/cgraph.vcxproj b/lib/cgraph/cgraph.vcxproj index cdae48851..19daa1257 100644 --- a/lib/cgraph/cgraph.vcxproj +++ b/lib/cgraph/cgraph.vcxproj @@ -98,6 +98,7 @@ win_flex -oscan.c scan.l + diff --git a/lib/cgraph/cgraph.vcxproj.filters b/lib/cgraph/cgraph.vcxproj.filters index 936392590..a20bf8fa7 100644 --- a/lib/cgraph/cgraph.vcxproj.filters +++ b/lib/cgraph/cgraph.vcxproj.filters @@ -18,6 +18,9 @@ Header Files + + Header Files + Header Files