bitarray.h 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. /// @file
  2. /// @ingroup cgraph_utils
  3. /// @brief API for compacted arrays of booleans
  4. ///
  5. /// The straightforward way to construct a dynamic array of booleans is to
  6. /// `calloc` an array of `bool` values. However, this wastes a lot of memory.
  7. /// Typically 8 bits per byte, which really adds up for large arrays.
  8. ///
  9. /// The following implements an alternative that stores 8 array elements per
  10. /// byte. Using this over the `bool` implementation described above decreases
  11. /// heap pressure and increases locality of reference, at the cost of a few
  12. /// (inexpensive) shifts and masks.
  13. ///
  14. /// As a bonus, short arrays are stored directly inline, avoiding heap
  15. /// allocation altogether. This is essentially Small String Optimization applied
  16. /// to a boolean array.
  17. ///
  18. /// The above design is essentially what C++’s `std::vector<bool>` does.
  19. ///
  20. /// This is deliberately implemented header-only so even Graphviz components
  21. /// that do not link against cgraph can use it.
  22. #pragma once
  23. #include <assert.h>
  24. #include <inttypes.h>
  25. #include <stdbool.h>
  26. #include <stdint.h>
  27. #include <stdlib.h>
  28. #include <util/alloc.h>
  29. /// a compressed array of boolean values
  30. ///
  31. /// Note that this complies with the zero-is-initialization idiom. That is, C99
  32. /// zero initializing one of these (`bitarray_t b = {0}`) or `memset`ing one of
  33. /// these to zero gives you a valid zero-length bit array.
  34. typedef struct {
  35. union {
  36. uint8_t block[sizeof(uint8_t *)]; ///< inline storage for small arrays
  37. uint8_t *base; ///< start of the underlying allocated buffer
  38. } u;
  39. size_t size_bits; ///< extent in bits
  40. } bitarray_t;
  41. /// create an array of the given element length
  42. static inline bitarray_t bitarray_new(size_t size_bits) {
  43. bitarray_t ba = {.size_bits = size_bits};
  44. // if the array is small enough, we can use inline storage
  45. if (size_bits <= sizeof(ba.u.block) * 8) {
  46. // nothing to be done
  47. // otherwise we need to heap-allocate
  48. } else {
  49. size_t capacity = size_bits / 8 + (size_bits % 8 == 0 ? 0 : 1);
  50. ba.u.base = gv_calloc(capacity, sizeof(uint8_t));
  51. }
  52. return ba;
  53. }
  54. /// get the value of the given element
  55. static inline bool bitarray_get(bitarray_t self, size_t index) {
  56. assert(index < self.size_bits && "out of bounds access");
  57. // determine if this array is stored inline or not
  58. const uint8_t *base;
  59. if (self.size_bits <= sizeof(self.u.block) * 8) {
  60. base = self.u.block;
  61. } else {
  62. base = self.u.base;
  63. }
  64. return (base[index / 8] >> (index % 8)) & 1;
  65. }
  66. /// set or clear the value of the given element
  67. static inline void bitarray_set(bitarray_t *self, size_t index, bool value) {
  68. assert(index < self->size_bits && "out of bounds access");
  69. // determine if this array is stored inline or not
  70. uint8_t *base;
  71. if (self->size_bits <= sizeof(self->u.block) * 8) {
  72. base = self->u.block;
  73. } else {
  74. base = self->u.base;
  75. }
  76. if (value) {
  77. base[index / 8] |= (uint8_t)(UINT8_C(1) << (index % 8));
  78. } else {
  79. base[index / 8] &= (uint8_t) ~(UINT8_C(1) << (index % 8));
  80. }
  81. }
  82. /// free underlying resources and leave a bit array empty
  83. static inline void bitarray_reset(bitarray_t *self) {
  84. assert(self != NULL);
  85. // is this array stored out of line?
  86. if (self->size_bits > sizeof(self->u.block) * 8)
  87. free(self->u.base);
  88. *self = (bitarray_t){0};
  89. }