RoaringBitmap/CRoaring

Name: CRoaring

Owner: Roaring bitmaps: A better compressed bitset

Description: Roaring bitmaps in C (and C++)

Created: 2015-12-01 21:45:40.0

Updated: 2018-01-14 23:39:21.0

Pushed: 2018-01-05 21:24:30.0

Homepage: http://roaringbitmap.org/

Size: 45059

Language: C

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

CRoaring Build Status Build Status

Portable Roaring bitmaps in C (and C++) with full support for your favorite compiler (GNU GCC, LLVM's clang, Visual Studio).

Introduction

Bitsets, also called bitmaps, are commonly used as fast data structures. Unfortunately, they can use too much memory. To compensate, we often use compressed bitmaps.

Roaring bitmaps are compressed bitmaps which tend to outperform conventional compressed bitmaps such as WAH, EWAH or Concise. They are used by several major systems such as Apache Lucene and derivative systems such as Solr and Elasticsearch, Metamarkets' Druid, LinkedIn Pinot, Netflix Atlas, Apache Spark, OpenSearchServer, Cloud Torrent, Whoosh, InfluxDB, Pilosa, Bleve, Microsoft Visual Studio Team Services (VSTS), and eBay's Apache Kylin.

We published a peer-reviewed article on the design and evaluation of this library:

Roaring bitmaps are found to work well in many important applications:

Use Roaring for bitmap compression whenever possible. Do not use other bitmap compression methods (Wang et al., SIGMOD 2017)

There is a serialized format specification for interoperability between implementations: https://github.com/RoaringBitmap/RoaringFormatSpec/

Objective

The primary goal of the CRoaring is to provide a high performance low-level implementation that fully take advantage of the latest hardware. Roaring bitmaps are already available on a variety of platform through Java, Go, Rust… implementations. CRoaring is a library that seeks to achieve superior performance by staying close to the latest hardware.

(c) 2016-2017 The CRoaring authors.

Requirements

Serialization on big endian hardware may not be compatible with serialization on little endian hardware.

Amalgamation/Unity Build

The CRoaring library can be amalgamated into a single source file that makes it easier for integration into other projects. Moreover, by making it possible to compile all the critical code into one compilation unit, it can improve the performance. For the rationale, please see the SQLite documentation, https://www.sqlite.org/amalgamation.html, or the corresponding Wikipedia entry (https://en.wikipedia.org/wiki/Single_Compilation_Unit). Users who choose this route, do not need to rely on CRoaring's build system (based on CMake).

We maintain pre-generated amalgamated files at https://github.com/lemire/CRoaringUnityBuild for your convenience.

To generate the amalgamated files yourself, you can invoke a bash script…

algamation.sh

(Bash shells are standard under Linux and macOS. Bash shells are available under Windows as part of the  GitHub Desktop under the name Git Shell. So if you have cloned the CRoaring GitHub repository from within the GitHub Desktop, you can right-click on CRoaring, select Git Shell and then enter the above commands.)

It is not necessary to invoke the script in the CRoaring directory. You can invoke it from any directory where you want the amalgamation files to be written.

It will generate three files for C users: roaring.h, roaring.c and amalgamation_demo.c… as well as some brief instructions. The amalgamation_demo.c file is a short example, whereas roaring.h and roaring.c are “amalgamated” files (including all source and header files for the project). This means that you can simply copy the files roaring.h and roaring.c into your project and be ready to go! No need to produce a library! See the amalgamation_demo.c file.

For example, you can use the C code as follows:

lude <stdio.h>
lude "roaring.c"
main() {
aring_bitmap_t *r1 = roaring_bitmap_create();
r (uint32_t i = 100; i < 1000; i++) roaring_bitmap_add(r1, i);
intf("cardinality = %d\n", (int) roaring_bitmap_get_cardinality(r1));
aring_bitmap_free(r1);
turn 0;

The script will also generate C++ files for C++ users, including an example. You can use the C++ as follows.

lude <iostream>
lude "roaring.hh"
lude "roaring.c"
main() {
aring r1;
r (uint32_t i = 100; i < 1000; i++) {
r1.add(i);

d::cout << "cardinality = " << r1.cardinality() << std::endl;

aring64Map r2;
r (uint64_t i = 18000000000000000100ull; i < 18000000000000001000ull; i++) {
r2.add(i);

d::cout << "cardinality = " << r2.cardinality() << std::endl;
turn 0;

If you prefer a silent output, you can use the following command to redirect stdout :

algamation.sh > /dev/null

API

The interface is found in the file include/roaring/roaring.h.

Example ©


 #include <roaring/roaring.h>


reate a new empty bitmap
ing_bitmap_t *r1 = roaring_bitmap_create();
hen we can add values
(uint32_t i = 100; i < 1000; i++) roaring_bitmap_add(r1, i);
heck whether a value is contained
rt(roaring_bitmap_contains(r1, 500));
ompute how many bits there are:
32_t cardinality = roaring_bitmap_get_cardinality(r1);
tf("Cardinality = %d \n", cardinality);

f your bitmaps have long runs, you can compress them by calling
un_optimize
32_t expectedsizebasic = roaring_bitmap_portable_size_in_bytes(r1);
ing_bitmap_run_optimize(r1);
32_t expectedsizerun = roaring_bitmap_portable_size_in_bytes(r1);
tf("size before run optimize %d bytes, and after %d bytes\n",
   expectedsizebasic, expectedsizerun);

reate a new bitmap containing the values {1,2,3,5,6}
ing_bitmap_t *r2 = roaring_bitmap_of(5, 1, 2, 3, 5, 6);
ing_bitmap_printf(r2);  // print it

e can also create a bitmap from a pointer to 32-bit integers
32_t somevalues[] = {2, 3, 4};
ing_bitmap_t *r3 = roaring_bitmap_of_ptr(3, somevalues);

e can also go in reverse and go from arrays to bitmaps
64_t card1 = roaring_bitmap_get_cardinality(r1);
32_t *arr1 = (uint32_t *) malloc(card1 * sizeof(uint32_t));
rt(arr1  != NULL);
ing_bitmap_to_uint32_array(r1, arr1);
ing_bitmap_t *r1f = roaring_bitmap_of_ptr(card1, arr1);
(arr1);
rt(roaring_bitmap_equals(r1, r1f));  // what we recover is equal
ing_bitmap_free(r1f);

e can copy and compare bitmaps
ing_bitmap_t *z = roaring_bitmap_copy(r3);
rt(roaring_bitmap_equals(r3, z));  // what we recover is equal
ing_bitmap_free(z);

e can compute union two-by-two
ing_bitmap_t *r1_2_3 = roaring_bitmap_or(r1, r2);
ing_bitmap_or_inplace(r1_2_3, r3);

e can compute a big union
t roaring_bitmap_t *allmybitmaps[] = {r1, r2, r3};
ing_bitmap_t *bigunion = roaring_bitmap_or_many(3, allmybitmaps);
rt(
roaring_bitmap_equals(r1_2_3, bigunion));  // what we recover is equal
an also do the big union with a heap
ing_bitmap_t *bigunionheap = roaring_bitmap_or_many_heap(3, allmybitmaps);
rt_true(roaring_bitmap_equals(r1_2_3, bigunionheap));

ing_bitmap_free(r1_2_3);
ing_bitmap_free(bigunion);
ing_bitmap_free(bigunionheap);

e can compute intersection two-by-two
ing_bitmap_t *i1_2 = roaring_bitmap_and(r1, r2);
ing_bitmap_free(i1_2);

e can write a bitmap to a pointer and recover it later
32_t expectedsize = roaring_bitmap_portable_size_in_bytes(r1);
 *serializedbytes = malloc(expectedsize);
ing_bitmap_portable_serialize(r1, serializedbytes);
ing_bitmap_t *t = roaring_bitmap_portable_deserialize(serializedbytes);
rt(roaring_bitmap_equals(r1, t));  // what we recover is equal
ing_bitmap_free(t);
e can also check whether there is a bitmap at a memory location without reading it
_t sizeofbitmap = roaring_bitmap_portable_deserialize_size(serializedbytes,expectedsize);
rt(sizeofbitmap == expectedsize);  // sizeofbitmap would be zero if no bitmap were found
e can also read the bitmap "safely" by specifying a byte size limit:
roaring_bitmap_portable_deserialize_safe(serializedbytes,expectedsize);
rt(roaring_bitmap_equals(r1, t));  // what we recover is equal
ing_bitmap_free(t);

(serializedbytes);


e can iterate over all values using custom functions
32_t counter = 0;
ing_iterate(r1, roaring_iterator_sumall, &counter);

ool roaring_iterator_sumall(uint32_t value, void *param) {
      *(uint32_t *) param += value;
      return true; //iterate till the end
}


e can also create iterator structs
ter = 0;
ing_uint32_iterator_t *  i = roaring_create_iterator(r1);
e(i->has_value) {
ounter++; // could use    i->current_value
oaring_advance_uint32_iterator(i);

ou can skip over values and move the iterator with
oaring_move_uint32_iterator_equalorlarger(i,someintvalue)

ing_free_uint32_iterator(i);
oaring_bitmap_get_cardinality(r1) == counter

ing_bitmap_free(r1);
ing_bitmap_free(r2);
ing_bitmap_free(r3);

Example (C++)


 #include "roaring.hh" from cpp directory

ing r1;
(uint32_t i = 100; i < 1000; i++) {
.add(i);


heck whether a value is contained
rt(r1.contains(500));

ompute how many bits there are:
32_t cardinality = r1.cardinality();

f your bitmaps have long runs, you can compress them by calling
un_optimize
32_t size = r1.getSizeInBytes();
unOptimize();

ou can enable "copy-on-write" for fast and shallow copies
etCopyOnWrite(true);


32_t compact_size = r1.getSizeInBytes();
:cout << "size before run optimize " << size << " bytes, and after "
        <<  compact_size << " bytes." << std::endl;


reate a new bitmap with varargs
ing r2 = Roaring::bitmapOf(5, 1, 2, 3, 5, 6);

rintf();
tf("\n");

e can also create a bitmap from a pointer to 32-bit integers
t uint32_t values[] = {2, 3, 4};
ing r3(3, values);

e can also go in reverse and go from arrays to bitmaps
64_t card1 = r1.cardinality();
32_t *arr1 = new uint32_t[card1];
oUint32Array(arr1);
ing r1f(card1, arr1);
te[] arr1;

itmaps shall be equal
rt(r1 == r1f);

e can copy and compare bitmaps
ing z (r3);
rt(r3 == z);

e can compute union two-by-two
ing r1_2_3 = r1 | r2;
_3 |= r3;

e can compute a big union
t Roaring *allmybitmaps[] = {&r1, &r2, &r3};
ing bigunion = Roaring::fastunion(3, allmybitmaps);
rt(r1_2_3 == bigunion);

e can compute intersection two-by-two
ing i1_2 = r1 & r2;

e can write a bitmap to a pointer and recover it later
32_t expectedsize = r1.getSizeInBytes();
 *serializedbytes = new char [expectedsize];
rite(serializedbytes);
ing t = Roaring::read(serializedbytes);
rt(r1 == t);
te[] serializedbytes;

e can iterate over all values using custom functions
32_t counter = 0;
terate(roaring_iterator_sumall, &counter);
/**
 * bool roaring_iterator_sumall(uint32_t value, void *param) {
 *        *(uint32_t *) param += value;
 *        return true; // iterate till the end
 *  }
 *
 */

e can also iterate the C++ way
ter = 0;
Roaring::const_iterator i = t.begin() ; i != t.end() ; i++) {
+counter;

ounter == t.cardinality()

e can move iterators to skip values
t uint32_t manyvalues[] = {2, 3, 4, 7, 8};
ing rogue(5, manyvalues);
ing::const_iterator j = rogue.begin();
ualorlarger(4); // *j == 4

Building with cmake (Linux and macOS, Visual Studio users should see below)

CRoaring follows the standard cmake workflow. Starting from the root directory of the project (CRoaring), you can do:

r -p build
uild
e ..

llow by 'make test' if you want to test.
u can also type 'make install' to install the library on your system
header files typically get installed to /usr/local/include/roaring
ereas C++ header files get installed to /usr/local/include/roaring

(You can replace the build directory with any other directory name.)

By default, on all platforms, we build a dynamic library. You can generate a static library by adding -DBUILD_STATIC=ON to the command line.

As with all cmake projects, you can specify the compilers you wish to use by adding (for example) -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ to the cmake command line.

If wish to build an x64 version while disabling AVX2 and BMI2 support at the expense of performance, you can do the following :

r -p buildnoavx
uildnoavx
e -DDISABLE_AVX=ON ..

The reverse is also possible. Some compilers may not enable AVX2 support, but you can force it in the following manner:

r -p buildwithavx
uildwithavx
e -DFORCE_AVX=ON ..

If you have x64 hardware, but you wish to disable all x64-specific optimizations (including AVX), then you can do the following…

r -p buildnox64
uildnoavx
e -DDISABLE_X64=ON ..

For a debug release, starting from the root directory of the project (CRoaring), try

r -p debug
ebug
e -DCMAKE_BUILD_TYPE=Debug -DSANITIZE=ON ..

(Again, you can use the -DDISABLE_AVX=ON flag if you need it.)

(Of course you can replace the debug directory with any other directory name.)

To run unit tests (you must first run make):

 test

The detailed output of the tests can be found in Testing/Temporary/LastTest.log.

To run real-data benchmark

al_bitmaps_benchmark ../benchmarks/realdata/census1881

where you must adjust the path “../benchmarks/realdata/census1881” so that it points to one of the directories in the benchmarks/realdata directory.

To check that your code abides by the style convention (make sure that clang-format is installed):

ols/clang-format-check.sh

To reformat your code according to the style convention (make sure that clang-format is installed):

ols/clang-format.sh

Building (Visual Studio under Windows)

We are assuming that you have a common Windows PC with at least Visual Studio 2015, and an x64 processor.

To build with at least Visual Studio 2015 from the command line:

To build with at least Visual Studio 2017 directly in the IDE:

We have optimizations specific to AVX2 in the code, and they are turned only if the __AVX2__ macro is defined. In turn, these optimizations should only be enabled if you know that your target machines will support AVX2. Given that all recent Intel and AMD processors support AVX2, you may want to make this assumption. Thankfully, Visual Studio does define the __AVX2__ macro whenever the /arch:AVX2 compiler option is set. Unfortunately, this option might not be set by default. Thankfully, you can enable it with CMake by adding the -DFORCE_AVX=ON flag (e.g., type cmake -DFORCE_AVX=ON -DCMAKE_GENERATOR_PLATFORM=x64 .. instead of cmake -DCMAKE_GENERATOR_PLATFORM=x64 ..). If you are building directly in the IDE (with at least Visual Studio 2017 and the Visual C++ tools for CMake component), then right click on CMakeLists.txt and select “Change CMake Settings”. This opens a JSON file called CMakeSettings.json. This file allows you to add CMake flags by editing the "cmakeCommandArgs" keys. E.g., you can modify the lines that read "cmakeCommandArgs" : "" so that they become "cmakeCommandArgs" : "-DFORCE_AVX=ON". The relevant part of the JSON file might look at follows:

  {
    "name": "x64-Debug",
    "generator": "Visual Studio 15 2017 Win64",
    "configurationType": "Debug",
    "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
    "cmakeCommandArgs": "-DFORCE_AVX=ON",
    "buildCommandArgs": "-m -v:minimal"
  },
  {
    "name": "x64-Release",
    "generator": "Visual Studio 15 2017 Win64",
    "configurationType" : "Release",
    "buildRoot":  "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
    "cmakeCommandArgs":  "-DFORCE_AVX=ON",
    "buildCommandArgs": "-m -v:minimal"
   }

After this modification, the output of CMake should include a line such as this one:

   CMAKE_C_FLAGS:   /arch:AVX2  -Wall

You must understand that this implies that the produced binaries will not run on hardware that does not support AVX2. However, you might get better performance.

We have additionnal optimizations that use inline assembly. However, Visual Studio does not support inline assembly so you cannot benefit from these optimizations under Visual Studio.

AVX2-related throttling

Our AVX2 code does not use floating-point numbers or multiplications, so it is not subject to turbo frequency throttling on many-core Intel processors.

Thread safety

Like, for example, STL containers or Java's default data structures, the CRoaring library has no built-in thread support. Thus whenever you modify a bitmap in one thread, it is unsafe to query it in others. It is safe however to query bitmaps (without modifying them) from several distinct threads, as long as you do not use the copy-on-write attribute. For example, you can safely copy a bitmap and use both copies in concurrently. One should probably avoid the use of the copy-on-write attribute in a threaded environment.

Python Wrapper

Tom Cornebize wrote a Python wrapper available at https://github.com/Ezibenroc/PyRoaringBitMap

C# Wrapper

Brandon Smith wrote a C# wrapper available at https://github.com/RogueException/CRoaring.Net (works for Windows and Linux under x64 processors)

Go (golang) Wrapper

There is a Go (golang) wrapper available at https://github.com/RoaringBitmap/gocroaring

Rust Wrapper

Saulius Grigaliunas wrote a Rust wrapper available at https://github.com/saulius/croaring-rs

Redis Module

Antonio Guilherme Ferreira Viggiano wrote a Redis Module available at https://github.com/aviggiano/redis-roaring

References and further reading

Mailing list/discussion group

https://groups.google.com/forum/#!forum/roaring-bitmaps

References about Roaring


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.