Skip to content

Commit

Permalink
Add roaring_bitmap_portable_deserialize_frozen (#421)
Browse files Browse the repository at this point in the history
* Add roaring_bitmap_portable_deserialize_frozen

* Add deserialization to real_bitmaps_benchmark

* Fix cycle count variable types in real_bitmaps_benchmark

* Add comment on unaligned access

* Allow unaligned memory access for roaring_bitmap_portable_deserialize_frozen

* Add ALLOW_UNALIGNED
  • Loading branch information
andreas authored Jan 26, 2023
1 parent 3078dfa commit 5f73c5e
Show file tree
Hide file tree
Showing 11 changed files with 323 additions and 0 deletions.
40 changes: 40 additions & 0 deletions benchmarks/real_bitmaps_benchmark.c
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,46 @@ int main(int argc, char **argv) {
" cycles\n",
count, total_count, cycles_final - cycles_start);

uint64_t portable_cycle_count = 0, portable_frozen_cycle_count = 0,
frozen_cycle_count = 0;
for(int i = 0; i < (int)count; i++) {
int size = roaring_bitmap_portable_size_in_bytes(bitmaps[i]);
char *buf = malloc(size);
roaring_bitmap_portable_serialize(bitmaps[i], buf);

int frozen_size = roaring_bitmap_frozen_size_in_bytes(bitmaps[i]);
char *frozen_buf = roaring_aligned_malloc(32, frozen_size);
roaring_bitmap_frozen_serialize(bitmaps[i], frozen_buf);

RDTSC_START(cycles_start);
roaring_bitmap_t *r1 = roaring_bitmap_portable_deserialize(buf);
RDTSC_FINAL(cycles_final);
portable_cycle_count += cycles_final - cycles_start;

RDTSC_START(cycles_start);
roaring_bitmap_t *r2 = roaring_bitmap_portable_deserialize_frozen(buf);
RDTSC_FINAL(cycles_final);
portable_frozen_cycle_count += cycles_final - cycles_start;

RDTSC_START(cycles_start);
roaring_bitmap_t *r3 = roaring_bitmap_frozen_view(frozen_buf, frozen_size);
RDTSC_FINAL(cycles_final);
frozen_cycle_count += cycles_final - cycles_start;

roaring_bitmap_free(r1);
roaring_bitmap_free(r2);
roaring_bitmap_free(r3);
free(buf);
roaring_aligned_free(frozen_buf);
}

printf("Deserializing %zu bitmaps took %" PRIu64 " cycles for portable format\n",
count, portable_cycle_count);
printf("Deserializing %zu bitmaps took %" PRIu64 " cycles for portable frozen format\n",
count, portable_frozen_cycle_count);
printf("Deserializing %zu bitmaps took %" PRIu64 " cycles for frozen format\n",
count, frozen_cycle_count);

for (int i = 0; i < (int)count; ++i) {
free(numbers[i]);
numbers[i] = NULL; // paranoid
Expand Down
2 changes: 2 additions & 0 deletions include/roaring/containers/array.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ void array_container_free(array_container_t *array);
array_container_t *array_container_clone(const array_container_t *src);

/* Get the cardinality of `array'. */
ALLOW_UNALIGNED
static inline int array_container_cardinality(const array_container_t *array) {
return array->cardinality;
}
Expand Down Expand Up @@ -214,6 +215,7 @@ static inline int32_t array_container_size_in_bytes(
/**
* Return true if the two arrays have the same content.
*/
ALLOW_UNALIGNED
static inline bool array_container_equals(
const array_container_t *container1,
const array_container_t *container2) {
Expand Down
1 change: 1 addition & 0 deletions include/roaring/containers/bitset.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ static inline bool bitset_container_contains_range(const bitset_container_t *bit
}

/* Get the number of bits set */
ALLOW_UNALIGNED
static inline int bitset_container_cardinality(
const bitset_container_t *bitset) {
return bitset->cardinality;
Expand Down
1 change: 1 addition & 0 deletions include/roaring/containers/run.h
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ static inline int32_t run_container_size_in_bytes(
/**
* Return true if the two containers have the same content.
*/
ALLOW_UNALIGNED
static inline bool run_container_equals(const run_container_t *container1,
const run_container_t *container2) {
if (container1->n_runs != container2->n_runs) {
Expand Down
6 changes: 6 additions & 0 deletions include/roaring/portability.h
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,12 @@ static inline int hamming(uint64_t x) {
#define CROARING_UNTARGET_REGION
#endif

// Allow unaligned memory access
#if defined(__GNUC__) || defined(__clang__)
#define ALLOW_UNALIGNED __attribute__((no_sanitize("alignment")))
#else
#define ALLOW_UNALIGNED
#endif

// We need portability.h to be included first,
// but we also always want isadetection.h to be
Expand Down
18 changes: 18 additions & 0 deletions include/roaring/roaring.h
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,24 @@ roaring_bitmap_t *roaring_bitmap_portable_deserialize(const char *buf);
roaring_bitmap_t *roaring_bitmap_portable_deserialize_safe(const char *buf,
size_t maxbytes);

/**
* Read bitmap from a serialized buffer.
* In case of failure, NULL is returned.
*
* Bitmap returned by this function can be used in all readonly contexts.
* Bitmap must be freed as usual, by calling roaring_bitmap_free().
* Underlying buffer must not be freed or modified while it backs any bitmaps.
*
* The function is unsafe in the following ways:
* 1) It may execute unaligned memory accesses.
* 2) A buffer overflow may occure if buf does not point to a valid serialized
* bitmap.
*
* This is meant to be compatible with the Java and Go versions:
* https://github.com/RoaringBitmap/RoaringFormatSpec
*/
roaring_bitmap_t *roaring_bitmap_portable_deserialize_frozen(const char *buf);

/**
* Check how many bytes would be read (up to maxbytes) at this pointer if there
* is a bitmap, returns zero if there is no valid bitmap.
Expand Down
1 change: 1 addition & 0 deletions src/containers/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ void array_container_intersection_inplace(array_container_t *src_1,
}
}

ALLOW_UNALIGNED
int array_container_to_uint32_array(void *vout, const array_container_t *cont,
uint32_t base) {
int outpos = 0;
Expand Down
3 changes: 3 additions & 0 deletions src/containers/bitset.c
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,7 @@ BITSET_CONTAINER_FN(andnot, &~, _mm256_andnot_si256, vbicq_u64)
// clang-format On


ALLOW_UNALIGNED
int bitset_container_to_uint32_array(
uint32_t *out,
const bitset_container_t *bc,
Expand Down Expand Up @@ -816,6 +817,7 @@ bool bitset_container_iterate64(const bitset_container_t *cont, uint32_t base, r

#ifdef CROARING_IS_X64
CROARING_TARGET_AVX2
ALLOW_UNALIGNED
static inline bool _avx2_bitset_container_equals(const bitset_container_t *container1, const bitset_container_t *container2) {
const __m256i *ptr1 = (const __m256i*)container1->words;
const __m256i *ptr2 = (const __m256i*)container2->words;
Expand All @@ -832,6 +834,7 @@ static inline bool _avx2_bitset_container_equals(const bitset_container_t *conta
CROARING_UNTARGET_REGION
#endif // CROARING_IS_X64

ALLOW_UNALIGNED
bool bitset_container_equals(const bitset_container_t *container1, const bitset_container_t *container2) {
if((container1->cardinality != BITSET_UNKNOWN_CARDINALITY) && (container2->cardinality != BITSET_UNKNOWN_CARDINALITY)) {
if(container1->cardinality != container2->cardinality) {
Expand Down
2 changes: 2 additions & 0 deletions src/containers/run.c
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,7 @@ void run_container_andnot(const run_container_t *src_1,
}
}

ALLOW_UNALIGNED
int run_container_to_uint32_array(void *vout, const run_container_t *cont,
uint32_t base) {
int outpos = 0;
Expand Down Expand Up @@ -830,6 +831,7 @@ int run_container_rank(const run_container_t *container, uint16_t x) {
#ifdef CROARING_IS_X64

CROARING_TARGET_AVX2
ALLOW_UNALIGNED
/* Get the cardinality of `run'. Requires an actual computation. */
static inline int _avx2_run_container_cardinality(const run_container_t *run) {
const int32_t n_runs = run->n_runs;
Expand Down
155 changes: 155 additions & 0 deletions src/roaring.c
Original file line number Diff line number Diff line change
Expand Up @@ -3194,6 +3194,161 @@ roaring_bitmap_frozen_view(const char *buf, size_t length) {
return rb;
}

ALLOW_UNALIGNED
roaring_bitmap_t *roaring_bitmap_portable_deserialize_frozen(const char *buf) {
char *start_of_buf = (char *) buf;
uint32_t cookie;
int32_t num_containers;
uint16_t *descriptive_headers;
uint32_t *offset_headers = NULL;
const char *run_flag_bitset = NULL;
bool hasrun = false;

// deserialize cookie
memcpy(&cookie, buf, sizeof(uint32_t));
buf += sizeof(uint32_t);
if (cookie == SERIAL_COOKIE_NO_RUNCONTAINER) {
memcpy(&num_containers, buf, sizeof(int32_t));
buf += sizeof(int32_t);
descriptive_headers = (uint16_t *) buf;
buf += num_containers * 2 * sizeof(uint16_t);
offset_headers = (uint32_t *) buf;
buf += num_containers * sizeof(uint32_t);
} else if ((cookie & 0xFFFF) == SERIAL_COOKIE) {
num_containers = (cookie >> 16) + 1;
hasrun = true;
int32_t run_flag_bitset_size = (num_containers + 7) / 8;
run_flag_bitset = buf;
buf += run_flag_bitset_size;
descriptive_headers = (uint16_t *) buf;
buf += num_containers * 2 * sizeof(uint16_t);
if(num_containers >= NO_OFFSET_THRESHOLD) {
offset_headers = (uint32_t *) buf;
buf += num_containers * sizeof(uint32_t);
}
} else {
return NULL;
}

// calculate total size for allocation
int32_t num_bitset_containers = 0;
int32_t num_run_containers = 0;
int32_t num_array_containers = 0;

for (int32_t i = 0; i < num_containers; i++) {
uint16_t tmp;
memcpy(&tmp, descriptive_headers + 2*i+1, sizeof(tmp));
uint32_t cardinality = tmp + 1;
bool isbitmap = (cardinality > DEFAULT_MAX_SIZE);
bool isrun = false;
if(hasrun) {
if((run_flag_bitset[i / 8] & (1 << (i % 8))) != 0) {
isbitmap = false;
isrun = true;
}
}

if (isbitmap) {
num_bitset_containers++;
} else if (isrun) {
num_run_containers++;
} else {
num_array_containers++;
}
}

size_t alloc_size = 0;
alloc_size += sizeof(roaring_bitmap_t);
alloc_size += num_containers * sizeof(container_t*);
alloc_size += num_bitset_containers * sizeof(bitset_container_t);
alloc_size += num_run_containers * sizeof(run_container_t);
alloc_size += num_array_containers * sizeof(array_container_t);
alloc_size += num_containers * sizeof(uint16_t); // keys
alloc_size += num_containers * sizeof(uint8_t); // typecodes

// allocate bitmap and construct containers
char *arena = (char *)roaring_malloc(alloc_size);
if (arena == NULL) {
return NULL;
}

roaring_bitmap_t *rb = (roaring_bitmap_t *)
arena_alloc(&arena, sizeof(roaring_bitmap_t));
rb->high_low_container.flags = ROARING_FLAG_FROZEN;
rb->high_low_container.allocation_size = num_containers;
rb->high_low_container.size = num_containers;
rb->high_low_container.containers =
(container_t **)arena_alloc(&arena,
sizeof(container_t*) * num_containers);

uint16_t *keys = arena_alloc(&arena, num_containers * sizeof(uint16_t));
uint8_t *typecodes = arena_alloc(&arena, num_containers * sizeof(uint8_t));

rb->high_low_container.keys = keys;
rb->high_low_container.typecodes = typecodes;

for (int32_t i = 0; i < num_containers; i++) {
uint16_t tmp;
memcpy(&tmp, descriptive_headers + 2*i+1, sizeof(tmp));
int32_t cardinality = tmp + 1;
bool isbitmap = (cardinality > DEFAULT_MAX_SIZE);
bool isrun = false;
if(hasrun) {
if((run_flag_bitset[i / 8] & (1 << (i % 8))) != 0) {
isbitmap = false;
isrun = true;
}
}

keys[i] = descriptive_headers[2*i];

if (isbitmap) {
typecodes[i] = BITSET_CONTAINER_TYPE;
bitset_container_t *c = arena_alloc(&arena, sizeof(bitset_container_t));
c->cardinality = cardinality;
if(offset_headers != NULL) {
c->words = (uint64_t *) (start_of_buf + offset_headers[i]);
} else {
c->words = (uint64_t *) buf;
buf += BITSET_CONTAINER_SIZE_IN_WORDS * sizeof(uint64_t);
}
rb->high_low_container.containers[i] = c;
} else if (isrun) {
typecodes[i] = RUN_CONTAINER_TYPE;
run_container_t *c = arena_alloc(&arena, sizeof(run_container_t));
c->capacity = cardinality;
uint16_t n_runs;
if(offset_headers != NULL) {
memcpy(&n_runs, start_of_buf + offset_headers[i], sizeof(uint16_t));
c->n_runs = n_runs;
c->runs = (rle16_t *) (start_of_buf + offset_headers[i] + sizeof(uint16_t));
} else {
memcpy(&n_runs, buf, sizeof(uint16_t));
c->n_runs = n_runs;
buf += sizeof(uint16_t);
c->runs = (rle16_t *) buf;
buf += c->n_runs * sizeof(rle16_t);
}
rb->high_low_container.containers[i] = c;
} else {
typecodes[i] = ARRAY_CONTAINER_TYPE;
array_container_t *c = arena_alloc(&arena, sizeof(array_container_t));
c->cardinality = cardinality;
c->capacity = cardinality;
if(offset_headers != NULL) {
c->array = (uint16_t *) (start_of_buf + offset_headers[i]);
} else {
c->array = (uint16_t *) buf;
buf += cardinality * sizeof(uint16_t);
}
rb->high_low_container.containers[i] = c;
}
}

return rb;
}


#ifdef __cplusplus
} } } // extern "C" { namespace roaring {
#endif
Loading

0 comments on commit 5f73c5e

Please sign in to comment.