Skip to contentSkip to conversation

introduce Roaring bitmaps to Git

RFC PATCH In review Abhradeep Chakraborty via GitGitGadget 7 participants 25 messages Sep 19, 2022 – Nov 1, 2022 Kbuild

lore
Share & tools
lore
#0 lore raw

cover introduce Roaring bitmaps to Git

Git currently uses ewah bitmaps ( which are based on run-length encoding) to compress bitmaps. Ewah bitmaps stores bitmaps in the form of run-length words i.e. instead of storing each and every bit, it tries to find consecutive bits (having same value) and replace them with the value bit and the range upto which the bit is present. It is simple and efficient. But one downside of this approach is that we have to decompress the whole bitmap in order to find the bit of a certain position.

For small (or medium sized) bitmaps, this is not an issue. But it can be an issue for large (or extra large) bitmaps. In that case roaring bitmaps are generally more efficient[1] than ewah itself. Some benchmarks suggests that roaring bitmaps give more performance benefits than ewah or any other similar compression technique.

This patch series is currently in RFC state and it aims to let Git use roaring bitmaps. As this is an RFC patch series (for now), the code are not fully accurate (i.e. some tests are failing). But it is backward-compatible (tests related to ewah bitmaps are passing). Some commit messages might need more explanation and some commits may need a split (specially the one that implement writing roaring bitmaps). Overall, the structure and code are near to ready to make the series a formal patch series.

I am submitting it as an RFC (after discussions with mentors) because the GSoC coding period is about to end. I will continue to work on the patch series.

Abhradeep Chakraborty (5):
reachability-bitmaps: add CRoaring library to Git roaring.[ch]: apply Git specific changes to the roaring API
roaring: teach Git to write roaring bitmaps
roaring: introduce a new config option for roaring bitmaps
roaring: teach Git to read roaring bitmaps

 Makefile                   |     3 +
 bitmap.c                   |   225 +
 bitmap.h                   |    33 +
 builtin/diff.c             |    10 +-
 builtin/multi-pack-index.c |     5 +
 builtin/pack-objects.c     |    81 +-
 ewah/bitmap.c              |    61 +-
 ewah/ewok.h                |    37 +-
 midx.c                     |     7 +
 midx.h                     |     1 +
 pack-bitmap-write.c        |   326 +-
 pack-bitmap.c              |   969 +-
 pack-bitmap.h              |    27 +-
 roaring/roaring.c          | 20047 +++++++++++++++++++++++++++++++++++
 roaring/roaring.h          |  1028 ++
 t/t5310-pack-bitmaps.sh    |    79 +-
 16 files changed, 22490 insertions(+), 449 deletions(-)
 create mode 100644 bitmap.c
 create mode 100644 bitmap.h
 create mode 100644 roaring/roaring.c
 create mode 100644 roaring/roaring.h

base-commit: d3fa443f97e3a8d75b51341e2d5bac380b7422df
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1357%2FAbhra303%2Froaring-bitmap-exp-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1357/Abhra303/roaring-bitmap-exp-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1357

Signature · 1 line
gitgitgadget
#1 lore raw

PATCH 1/5 reachability-bitmaps: add CRoaring library to Git

From: Abhradeep Chakraborty <[email protected]>

According to Roaring bitmap's paper[1], it gives better performance (in most cases) than EWAH bitmaps. Its compression ratio is also good. Moreover, unlike EWAH, it doesn't have to parse the whole bitmap to know about one object. So, It may be good for Git to use Roaring bitmaps for its work.

CRoaring is a well tested library for Roaring bitmaps and is mainly written by the author of the mentioned paper.

Add CRoaring library to use roaring bitmap related functions.

[1] https://arxiv.org/pdf/1603.06549.pdf

---
 Makefile          |     2 +
 roaring/roaring.c | 19522 ++++++++++++++++++++++++++++++++++++++++++++
 roaring/roaring.h |  1011 +++
 3 files changed, 20535 insertions(+)
 create mode 100644 roaring/roaring.c
 create mode 100644 roaring/roaring.h
Makefile +2 -0
index d9247ead45b..e9537951105 100644
@@ -1060,6 +1060,7 @@ LIB_OBJS += rerere.o
 LIB_OBJS += reset.o
 LIB_OBJS += resolve-undo.o
 LIB_OBJS += revision.o
+LIB_OBJS += roaring/roaring.o
 LIB_OBJS += run-command.o
 LIB_OBJS += send-pack.o
 LIB_OBJS += sequencer.o
@@ -1258,6 +1259,7 @@ THIRD_PARTY_SOURCES += compat/nedmalloc/%
 THIRD_PARTY_SOURCES += compat/obstack.%
 THIRD_PARTY_SOURCES += compat/poll/%
 THIRD_PARTY_SOURCES += compat/regex/%
+THIRD_PARTY_SOURCES += roaring/roaring.c
 THIRD_PARTY_SOURCES += sha1collisiondetection/%
 THIRD_PARTY_SOURCES += sha1dc/%
 
roaring/roaring.c new file +19522 -0

2,986 lines not on this page · raw message

roaring/roaring.h new file +1011 -0

1,011 lines not on this page · raw message

The rest of this diff is not shown. View the raw message.

Signature · 1 line
gitgitgadget

Part of the diff is not shown here. View the raw message.

#19 lore raw

PATCH 4/5 roaring: introduce a new config option for roaring bitmaps

In reply to Abhradeep Chakraborty via GitGitGadget

From: Abhradeep Chakraborty <[email protected]>

Though Git can write roaring bitmaps, there is still no way (e.g. configurations) to control the writing of roaring bitmaps.

Introduce pack.useroaringbitmap option to control the writing of roaring bitmaps.

---
 builtin/multi-pack-index.c | 5 +++++
 builtin/pack-objects.c     | 7 ++++++-
 midx.c                     | 7 +++++++
 midx.h                     | 1 +
 pack-bitmap.h              | 7 +++----
 5 files changed, 22 insertions(+), 5 deletions(-)
builtin/multi-pack-index.c +5 -0
index 9b126d6ce0e..9e221dd7cc9 100644
@@ -80,6 +80,11 @@ static struct option *add_common_options(struct option *prev)
 static int git_multi_pack_index_write_config(const char *var, const char *value,
 					     void *cb UNUSED)
 {
+	if (!strcmp(var, "pack.useroaringbitmap")) {
+		if (git_config_bool(var, value))
+			opts.flags |= MIDX_WRITE_ROARING_BITMAP;
+	}
+
 	if (!strcmp(var, "pack.writebitmaphashcache")) {
 		if (git_config_bool(var, value))
 			opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE;
builtin/pack-objects.c +6 -1
index 3658c05cafc..439c5572c18 100644
@@ -228,7 +228,7 @@ static enum {
 	WRITE_BITMAP_QUIET,
 	WRITE_BITMAP_TRUE,
 } write_bitmap_index;
-static uint16_t write_bitmap_options = BITMAP_OPT_HASH_CACHE;
+static uint16_t write_bitmap_options = BITMAP_OPT_HASH_CACHE | BITMAP_SET_EWAH_BITMAP;
 
 static int exclude_promisor_objects;
 
@@ -1258,6 +1258,7 @@ static void write_pack_file(void)
 				    hash_to_hex(hash));
 
 			if (write_bitmap_index) {
+				bitmap_writer_init_bm_type(write_bitmap_options);
 				bitmap_writer_set_checksum(hash);
 				bitmap_writer_build_type_index(
 					&to_pack, written_list, nr_written);
@@ -3143,6 +3144,10 @@ static int git_pack_config(const char *k, const char *v, void *cb)
 		cache_max_small_delta_size = git_config_int(k, v);
 		return 0;
 	}
+	if (!strcmp(k, "pack.useroaringbitmap")) {
+		if (git_config_bool(k, v))
+			write_bitmap_options |= BITMAP_SET_ROARING_BITMAP;
+	}
 	if (!strcmp(k, "pack.writebitmaphashcache")) {
 		if (git_config_bool(k, v))
 			write_bitmap_options |= BITMAP_OPT_HASH_CACHE;
midx.c +7 -0
index c27d0e5f151..b80db2239a8 100644
@@ -1112,10 +1112,16 @@ static int write_midx_bitmap(const char *midx_name,
 {
 	int ret, i;
 	uint16_t options = 0;
+	unsigned version = 0;
 	struct pack_idx_entry **index;
 	char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name,
 					hash_to_hex(midx_hash));
 
+	if (flags & MIDX_WRITE_ROARING_BITMAP)
+		version |= BITMAP_SET_ROARING_BITMAP;
+	else
+		version |= BITMAP_SET_EWAH_BITMAP;
+
 	if (flags & MIDX_WRITE_BITMAP_HASH_CACHE)
 		options |= BITMAP_OPT_HASH_CACHE;
 
@@ -1131,6 +1137,7 @@ static int write_midx_bitmap(const char *midx_name,
 	for (i = 0; i < pdata->nr_objects; i++)
 		index[i] = &pdata->objects[i].idx;
 
+	bitmap_writer_init_bm_type(version);
 	bitmap_writer_show_progress(flags & MIDX_PROGRESS);
 	bitmap_writer_build_type_index(pdata, index, pdata->nr_objects);
 
midx.h +1 -0
index 5578cd7b835..c0b19b93c9c 100644
@@ -48,6 +48,7 @@ struct multi_pack_index {
 #define MIDX_WRITE_BITMAP (1 << 2)
 #define MIDX_WRITE_BITMAP_HASH_CACHE (1 << 3)
 #define MIDX_WRITE_BITMAP_LOOKUP_TABLE (1 << 4)
+#define MIDX_WRITE_ROARING_BITMAP (1 << 5)
 
 const unsigned char *get_midx_checksum(struct multi_pack_index *m);
 void get_midx_filename(struct strbuf *out, const char *object_dir);
pack-bitmap.h +3 -4
index 7d71deca023..6103e0d57e7 100644
@@ -30,9 +30,6 @@ struct bitmap_disk_header {
 
 #define NEEDS_BITMAP (1u<<22)
 
-#define BITMAP_SET_EWAH_BITMAP 0x1
-#define BITMAP_SET_ROARING_BITMAP (1 << 1)
-
 /*
  * The width in bytes of a single triplet in the lookup table
  * extension:
@@ -44,7 +41,9 @@ struct bitmap_disk_header {
 
 enum pack_bitmap_opts {
 	BITMAP_OPT_FULL_DAG = 0x1,
-	BITMAP_OPT_HASH_CACHE = 0x4,
+	BITMAP_SET_EWAH_BITMAP = 0x2,
+	BITMAP_SET_ROARING_BITMAP = 0x4,
+	BITMAP_OPT_HASH_CACHE = 0x8,
 	BITMAP_OPT_LOOKUP_TABLE = 0x10,
 };
 
Signature · 1 line
gitgitgadget
#2 lore raw

PATCH 2/5 roaring.[ch]: apply Git specific changes to the roaring API

In reply to Abhradeep Chakraborty via GitGitGadget

From: Abhradeep Chakraborty <[email protected]>

Though the Roaring library is introduced in previous commit, the library cannot be used as is. One reason is that the library doesn't support Big endian machines. Besides, Git specific file related functions does use hashwrite() (or similar). So there is a need to modify the library.

Implement and modify new functions so that Git can actually use the library.

---
 roaring/roaring.c | 565 ++++++++++++++++++++++++++++++++++++++++++++--
 roaring/roaring.h |  17 ++
 2 files changed, 562 insertions(+), 20 deletions(-)
roaring/roaring.c +545 -20
index df2d90544cd..ee44de20996 100644
@@ -1791,6 +1791,10 @@ bool array_container_iterate64(const array_container_t *cont, uint32_t base,
  *
  */
 int32_t array_container_write(const array_container_t *container, char *buf);
+
+int array_container_network_write(const array_container_t *container,
+				  int (*write_fn) (void *, const void *, size_t),
+				  void *data);
 /**
  * Reads the instance from buf, outputs how many bytes were read.
  * This is meant to be byte-by-byte compatible with the Java and Go versions of
@@ -1801,6 +1805,9 @@ int32_t array_container_write(const array_container_t *container, char *buf);
 int32_t array_container_read(int32_t cardinality, array_container_t *container,
                              const char *buf);
 
+int32_t array_container_network_read(int32_t cardinality, array_container_t *container,
+                        	     const char *buf);
+
 /**
  * Return the serialized size in bytes of a container (see
  * bitset_container_write)
@@ -2506,6 +2513,10 @@ bool bitset_container_iterate64(const bitset_container_t *cont, uint32_t base,
  */
 int32_t bitset_container_write(const bitset_container_t *container, char *buf);
 
+int bitset_container_network_write(const bitset_container_t *container,
+				   int (*write_fn) (void *, const void *, size_t),
+				   void *data);
+
 /**
  * Reads the instance from buf, outputs how many bytes were read.
  * This is meant to be byte-by-byte compatible with the Java and Go versions of
@@ -2515,6 +2526,9 @@ int32_t bitset_container_write(const bitset_container_t *container, char *buf);
  */
 int32_t bitset_container_read(int32_t cardinality,
                               bitset_container_t *container, const char *buf);
+
+int32_t bitset_container_network_read(int32_t cardinality, bitset_container_t *container,
+				      const char *buf);
 /**
  * Return the serialized size in bytes of a container (see
  * bitset_container_write).
@@ -3029,6 +3043,10 @@ bool run_container_iterate64(const run_container_t *cont, uint32_t base,
  */
 int32_t run_container_write(const run_container_t *container, char *buf);
 
+int run_container_network_write(const run_container_t *container,
+				int (*write_fn) (void *, const void*, size_t),
+				void *data);
+
 /**
  * Reads the instance from buf, outputs how many bytes were read.
  * This is meant to be byte-by-byte compatible with the Java and Go versions of
@@ -3041,6 +3059,9 @@ int32_t run_container_write(const run_container_t *container, char *buf);
 int32_t run_container_read(int32_t cardinality, run_container_t *container,
                            const char *buf);
 
+int32_t run_container_network_read(int32_t cardinality, run_container_t *container,
+                        	   const char *buf);
+
 /**
  * Return the serialized size in bytes of a container (see run_container_write).
  * This is meant to be compatible with the Java and Go versions of Roaring.
@@ -4513,6 +4534,24 @@ static inline int32_t container_write(
     return 0;  // unreached
 }
 
+static int container_network_write(const container_t *c, uint8_t typecode,
+				   int (*write_fn) (void *, const void *, size_t),
+				   void *data)
+{
+	c = container_unwrap_shared(c, &typecode);
+	switch (typecode) {
+		case BITSET_CONTAINER_TYPE:
+			return bitset_container_network_write(const_CAST_bitset(c), write_fn, data);
+		case ARRAY_CONTAINER_TYPE:
+			return array_container_network_write(const_CAST_array(c), write_fn, data);
+		case RUN_CONTAINER_TYPE:
+			return run_container_network_write(const_CAST_run(c), write_fn, data);
+	}
+	assert(false);
+	__builtin_unreachable();
+	return 0;
+}
+
 /**
  * Get the container size in bytes under portable serialization (see
  * container_write), requires a
@@ -6609,6 +6648,7 @@ static inline container_t *container_remove_range(
 #include <assert.h>
 #include <stdbool.h>
 #include <stdint.h>
+#include <arpa/inet.h>
 
 
 #ifdef __cplusplus
@@ -6811,6 +6851,10 @@ bool ra_range_uint32_array(const roaring_array_t *ra, size_t offset, size_t limi
  */
 size_t ra_portable_serialize(const roaring_array_t *ra, char *buf);
 
+int ra_portable_network_serialize(const roaring_array_t *ra,
+				  int (*write_fn) (void *, const void *, size_t),
+				  void *data);
+
 /**
  * read a bitmap from a serialized version. This is meant to be compatible
  * with the Java and Go versions.
@@ -7421,15 +7465,6 @@ void ra_append_range(roaring_array_t *ra, roaring_array_t *sa,
     }
 }
 
-container_t *ra_get_container(
-    roaring_array_t *ra, uint16_t x, uint8_t *typecode
-){
-    int i = binarySearch(ra->keys, (int32_t)ra->size, x);
-    if (i < 0) return NULL;
-    *typecode = ra->typecodes[i];
-    return ra->containers[i];
-}
-
 extern inline container_t *ra_get_container_at_index(
     const roaring_array_t *ra, uint16_t i,
     uint8_t *typecode);
@@ -7670,6 +7705,18 @@ uint32_t ra_portable_header_size(const roaring_array_t *ra) {
     }
 }
 
+static uint32_t ra_portable_network_header_size(const roaring_array_t *ra)
+{
+	if (ra_has_run_container(ra)) {
+		if (ra->size < NO_OFFSET_THRESHOLD) // for small bitmaps, we omit the offsets
+			return 4 + (ra->size + 7) / 8 + 4 * ra->size;
+
+		return 4 + (ra->size + 7) / 8 + 8 * ra->size;
+	} else {
+		return 4 + 8 * ra->size;
+	}
+}
+
 size_t ra_portable_size_in_bytes(const roaring_array_t *ra) {
     size_t count = ra_portable_header_size(ra);
 
@@ -7679,6 +7726,15 @@ size_t ra_portable_size_in_bytes(const roaring_array_t *ra) {
     return count;
 }
 
+static size_t ra_portable_network_size_in_bytes(const roaring_array_t *ra)
+{
+	size_t count = ra_portable_network_header_size(ra);
+
+	for (int32_t k = 0; k < ra->size; ++k)
+		count += container_size_in_bytes(ra->containers[k], ra->typecodes[k]);
+	return count;
+}
+
 size_t ra_portable_serialize(const roaring_array_t *ra, char *buf) {
     char *initbuf = buf;
     uint32_t startOffset = 0;
@@ -7740,6 +7796,80 @@ size_t ra_portable_serialize(const roaring_array_t *ra, char *buf) {
     return buf - initbuf;
 }
 
+int ra_portable_network_serialize(const roaring_array_t *ra,
+				  int (*write_fn) (void *, const void *, size_t),
+				  void *data)
+{
+	uint32_t initial_offset;
+	uint32_t cookie;
+	bool has_run = ra_has_run_container(ra);
+
+	if (has_run) {
+		uint8_t *bitmap_of_run_containers = NULL;
+		size_t bitmap_run_container_size = (ra->size + 7) / 8;
+
+		cookie = htonl(SERIAL_COOKIE | ((ra->size - 1) << 16));
+		if (write_fn(data, &cookie, 4) != 4)
+			return -1;
+		initial_offset = sizeof(cookie);
+
+		bitmap_of_run_containers = (uint8_t *)roaring_calloc(bitmap_run_container_size, 1);
+
+		for (uint32_t i = 0; i < ra->size; i++) {
+			if (get_container_type(ra->containers[i], ra->typecodes[i]) ==
+                	    RUN_CONTAINER_TYPE)
+				bitmap_of_run_containers[i / 8] |= (1 << (i % 8));
+		}
+
+		for (size_t i = 0; i < bitmap_run_container_size; i++) {
+			if (write_fn(data, bitmap_of_run_containers + i, 1) != 1) {
+				free(bitmap_of_run_containers);
+				return -1;
+			}
+		}
+		free(bitmap_of_run_containers);
+		initial_offset += bitmap_run_container_size;
+	} else {
+		cookie = htonl(SERIAL_COOKIE_NO_RUNCONTAINER | (ra->size - 1) << 16);
+		if (write_fn(data, &cookie, 4) != 4)
+			return -1;
+
+		initial_offset = sizeof(cookie);
+	}
+
+	/* description table */
+	for (uint32_t i = 0; i < ra->size; i++) {
+		uint16_t card;
+		uint16_t nt_key = htons(ra->keys[i]);
+
+		if (write_fn(data, &nt_key, sizeof(uint16_t)) != sizeof(uint16_t))
+			return -1;
+
+		card = (uint16_t)(container_get_cardinality(ra->containers[i], ra->typecodes[i]) - 1);
+		card = htons(card);
+		if (write_fn(data, &card, 2) != 2)
+			return -1;
+	}
+	initial_offset += 4 * ra->size;
+
+	if ((!has_run) || (ra->size >= NO_OFFSET_THRESHOLD)) {
+		uint32_t nt_offset;
+		initial_offset += 4 * ra->size;
+		nt_offset = htonl(initial_offset);
+		// writing the containers offsets
+		for (int32_t k = 0; k < ra->size; k++) {
+			if (write_fn(data, &nt_offset, sizeof(int32_t)) != sizeof(int32_t))
+				return -1;
+			initial_offset += container_size_in_bytes(ra->containers[k], ra->typecodes[k]);
+		}
+	}
+	for (int32_t k = 0; k < ra->size; ++k) {
+		container_network_write(ra->containers[k], ra->typecodes[k], write_fn, data);
+	}
+
+	return 0;
+}
+
 // Quickly checks whether there is a serialized bitmap at the pointer,
 // not exceeding size "maxbytes" in bytes. This function does not allocate
 // memory dynamically.
@@ -7827,6 +7957,83 @@ size_t ra_portable_deserialize_size(const char *buf, const size_t maxbytes) {
     return bytestotal;
 }
 
+size_t ra_portable_network_deserialize_size(const char *buf, const size_t maxbytes) {
+	size_t bytestotal = sizeof(int32_t);// for cookie
+	uint32_t cookie;
+	int32_t size;
+	if(bytestotal > maxbytes)
+		return 0;
+	memcpy(&cookie, buf, sizeof(int32_t));
+	cookie = ntohl(cookie);
+	buf += sizeof(uint32_t);
+	if ((cookie & 0xFFFF) != SERIAL_COOKIE &&
+		(cookie & 0xFFFF) != SERIAL_COOKIE_NO_RUNCONTAINER) {
+		return 0;
+	}
+
+	size = (cookie >> 16) + 1;
+	if (size > (1<<16)) {
+		return 0; // logically impossible
+	}
+	else if (size == (1 << 16))
+		return bytestotal;
+	char *bitmapOfRunContainers = NULL;
+	bool hasrun = (cookie & 0xFFFF) == SERIAL_COOKIE;
+	if (hasrun) {
+		int32_t s = (size + 7) / 8;
+		bytestotal += s;
+		if(bytestotal > maxbytes) return 0;
+		bitmapOfRunContainers = (char *)buf;
+		buf += s;
+	}
+	bytestotal += size * 2 * sizeof(uint16_t);
+	if(bytestotal > maxbytes) return 0;
+	uint16_t *keyscards = (uint16_t *)buf;
+	buf += size * 2 * sizeof(uint16_t);
+	if ((!hasrun) || (size >= NO_OFFSET_THRESHOLD)) {
+		// skipping the offsets
+		bytestotal += size * 4;
+		if(bytestotal > maxbytes) return 0;
+		buf += size * 4;
+	}
+	// Reading the containers
+	for (int32_t k = 0; k < size; ++k) {
+		uint16_t tmp;
+		memcpy(&tmp, keyscards + 2*k+1, sizeof(tmp));
+		uint32_t thiscard = ntohs(tmp) + 1;
+		bool isbitmap = (thiscard > DEFAULT_MAX_SIZE);
+		bool isrun = false;
+		if(hasrun && (bitmapOfRunContainers[k / 8] & (1 << (k % 8))) != 0) {
+			isbitmap = false;
+			isrun = true;
+		}
+		if (isbitmap) {
+			size_t containersize = BITSET_CONTAINER_SIZE_IN_WORDS * sizeof(uint64_t);
+			bytestotal += containersize;
+			if(bytestotal > maxbytes)
+				return 0;
+			buf += containersize;
+		} else if (isrun) {
+			bytestotal += sizeof(uint16_t);
+			if(bytestotal > maxbytes) return 0;
+			uint16_t n_runs;
+			memcpy(&n_runs, buf, sizeof(uint16_t));
+			n_runs = ntohs(n_runs);
+			buf += sizeof(uint16_t);
+			size_t containersize = n_runs * sizeof(rle16_t);
+			bytestotal += containersize;
+			if(bytestotal > maxbytes) return 0;
+			buf += containersize;
+		} else {
+			size_t containersize = thiscard * sizeof(uint16_t);
+			bytestotal += containersize;
+			if(bytestotal > maxbytes)
+				return 0;
+			buf += containersize;
+		}
+	}
+	return bytestotal;
+}
 
 // this function populates answer from the content of buf (reading up to maxbytes bytes).
 // The function returns false if a properly serialized bitmap cannot be found.
@@ -8000,6 +8207,177 @@ bool ra_portable_deserialize(roaring_array_t *answer, const char *buf, const siz
     return true;
 }
 
+bool ra_portable_network_deserialize(roaring_array_t *answer, const char *buf, const size_t maxbytes, size_t *readbytes)
+{
+	uint32_t cookie;
+	int32_t size;
+	const char *bitmapOfRunContainers = NULL;
+	int hasrun = 0;
+	uint16_t *keyscards;
+
+	*readbytes = sizeof(int32_t);// for cookie
+	if(*readbytes > maxbytes) {
+	fprintf(stderr, "Ran out of bytes while reading first 4 bytes.\n");
+	return false;
+	}
+	memcpy(&cookie, buf, sizeof(int32_t));
+	cookie = ntohl(cookie);
+	buf += sizeof(uint32_t);
+	if ((cookie & 0xFFFF) != SERIAL_COOKIE &&
+		(cookie & 0xFFFF) != SERIAL_COOKIE_NO_RUNCONTAINER) {
+		fprintf(stderr, "I failed to find one of the right cookies. Found %" PRIu32 "\n",
+			cookie);
+		return false;
+	}
+
+	size = (cookie >> 16) + 1;
+	if (size < 0) {
+		fprintf(stderr, "You cannot have a negative number of containers, the data must be corrupted: %" PRId32 "\n",
+				size);
+		return false; // logically impossible
+	}
+	if (size > (1<<16)) {
+		fprintf(stderr, "You cannot have so many containers, the data must be corrupted: %" PRId32 "\n",
+				size);
+		return false; // logically impossible
+	}
+	else if (size == (1 << 16)) {
+		ra_init_with_capacity(answer, size);
+		return true;
+	}
+	hasrun = (cookie & 0xFFFF) == SERIAL_COOKIE;
+	if (hasrun) {
+		int32_t s = (size + 7) / 8;
+		*readbytes += s;
+		if(*readbytes > maxbytes) {// data is corrupted?
+		fprintf(stderr, "Ran out of bytes while reading run bitmap.\n");
+		return false;
+		}
+		bitmapOfRunContainers = buf;
+		buf += s;
+	}
+	keyscards = (uint16_t *)buf;
+
+	*readbytes += size * 2 * sizeof(uint16_t);
+	if(*readbytes > maxbytes) {
+		fprintf(stderr, "Ran out of bytes while reading key-cardinality array.\n");
+		return false;
+	}
+	buf += size * 2 * sizeof(uint16_t);
+
+	bool is_ok = ra_init_with_capacity(answer, size);
+	if (!is_ok) {
+		fprintf(stderr, "Failed to allocate memory for roaring array. Bailing out.\n");
+		return false;
+	}
+
+	for (int32_t k = 0; k < size; ++k) {
+		uint16_t tmp;
+		memcpy(&tmp, keyscards + 2*k, sizeof(tmp));
+		answer->keys[k] = ntohs(tmp);
+	}
+	if ((!hasrun) || (size >= NO_OFFSET_THRESHOLD)) {
+		*readbytes += size * 4;
+		if(*readbytes > maxbytes) {// data is corrupted?
+			fprintf(stderr, "Ran out of bytes while reading offsets.\n");
+			ra_clear(answer);// we need to clear the containers already allocated, and the roaring array
+			return false;
+		}
+
+		// skipping the offsets
+		buf += size * 4;
+	}
+	// Reading the containers
+	for (int32_t k = 0; k < size; ++k) {
+		uint16_t tmp;
+		uint32_t thiscard;
+		bool isbitmap;
+		bool isrun;
+
+		memcpy(&tmp, keyscards + 2*k+1, sizeof(tmp));
+		thiscard = ntohs(tmp) + 1;
+		isbitmap = (thiscard > DEFAULT_MAX_SIZE);
+		isrun = false;
+		if(hasrun && (bitmapOfRunContainers[k / 8] & (1 << (k % 8))) != 0) {
+			isbitmap = false;
+			isrun = true;
+		}
+		if (isbitmap) {
+			// we check that the read is allowed
+			size_t containersize = BITSET_CONTAINER_SIZE_IN_WORDS * sizeof(uint64_t);
+			*readbytes += containersize;
+			if(*readbytes > maxbytes) {
+				fprintf(stderr, "Running out of bytes while reading a bitset container.\n");
+				ra_clear(answer);// we need to clear the containers already allocated, and the roaring array
+				return false;
+			}
+			// it is now safe to read
+			bitset_container_t *c = bitset_container_create();
+			if(c == NULL) {// memory allocation failure
+				fprintf(stderr, "Failed to allocate memory for a bitset container.\n");
+				ra_clear(answer);// we need to clear the containers already allocated, and the roaring array
+				return false;
+			}
+			answer->size++;
+			buf += bitset_container_network_read(thiscard, c, buf);
+			answer->containers[k] = c;
+			answer->typecodes[k] = BITSET_CONTAINER_TYPE;
+		} else if (isrun) {
+			// we check that the read is allowed
+			*readbytes += sizeof(uint16_t);
+			if(*readbytes > maxbytes) {
+				fprintf(stderr, "Running out of bytes while reading a run container (header).\n");
+				ra_clear(answer);// we need to clear the containers already allocated, and the roaring array
+				return false;
+			}
+			uint16_t n_runs;
+			memcpy(&n_runs, buf, sizeof(uint16_t));
+			n_runs = ntohs(n_runs);
+			size_t containersize = n_runs * sizeof(rle16_t);
+			*readbytes += containersize;
+			if(*readbytes > maxbytes) {// data is corrupted?
+				fprintf(stderr, "Running out of bytes while reading a run container.\n");
+				ra_clear(answer);// we need to clear the containers already allocated, and the roaring array
+				return false;
+			}
+			// it is now safe to read
+
+			run_container_t *c = run_container_create();
+			if(c == NULL) {// memory allocation failure
+				fprintf(stderr, "Failed to allocate memory for a run container.\n");
+				ra_clear(answer);// we need to clear the containers already allocated, and the roaring array
+				return false;
+			}
+			answer->size++;
+			buf += run_container_network_read(thiscard, c, buf);
+			answer->containers[k] = c;
+			answer->typecodes[k] = RUN_CONTAINER_TYPE;
+		} else {
+			// we check that the read is allowed
+			size_t containersize = thiscard * sizeof(uint16_t);
+			*readbytes += containersize;
+			if(*readbytes > maxbytes) {// data is corrupted?
+				fprintf(stderr, "Running out of bytes while reading an array container.\n");
+				ra_clear(answer);// we need to clear the containers already allocated, and the roaring array
+				return false;
+			}
+			// it is now safe to read
+			array_container_t *c =
+				array_container_create_given_capacity(thiscard);
+			if(c == NULL) {// memory allocation failure
+				fprintf(stderr, "Failed to allocate memory for an array container.\n");
+				ra_clear(answer);// we need to clear the containers already allocated, and the roaring array
+				return false;
+			}
+			answer->size++;
+			buf += array_container_network_read(thiscard, c, buf);
+			answer->containers[k] = c;
+			answer->typecodes[k] = ARRAY_CONTAINER_TYPE;
+		}
+	}
+	return true;
+}
+
 #ifdef __cplusplus
 } } }  // extern "C" { namespace roaring { namespace internal {
 #endif
@@ -8603,16 +8981,16 @@ extern inline void roaring_bitmap_remove_range(roaring_bitmap_t *r, uint64_t min
 void roaring_bitmap_printf(const roaring_bitmap_t *r) {
     const roaring_array_t *ra = &r->high_low_container;
 
-    printf("{");
+    fprintf(stderr, "{");
     for (int i = 0; i < ra->size; ++i) {
         container_printf_as_uint32_array(ra->containers[i], ra->typecodes[i],
                                          ((uint32_t)ra->keys[i]) << 16);
 
         if (i + 1 < ra->size) {
-            printf(",");
+            fprintf(stderr, ",");
         }
     }
-    printf("}");
+    fprintf(stderr, "}");
 }
 
 void roaring_bitmap_printf_describe(const roaring_bitmap_t *r) {
@@ -8736,6 +9114,14 @@ void roaring_bitmap_free(const roaring_bitmap_t *r) {
     roaring_free((roaring_bitmap_t*)r);
 }
 
+void roaring_bitmap_free_safe(roaring_bitmap_t **r)
+{
+	if (*r) {
+		roaring_bitmap_free((const roaring_bitmap_t *)*r);
+		r = NULL;
+	}
+}
+
 void roaring_bitmap_clear(roaring_bitmap_t *r) {
   ra_reset(&r->high_low_container);
 }
@@ -9700,6 +10086,11 @@ size_t roaring_bitmap_portable_size_in_bytes(const roaring_bitmap_t *r) {
     return ra_portable_size_in_bytes(&r->high_low_container);
 }
 
+size_t roaring_bitmap_network_portable_size_in_bytes(const roaring_bitmap_t *r)
+{
+	return ra_portable_network_size_in_bytes(&r->high_low_container);
+}
+
 
 roaring_bitmap_t *roaring_bitmap_portable_deserialize_safe(const char *buf, size_t maxbytes) {
     roaring_bitmap_t *ans =
@@ -9718,21 +10109,50 @@ roaring_bitmap_t *roaring_bitmap_portable_deserialize_safe(const char *buf, size
     return ans;
 }
 
+roaring_bitmap_t *roaring_bitmap_portable_network_deserialize_safe(const char *buf, size_t maxbytes)
+{
+	roaring_bitmap_t *ans =
+		(roaring_bitmap_t *)roaring_malloc(sizeof(roaring_bitmap_t));
+	if (ans == NULL) {
+		return NULL;
+	}
+	size_t bytesread;
+	bool is_ok = ra_portable_network_deserialize(&ans->high_low_container, buf, maxbytes, &bytesread);
+	if(is_ok) assert(bytesread <= maxbytes);
+	roaring_bitmap_set_copy_on_write(ans, false);
+	if (!is_ok) {
+		roaring_free(ans);
+		return NULL;
+	}
+	return ans;
+}
+
 roaring_bitmap_t *roaring_bitmap_portable_deserialize(const char *buf) {
     return roaring_bitmap_portable_deserialize_safe(buf, SIZE_MAX);
 }
 
-
 size_t roaring_bitmap_portable_deserialize_size(const char *buf, size_t maxbytes) {
   return ra_portable_deserialize_size(buf, maxbytes);
 }
 
+size_t roaring_bitmap_portable_network_deserialize_size(const char *buf, size_t maxbytes) {
+	size_t size = ra_portable_network_deserialize_size(buf, maxbytes);
+	return size;
+}
+
 
 size_t roaring_bitmap_portable_serialize(const roaring_bitmap_t *r,
                                          char *buf) {
     return ra_portable_serialize(&r->high_low_container, buf);
 }
 
+int roaring_bitmap_portable_network_serialize(roaring_bitmap_t *rb,
+				     int (*write_fn) (void *, const void *, size_t),
+				     void *data)
+{
+	return ra_portable_network_serialize(&rb->high_low_container, write_fn, data);
+}
+
 roaring_bitmap_t *roaring_bitmap_deserialize(const void *buf) {
     const char *bufaschar = (const char *)buf;
     if (*(const unsigned char *)buf == CROARING_SERIALIZATION_ARRAY_UINT32) {
@@ -13827,9 +14247,9 @@ void array_container_printf_as_uint32_array(const array_container_t *v,
     if (v->cardinality == 0) {
         return;
     }
-    printf("%u", v->array[0] + base);
+    fprintf(stderr, "%u", v->array[0] + base);
     for (int i = 1; i < v->cardinality; ++i) {
-        printf(",%u", v->array[i] + base);
+        fprintf(stderr, ",%u", v->array[i] + base);
     }
 }
 
@@ -13856,6 +14276,20 @@ int32_t array_container_write(const array_container_t *container, char *buf) {
     return array_container_size_in_bytes(container);
 }
 
+int array_container_network_write(const array_container_t *container,
+				  int (*write_fn) (void *, const void *, size_t),
+				  void *data)
+{
+	int32_t i;
+	size_t size = container->cardinality * sizeof(uint16_t);
+	for (i = 0; i < container->cardinality; i++) {
+		uint16_t nt_elem = htons(container->array[i]);
+		if (write_fn(data, &nt_elem, sizeof(uint16_t)) != sizeof(uint16_t))
+			return -1;
+	}
+	return 0;
+}
+
 bool array_container_is_subset(const array_container_t *container1,
                                const array_container_t *container2) {
     if (container1->cardinality > container2->cardinality) {
@@ -13890,6 +14324,23 @@ int32_t array_container_read(int32_t cardinality, array_container_t *container,
     return array_container_size_in_bytes(container);
 }
 
+int32_t array_container_network_read(int32_t cardinality, array_container_t *container,
+                        	     const char *buf)
+{
+	uint32_t i;
+	if (container->capacity < cardinality) {
+		array_container_grow(container, cardinality, false);
+	}
+	container->cardinality = cardinality;
+	for (i = 0; i < container->cardinality; i++) {
+		uint16_t val;
+		memcpy(&val, buf + i * sizeof(uint16_t), sizeof(uint16_t));
+		val = ntohs(val);
+		container->array[i] = val;
+	}
+	return array_container_size_in_bytes(container);
+}
+
 bool array_container_iterate(const array_container_t *cont, uint32_t base,
                              roaring_iterator iterator, void *ptr) {
     for (int i = 0; i < cont->cardinality; i++)
@@ -15208,13 +15659,13 @@ void run_container_printf_as_uint32_array(const run_container_t *cont,
     {
         uint32_t run_start = base + cont->runs[0].value;
         uint16_t le = cont->runs[0].length;
-        printf("%u", run_start);
-        for (uint32_t j = 1; j <= le; ++j) printf(",%u", run_start + j);
+        fprintf(stderr, "%u", run_start);
+        for (uint32_t j = 1; j <= le; ++j) fprintf(stderr, ",%u", run_start + j);
     }
     for (int32_t i = 1; i < cont->n_runs; ++i) {
         uint32_t run_start = base + cont->runs[i].value;
         uint16_t le = cont->runs[i].length;
-        for (uint32_t j = 0; j <= le; ++j) printf(",%u", run_start + j);
+        for (uint32_t j = 0; j <= le; ++j) fprintf(stderr, ",%u", run_start + j);
     }
 }
 
@@ -15225,6 +15676,28 @@ int32_t run_container_write(const run_container_t *container, char *buf) {
     return run_container_size_in_bytes(container);
 }
 
+int run_container_network_write(const run_container_t *container,
+				int (*write_fn) (void *, const void *, size_t),
+				void *data)
+{
+	uint16_t i;
+	int32_t nt_nruns = htonl(container->n_runs);
+	if (write_fn(data, &nt_nruns, sizeof(uint16_t)))
+		return -1;
+
+	for (i = 0; i < container->n_runs; i++) {
+		rle16_t run = container->runs[i];
+		uint16_t nt_value = htons(run.value);
+		uint16_t nt_len = htons(run.length);
+		if (write_fn(data, &nt_value, sizeof(uint16_t)) != sizeof(uint16_t))
+			return -1;
+		if (write_fn(data, &nt_len, sizeof(uint16_t)) != sizeof(uint16_t))
+			return -1;
+	}
+
+	return 0;
+}
+
 int32_t run_container_read(int32_t cardinality, run_container_t *container,
                            const char *buf) {
     (void)cardinality;
@@ -15238,6 +15711,29 @@ int32_t run_container_read(int32_t cardinality, run_container_t *container,
     return run_container_size_in_bytes(container);
 }
 
+int32_t run_container_network_read(int32_t cardinality, run_container_t *container,
+                        	   const char *buf)
+{
+	int32_t n_runs;
+	memcpy(&n_runs, buf, sizeof(uint16_t));
+	n_runs = ntohs(n_runs);
+	container->n_runs = n_runs;
+	if (container->n_runs > container->capacity)
+        run_container_grow(container, container->n_runs, false);
+	if(container->n_runs > 0) {
+		uint32_t i;
+
+		for (i = 0; i < container->n_runs; i++) {
+			rle16_t run;
+			memcpy(&run, buf + sizeof(uint16_t) + i * sizeof(rle16_t), sizeof(rle16_t));
+			run.length = ntohs(run.length);
+			run.value = ntohs(run.value);
+			container->runs[i] = run;
+		}
+	}
+	return run_container_size_in_bytes(container);
+}
+
 bool run_container_iterate(const run_container_t *cont, uint32_t base,
                            roaring_iterator iterator, void *ptr) {
     for (int i = 0; i < cont->n_runs; ++i) {
@@ -17417,10 +17913,10 @@ void bitset_container_printf_as_uint32_array(const bitset_container_t * v, uint3
 			uint64_t t = w & (~w + 1);
 			int r = __builtin_ctzll(w);
 			if(iamfirst) {// predicted to be false
-				printf("%u", r + base);
+				fprintf(stderr, "%u", r + base);
 				iamfirst = false;
 			} else {
-				printf(",%u",r + base);
+				fprintf(stderr, ",%u",r + base);
 			}
 			w ^= t;
 		}
@@ -17454,6 +17950,18 @@ int32_t bitset_container_write(const bitset_container_t *container,
 	return bitset_container_size_in_bytes(container);
 }
 
+int bitset_container_network_write(const bitset_container_t *container,
+				   int (*write_fn) (void *, const void *, size_t),
+				   void *data)
+{
+	uint32_t i = 0;
+	for (i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; i++) {
+		uint64_t nt_word = htonll(container->words[i]);
+		if (write_fn(data, &nt_word, sizeof(uint64_t)) != sizeof(uint64_t))
+			return -1;
+	}
+	return 0;
+}
 
 int32_t bitset_container_read(int32_t cardinality, bitset_container_t *container,
 		const char *buf)  {
@@ -17462,6 +17970,23 @@ int32_t bitset_container_read(int32_t cardinality, bitset_container_t *container
 	return bitset_container_size_in_bytes(container);
 }
 
+int32_t bitset_container_network_read(int32_t cardinality, bitset_container_t *container,
+				      const char *buf)
+{
+	uint32_t i = 0;
+	const char *mbuf = buf;
+	container->cardinality = cardinality;
+
+	for (i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; i++) {
+		uint64_t nt_word;
+		memcpy(&nt_word, mbuf, sizeof(uint64_t));
+		mbuf += sizeof(uint64_t);
+
+		container->words[i] = ntohll(nt_word);
+	}
+	return bitset_container_size_in_bytes(container);
+}
+
 bool bitset_container_iterate(const bitset_container_t *cont, uint32_t base, roaring_iterator iterator, void *ptr) {
   for (int32_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; ++i ) {
     uint64_t w = cont->words[i];
roaring/roaring.h +17 -0
index bd5e0a0fe1c..84489eaa260 100644
@@ -409,6 +409,11 @@ void roaring_bitmap_andnot_inplace(roaring_bitmap_t *r1,
  */
 void roaring_bitmap_free(const roaring_bitmap_t *r);
 
+/**
+ * Frees the memory if exists
+ */
+void roaring_bitmap_free_safe(roaring_bitmap_t **r);
+
 /**
  * Add value n_args from pointer vals, faster than repeatedly calling
  * `roaring_bitmap_add()`
@@ -605,6 +610,9 @@ roaring_bitmap_t *roaring_bitmap_portable_deserialize(const char *buf);
 roaring_bitmap_t *roaring_bitmap_portable_deserialize_safe(const char *buf,
                                                            size_t maxbytes);
 
+roaring_bitmap_t *roaring_bitmap_portable_network_deserialize_safe(const char *buf,
+								   size_t maxbytes);
+
 /**
  * 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.
@@ -615,6 +623,9 @@ roaring_bitmap_t *roaring_bitmap_portable_deserialize_safe(const char *buf,
 size_t roaring_bitmap_portable_deserialize_size(const char *buf,
                                                 size_t maxbytes);
 
+size_t roaring_bitmap_portable_network_deserialize_size(const char *buf,
+							size_t maxbytes);
+
 /**
  * How many bytes are required to serialize this bitmap.
  *
@@ -623,6 +634,8 @@ size_t roaring_bitmap_portable_deserialize_size(const char *buf,
  */
 size_t roaring_bitmap_portable_size_in_bytes(const roaring_bitmap_t *r);
 
+size_t roaring_bitmap_network_portable_size_in_bytes(const roaring_bitmap_t *r);
+
 /**
  * Write a bitmap to a char buffer.  The output buffer should refer to at least
  * `roaring_bitmap_portable_size_in_bytes(r)` bytes of allocated memory.
@@ -635,6 +648,10 @@ size_t roaring_bitmap_portable_size_in_bytes(const roaring_bitmap_t *r);
  */
 size_t roaring_bitmap_portable_serialize(const roaring_bitmap_t *r, char *buf);
 
+int roaring_bitmap_portable_network_serialize(roaring_bitmap_t *rb,
+				     int (*write_fn) (void *, const void *, size_t),
+				     void *data);
+
 /*
  * "Frozen" serialization format imitates memory layout of roaring_bitmap_t.
  * Deserialized bitmap is a constant view of the underlying buffer.
Signature · 1 line
gitgitgadget
#9 lore raw

PATCH 3/5 roaring: teach Git to write roaring bitmaps

In reply to Abhradeep Chakraborty via GitGitGadget

From: Abhradeep Chakraborty <[email protected]>

Roaring bitmaps are said to be more efficient (most of the time) than ewah bitmaps. So Git might gain some optimization if it support roaring bitmaps. As Roaring library has all the changes it needed to implement roaring bitmaps in Git, Git can learn to write roaring bitmaps. However, all the changes are backward-compatible.

Teach Git to write roaring bitmaps.

---
 Makefile                |   1 +
 bitmap.c                | 225 +++++++++++++++++++++++++++
 bitmap.h                |  33 ++++
 builtin/diff.c          |  10 +-
 ewah/bitmap.c           |  61 +++++---
 ewah/ewok.h             |  37 ++---
 pack-bitmap-write.c     | 326 ++++++++++++++++++++++++++++++----------
 pack-bitmap.c           | 114 +++++++-------
 pack-bitmap.h           |  22 ++-
 t/t5310-pack-bitmaps.sh |  17 +++
 10 files changed, 664 insertions(+), 182 deletions(-)
 create mode 100644 bitmap.c
 create mode 100644 bitmap.h
Makefile +1 -0
index e9537951105..9ca19b3ca8d 100644
@@ -900,6 +900,7 @@ LIB_OBJS += archive.o
 LIB_OBJS += attr.o
 LIB_OBJS += base85.o
 LIB_OBJS += bisect.o
+LIB_OBJS += bitmap.o
 LIB_OBJS += blame.o
 LIB_OBJS += blob.o
 LIB_OBJS += bloom.o
bitmap.c new file +225 -0

226 lines not on this page · raw message

bitmap.h new file +33 -0
new file mode 100644
index 00000000000..d75400922cc
@@ -0,0 +1,33 @@
+#ifndef __BITMAP_H__
+#define __BITMAP_H__
+
+
+#include "git-compat-util.h"
+#include "ewah/ewok.h"
+#include "roaring/roaring.h"
+
+enum bitmap_type {
+	INIT_BITMAP_TYPE = 0,
+	EWAH,
+	ROARING
+};
+
+enum bitmap_type get_bitmap_type(void);
+void set_bitmap_type(enum bitmap_type type);
+void *roaring_or_ewah_bitmap_init(void);
+void *roaring_or_raw_bitmap_new(void);
+void *roaring_or_raw_bitmap_copy(void *bitmap);
+int roaring_or_ewah_bitmap_set(void *bitmap, uint32_t i);
+void roaring_or_raw_bitmap_set(void *bitmap, uint32_t i);
+int roaring_or_raw_bitmap_get(void *bitmap, uint32_t pos);
+int roaring_or_raw_bitmap_is_subset(void *a, void *b);
+void roaring_or_raw_bitmap_or(void *self, void *other);
+void roaring_or_raw_bitmap_free(void *bitmap);
+void roaring_or_raw_bitmap_free_safe(void **bitmap);
+void roaring_or_raw_bitmap_unset(void *bitmap, uint32_t i);
+void roaring_or_raw_bitmap_printf(void *a);
+int roaring_or_raw_bitmap_equals(void *a, void *b);
+void roaring_or_raw_bitmap_and_not(void *self, void *other);
+size_t roaring_or_raw_bitmap_cardinality(void *bitmap);
+
+#endif
builtin/diff.c +5 -5
index 54bb3de964c..8cf7481d325 100644
@@ -353,8 +353,8 @@ static void symdiff_prepare(struct rev_info *rev, struct symdiff *sym)
 			continue;
 		}
 		if (!map)
-			map = bitmap_new();
-		bitmap_set(map, i);
+			map = raw_bitmap_new();
+		raw_bitmap_set(map, i);
 	}
 
 	/*
@@ -364,7 +364,7 @@ static void symdiff_prepare(struct rev_info *rev, struct symdiff *sym)
 		usage(builtin_diff_usage);
 
 	if (!is_symdiff) {
-		bitmap_free(map);
+		raw_bitmap_free(map);
 		sym->warn = 0;
 		sym->skip = NULL;
 		return;
@@ -375,7 +375,7 @@ static void symdiff_prepare(struct rev_info *rev, struct symdiff *sym)
 	if (basecount == 0)
 		die(_("%s...%s: no merge base"), sym->left, sym->right);
 	sym->base = rev->pending.objects[basepos].name;
-	bitmap_unset(map, basepos);	/* unmark the base we want */
+	raw_bitmap_unset(map, basepos);	/* unmark the base we want */
 	sym->warn = basecount > 1;
 	sym->skip = map;
 }
@@ -539,7 +539,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
 			obj = &get_commit_tree(((struct commit *)obj))->object;
 
 		if (obj->type == OBJ_TREE) {
-			if (sdiff.skip && bitmap_get(sdiff.skip, i))
+			if (sdiff.skip && raw_bitmap_get(sdiff.skip, i))
 				continue;
 			obj->flags |= flags;
 			add_object_array(obj, name, &ent);
ewah/bitmap.c +43 -18

150 lines not on this page · raw message

ewah/ewok.h +19 -18
index 7eb8b9b6301..4fc96fd73d0 100644
@@ -171,23 +171,24 @@ struct bitmap {
 	size_t word_alloc;
 };
 
-struct bitmap *bitmap_new(void);
-struct bitmap *bitmap_word_alloc(size_t word_alloc);
-struct bitmap *bitmap_dup(const struct bitmap *src);
-void bitmap_set(struct bitmap *self, size_t pos);
-void bitmap_unset(struct bitmap *self, size_t pos);
-int bitmap_get(struct bitmap *self, size_t pos);
-void bitmap_free(struct bitmap *self);
-int bitmap_equals(struct bitmap *self, struct bitmap *other);
-int bitmap_is_subset(struct bitmap *self, struct bitmap *other);
-
-struct ewah_bitmap * bitmap_to_ewah(struct bitmap *bitmap);
-struct bitmap *ewah_to_bitmap(struct ewah_bitmap *ewah);
-
-void bitmap_and_not(struct bitmap *self, struct bitmap *other);
-void bitmap_or_ewah(struct bitmap *self, struct ewah_bitmap *other);
-void bitmap_or(struct bitmap *self, const struct bitmap *other);
-
-size_t bitmap_popcount(struct bitmap *self);
+struct bitmap *raw_bitmap_new(void);
+struct bitmap *raw_bitmap_word_alloc(size_t word_alloc);
+struct bitmap *raw_bitmap_dup(const struct bitmap *src);
+void raw_bitmap_set(struct bitmap *self, size_t pos);
+void raw_bitmap_unset(struct bitmap *self, size_t pos);
+int raw_bitmap_get(struct bitmap *self, size_t pos);
+void raw_bitmap_free(struct bitmap *self);
+int raw_bitmap_equals(struct bitmap *self, struct bitmap *other);
+int raw_bitmap_is_subset(struct bitmap *self, struct bitmap *other);
+
+struct ewah_bitmap * raw_bitmap_to_ewah(struct bitmap *bitmap);
+struct bitmap *ewah_to_raw_bitmap(struct ewah_bitmap *ewah);
+
+void raw_bitmap_and_not(struct bitmap *self, struct bitmap *other);
+void raw_bitmap_or_ewah(struct bitmap *self, struct ewah_bitmap *other);
+void raw_bitmap_or(struct bitmap *self, const struct bitmap *other);
+void ewah_bitmap_print(struct ewah_bitmap *bm);
+
+size_t raw_bitmap_popcount(struct bitmap *self);
 
 #endif
pack-bitmap-write.c +247 -79

562 lines not on this page · raw message

pack-bitmap.c +57 -57

323 lines not on this page · raw message

pack-bitmap.h +17 -5
index f0180b5276b..7d71deca023 100644
@@ -2,11 +2,18 @@
 #define PACK_BITMAP_H
 
 #include "ewah/ewok.h"
+#include "roaring/roaring.h"
+#include "bitmap.h"
 #include "khash.h"
 #include "pack.h"
 #include "pack-objects.h"
 #include "string-list.h"
 
+#define BITMAP_TYPE_INDEXES 0x54494458 /* "TIDX" */
+#define BITMAP_REACHABILITY_BITMAPS 0x5242544D /* "RBTM" */
+#define BITMAP_HASH_CACHE 0x424D4843 /* "BMHC" */
+#define BITMAP_LOOKUP_TABLE 0x424D4C54 /* "BMLT" */
+
 struct commit;
 struct repository;
 struct rev_info;
@@ -23,6 +30,9 @@ struct bitmap_disk_header {
 
 #define NEEDS_BITMAP (1u<<22)
 
+#define BITMAP_SET_EWAH_BITMAP 0x1
+#define BITMAP_SET_ROARING_BITMAP (1 << 1)
+
 /*
  * The width in bytes of a single triplet in the lookup table
  * extension:
@@ -86,16 +96,18 @@ off_t get_disk_usage_from_bitmap(struct bitmap_index *, struct rev_info *);
 
 void bitmap_writer_show_progress(int show);
 void bitmap_writer_set_checksum(const unsigned char *sha1);
+void bitmap_writer_init_bm_type(unsigned version_type);
 void bitmap_writer_build_type_index(struct packing_data *to_pack,
 				    struct pack_idx_entry **index,
 				    uint32_t index_nr);
 uint32_t *create_bitmap_mapping(struct bitmap_index *bitmap_git,
 				struct packing_data *mapping);
-int rebuild_bitmap(const uint32_t *reposition,
-		   struct ewah_bitmap *source,
-		   struct bitmap *dest);
-struct ewah_bitmap *bitmap_for_commit(struct bitmap_index *bitmap_git,
-				      struct commit *commit);
+int rebuild_bitmap(struct bitmap_index *bitmap_git,
+		   const uint32_t *reposition,
+		   void *source,
+		   void *dest);
+void *bitmap_for_commit(struct bitmap_index *bitmap_git,
+			struct commit *commit);
 void bitmap_writer_select_commits(struct commit **indexed_commits,
 		unsigned int indexed_commits_nr, int max_bitmaps);
 int bitmap_writer_build(struct packing_data *to_pack);
t/t5310-pack-bitmaps.sh +17 -0
index 7e50f8e7653..d953de6b7fe 100755
@@ -475,4 +475,21 @@ test_expect_success 'truncated bitmap fails gracefully (lookup table)' '
 	test_i18ngrep corrupted.bitmap.index stderr
 '
 
+test_expect_success 'setup test repository (roaring)' '
+	rm -fr * .git &&
+	git init
+'
+setup_bitmap_history
+
+test_expect_success 'setup writing roaring bitmaps during repack' '
+	git config repack.writeBitmaps true &&
+	git config pack.useRoaringBitmap true
+'
+
+test_expect_success 'full repack creates roaring bitmaps' '
+	GIT_TRACE2_EVENT="$(pwd)/trace6" \
+		git repack -ad &&
+	grep "\"label\":\"write-roaring-bitmap\"" trace6
+'
+
 test_done
Signature · 1 line
gitgitgadget
#20 lore raw

PATCH 5/5 roaring: teach Git to read roaring bitmaps

In reply to Abhradeep Chakraborty via GitGitGadget

From: Abhradeep Chakraborty <[email protected]>

Git knows how to write roaring bitmaps but it still doesn't know how to read roaring bitmaps. The changes are backward-compatible.

Teach Git to read roaring bitmaps.

---
 builtin/pack-objects.c  |  74 ++-
 pack-bitmap.c           | 969 ++++++++++++++++++++++++++++------------
 pack-bitmap.h           |   4 +-
 t/t5310-pack-bitmaps.sh |  84 ++--
 4 files changed, 798 insertions(+), 333 deletions(-)
builtin/pack-objects.c +55 -19
index 439c5572c18..e4011669889 100644
@@ -218,7 +218,7 @@ static struct progress *progress_state;
 
 static struct packed_git *reuse_packfile;
 static uint32_t reuse_packfile_objects;
-static struct bitmap *reuse_packfile_bitmap;
+static void *reuse_packfile_bitmap;
 
 static int use_bitmap_index_default = 1;
 static int use_bitmap_index = -1;
@@ -1084,15 +1084,29 @@ static size_t write_reused_pack_verbatim(struct hashfile *out,
 					 struct pack_window **w_curs)
 {
 	size_t pos = 0;
-
-	while (pos < reuse_packfile_bitmap->word_alloc &&
-			reuse_packfile_bitmap->words[pos] == (eword_t)~0)
-		pos++;
+	enum bitmap_type bm_type = get_bitmap_type();
+	trace2_region_enter("pack-objects", "write-reuse-v2-pack", the_repository);
+
+	if (bm_type == EWAH) {
+		struct bitmap *raw_reuse_packfile_bitmap = reuse_packfile_bitmap;
+		while (pos < raw_reuse_packfile_bitmap->word_alloc &&
+				raw_reuse_packfile_bitmap->words[pos] == (eword_t)~0)
+			pos++;
+		written = (pos * BITS_IN_EWORD);
+	}
+	else if (bm_type == ROARING) {
+		uint32_t cardinality = roaring_bitmap_get_cardinality(reuse_packfile_bitmap);
+		while (pos < cardinality && roaring_bitmap_contains(reuse_packfile_bitmap, pos))
+			pos++;
+		written = pos;
+	}
+	else
+		die(_("bitmap type is not initialized\n"));
+	trace2_region_leave("pack-objects", "write-reuse-v2-pack", the_repository);
 
 	if (pos) {
 		off_t to_write;
 
-		written = (pos * BITS_IN_EWORD);
 		to_write = pack_pos_to_offset(reuse_packfile, written)
 			- sizeof(struct pack_header);
 
@@ -1112,30 +1126,49 @@ static void write_reused_pack(struct hashfile *f)
 	size_t i = 0;
 	uint32_t offset;
 	struct pack_window *w_curs = NULL;
+	enum bitmap_type bm_type = get_bitmap_type();
+	trace2_region_enter("pack-objects", "write-reused-pack", the_repository);
 
 	if (allow_ofs_delta)
 		i = write_reused_pack_verbatim(f, &w_curs);
 
-	for (; i < reuse_packfile_bitmap->word_alloc; ++i) {
-		eword_t word = reuse_packfile_bitmap->words[i];
-		size_t pos = (i * BITS_IN_EWORD);
+	if (bm_type == EWAH) {
+		struct bitmap *raw_reuse_packfile_bitmap = reuse_packfile_bitmap;
+		for (; i < raw_reuse_packfile_bitmap->word_alloc; ++i) {
+			eword_t word = raw_reuse_packfile_bitmap->words[i];
+			size_t pos = (i * BITS_IN_EWORD);
 
-		for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
-			if ((word >> offset) == 0)
-				break;
+			for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
+				if ((word >> offset) == 0)
+					break;
 
-			offset += ewah_bit_ctz64(word >> offset);
-			/*
-			 * Can use bit positions directly, even for MIDX
-			 * bitmaps. See comment in try_partial_reuse()
-			 * for why.
-			 */
-			write_reused_pack_one(pos + offset, f, &w_curs);
+				offset += ewah_bit_ctz64(word >> offset);
+				/*
+				* Can use bit positions directly, even for MIDX
+				* bitmaps. See comment in try_partial_reuse()
+				* for why.
+				*/
+				write_reused_pack_one(pos + offset, f, &w_curs);
+				display_progress(progress_state, ++written);
+			}
+		}
+	}
+	else if (bm_type == ROARING) {
+		uint32_t cardinality = roaring_bitmap_get_cardinality(reuse_packfile_bitmap);
+		uint32_t *bm_arr = NULL;
+		roaring_bitmap_to_uint32_array(reuse_packfile_bitmap, bm_arr);
+
+		for (; i < cardinality; ++i) {
+			write_reused_pack_one(bm_arr[i], f, &w_curs);
 			display_progress(progress_state, ++written);
 		}
+		free(bm_arr);
 	}
+	else
+		die(_("bitmap_type not initialized"));
 
 	unuse_pack(&w_curs);
+	trace2_region_leave("pack-objects", "write-reused-pack", the_repository);
 }
 
 static void write_excluded_by_configs(void)
@@ -1260,6 +1293,7 @@ static void write_pack_file(void)
 			if (write_bitmap_index) {
 				bitmap_writer_init_bm_type(write_bitmap_options);
 				bitmap_writer_set_checksum(hash);
+				fprintf(stderr, "hi man\n");
 				bitmap_writer_build_type_index(
 					&to_pack, written_list, nr_written);
 			}
@@ -1279,9 +1313,11 @@ static void write_pack_file(void)
 				stop_progress(&progress_state);
 
 				bitmap_writer_show_progress(progress);
+				fprintf(stderr, "hello I am working good\n");
 				bitmap_writer_select_commits(indexed_commits, indexed_commits_nr, -1);
 				if (bitmap_writer_build(&to_pack) < 0)
 					die(_("failed to write bitmap index"));
+				fprintf(stderr, "after building bitmaps\n");
 				bitmap_writer_finish(written_list, nr_written,
 						     tmpname.buf, write_bitmap_options);
 				write_bitmap_index = 0;
pack-bitmap.c +691 -278

1,486 lines not on this page · raw message

pack-bitmap.h +2 -2
index 6103e0d57e7..e9676ec53de 100644
@@ -77,12 +77,12 @@ uint32_t midx_preferred_pack(struct bitmap_index *bitmap_git);
 int reuse_partial_packfile_from_bitmap(struct bitmap_index *,
 				       struct packed_git **packfile,
 				       uint32_t *entries,
-				       struct bitmap **reuse_out);
+				       void**reuse_out);
 int rebuild_existing_bitmaps(struct bitmap_index *, struct packing_data *mapping,
 			     kh_oid_map_t *reused_bitmaps, int show_progress);
 void free_bitmap_index(struct bitmap_index *);
 int bitmap_walk_contains(struct bitmap_index *,
-			 struct bitmap *bitmap, const struct object_id *oid);
+			 void *bitmap, const struct object_id *oid);
 
 /*
  * After a traversal has been performed by prepare_bitmap_walk(), this can be
t/t5310-pack-bitmaps.sh +50 -34

140 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#21 lore raw
In reply to Abhradeep Chakraborty via GitGitGadget
Quoted 22 lines · Abhradeep Chakraborty via GitGitGadget

Git currently uses ewah bitmaps ( which are based on run-length encoding) to compress bitmaps. Ewah bitmaps stores bitmaps in the form of run-length words i.e. instead of storing each and every bit, it tries to find consecutive bits (having same value) and replace them with the value bit and the range upto which the bit is present. It is simple and efficient. But one downside of this approach is that we have to decompress the whole bitmap in order to find the bit of a certain position.

For small (or medium sized) bitmaps, this is not an issue. But it can be an issue for large (or extra large) bitmaps. In that case roaring bitmaps are generally more efficient[1] than ewah itself. Some benchmarks suggests that roaring bitmaps give more performance benefits than ewah or any other similar compression technique.

This patch series is currently in RFC state and it aims to let Git use roaring bitmaps. As this is an RFC patch series (for now), the code are not fully accurate (i.e. some tests are failing). But it is backward-compatible (tests related to ewah bitmaps are passing). Some commit messages might need more explanation and some commits may need a split (specially the one that implement writing roaring bitmaps). Overall, the structure and code are near to ready to make the series a formal patch series.

Quoted 3 lines

I am submitting it as an RFC (after discussions with mentors) because the GSoC coding period is about to end. I will continue to work on the patch series.

I look forward to your next version. I hope to see some information about the performance characteristics across the two versions. Specifically:

1. How do various test in t/perf/ change between the two formats?
2. For certain test repos (git/git, torvalds/linux, etc.) how much does the .bitmap file change in size across the formats?

Quoted 3 lines
 Makefile                   |     3 +
 bitmap.c                   |   225 +
 bitmap.h                   |    33 +

...

Quoted 2 lines
 ewah/bitmap.c              |    61 +-
 ewah/ewok.h                |    37 +-

...

Quoted 2 lines
 roaring/roaring.c          | 20047 +++++++++++++++++++++++++++++++++++
 roaring/roaring.h          |  1028 ++

I wonder if there is value in modifying the structure of these files into a bitmap/ directory and then perhaps ewah/ and roaring/ within each? Just a thought.

Thanks,
-Stolee

#3 lore raw

Re: [PATCH 2/5] roaring.[ch]: apply Git specific changes to the roaring API

In reply to Abhradeep Chakraborty via GitGitGadget
Quoted 6 lines · Abhradeep Chakraborty via GitGitGadget

From: Abhradeep Chakraborty <[email protected]>

Though the Roaring library is introduced in previous commit, the library cannot be used as is. One reason is that the library doesn't support Big endian machines. Besides, Git specific file related functions does use hashwrite() (or similar). So there is a need to modify the library.

There are a few refactorings happening in this single patch, so it might be good to split them out for easier spot-checking from the reviewer's perspective. I'll try to list the ones I see.

roaring/roaring.c bool array_container_iterate64(const array_container_t *cont, uint32_t base, · line 1797

Quoted 5 lines

int32_t array_container_write(const array_container_t *container, char *buf);

roaring/roaring.c +4 -0
+
+int array_container_network_write(const array_container_t *container,
+				  int (*write_fn) (void *, const void *, size_t),
+				  void *data);

Should we make write_fn a defined type? I'm not sure I've seen this implicit type within a function declaration before.

roaring/roaring.c int32_t array_container_write(const array_container_t *container, char *buf); · line 1810

Quoted 10 lines

/**
* Reads the instance from buf, outputs how many bytes were read.
* This is meant to be byte-by-byte compatible with the Java and Go versions of

roaring/roaring.c +3 -0
@@ -1801,6 +1805,9 @@ int32_t array_container_write(const array_container_t *container, char *buf);
 int32_t array_container_read(int32_t cardinality, array_container_t *container,
                              const char *buf);
 
+int32_t array_container_network_read(int32_t cardinality, array_container_t *container,
+                        	     const char *buf);
+

Both of these functions are creating new implementations instead of modifying the existing implementations. Is there any reason why we should keep both of these in perpetuity? They are likely to drift if we do that.

Quoted 8 lines
roaring/roaring.c +8 -0
+static int container_network_write(const container_t *c, uint8_t typecode,
+				   int (*write_fn) (void *, const void *, size_t),
+				   void *data)
+{
+	c = container_unwrap_shared(c, &typecode);
+	switch (typecode) {
+		case BITSET_CONTAINER_TYPE:
+			return bitset_container_network_write(const_CAST_bitset(c), write_fn, data);

roaring/roaring.c static inline int32_t container_write( · line 4554

Quoted 10 lines
roaring/roaring.c +10 -0
+		case ARRAY_CONTAINER_TYPE:
+			return array_container_network_write(const_CAST_array(c), write_fn, data);
+		case RUN_CONTAINER_TYPE:
+			return run_container_network_write(const_CAST_run(c), write_fn, data);
+	}
+	assert(false);
+	__builtin_unreachable();
+	return 0;
+}
+

This similarly is a copy of an existing function. Instead we should probably make all writers/readers expect network byte order (for all multi-word integers).

roaring/roaring.c size_t ra_portable_size_in_bytes(const roaring_array_t *ra) { · line 7733

Quoted 5 lines
roaring/roaring.c +5 -0
+static size_t ra_portable_network_size_in_bytes(const roaring_array_t *ra)
+{
+	size_t count = ra_portable_network_header_size(ra);
+
+	for (int32_t k = 0; k < ra->size; ++k)

We have not loosened the restriction on defining iterator variables within the for and instead would need this in the outer block. One possible refactoring would be to move these definitions everywhere within roaring.c.

Quoted 8 lines
roaring/roaring.c +1 -1
@@ -8603,16 +8981,16 @@ extern inline void roaring_bitmap_remove_range(roaring_bitmap_t *r, uint64_t min
 void roaring_bitmap_printf(const roaring_bitmap_t *r) {
     const roaring_array_t *ra = &r->high_low_container;
 
-    printf("{");
+    fprintf(stderr, "{");
     for (int i = 0; i < ra->size; ++i) {
         container_printf_as_uint32_array(ra->containers[i], ra->typecodes[i],

roaring/roaring.c extern inline void roaring_bitmap_remove_range(roaring_bitmap_t *r, uint64_t min · line 8994

Quoted 10 lines
roaring/roaring.c +2 -2
                                          ((uint32_t)ra->keys[i]) << 16);
 
         if (i + 1 < ra->size) {
-            printf(",");
+            fprintf(stderr, ",");
         }
     }
-    printf("}");
+    fprintf(stderr, "}");
 }

This change is confusing to me. I epxect the printf() to print to stdout, and this might be used in a test helper or something. If you really want this to go somewhere other than stdout, then the method should be changed to take an arbitrary FILE*.

roaring/roaring.c void roaring_bitmap_free(const roaring_bitmap_t *r) { · line 9121

Quoted 5 lines
roaring/roaring.c +5 -0
+void roaring_bitmap_free_safe(roaring_bitmap_t **r)
+{
+	if (*r) {
+		roaring_bitmap_free((const roaring_bitmap_t *)*r);
+		r = NULL;

I think you want "*r = NULL" here, if you are intending to free and NULL the given address.

This method seems separate from the network-byte-order changes.

roaring/roaring.c roaring_bitmap_t *roaring_bitmap_portable_deserialize_safe(const char *buf, size · line 10154

Quoted 8 lines
roaring/roaring.c +7 -0
+	}
+}
+
 
+size_t roaring_bitmap_network_portable_size_in_bytes(const roaring_bitmap_t *r)
+{
+	return ra_portable_network_size_in_bytes(&r->high_low_container);
+}

Does network order change the potential size of the bitmap?

roaring/roaring.c roaring_bitmap_t *roaring_bitmap_portable_deserialize_safe(const char *buf, size · line 10118

Quoted 7 lines
roaring/roaring.c +7 -0
+roaring_bitmap_t *roaring_bitmap_portable_network_deserialize_safe(const char *buf, size_t maxbytes)
+{
+	roaring_bitmap_t *ans =
+		(roaring_bitmap_t *)roaring_malloc(sizeof(roaring_bitmap_t));
+	if (ans == NULL) {
+		return NULL;
+	}

nit: Lose braces around single-line blocks.

roaring/roaring.c roaring_bitmap_t *roaring_bitmap_portable_deserialize_safe(const char *buf, size · line 10120

Quoted 2 lines
roaring/roaring.c +2 -0
+	size_t bytesread;
+	bool is_ok = ra_portable_network_deserialize(&ans->high_low_container, buf, maxbytes, &bytesread);

Declare all variables before your logic. I think this will fail if you run "make DEVELOPER=1".

roaring/roaring.c roaring_bitmap_t *roaring_bitmap_portable_deserialize_safe(const char *buf, size · line 10121

Quoted 1 line
roaring/roaring.c +1 -0
+	if(is_ok) assert(bytesread <= maxbytes);

nit: break lines for if bodies.

Quoted 10 lines
roaring/roaring.c +8 -0
+	roaring_bitmap_set_copy_on_write(ans, false);
+	if (!is_ok) {
+		roaring_free(ans);
+		return NULL;
+	}
+	return ans;
+}
+
 
 size_t roaring_bitmap_portable_serialize(const roaring_bitmap_t *r,

roaring/roaring.c roaring_bitmap_t *roaring_bitmap_portable_deserialize_safe(const char *buf, size · line 10154

Quoted 10 lines
roaring/roaring.c +6 -0
                                          char *buf) {
     return ra_portable_serialize(&r->high_low_container, buf);
 }
 
+int roaring_bitmap_portable_network_serialize(roaring_bitmap_t *rb,
+				     int (*write_fn) (void *, const void *, size_t),
+				     void *data)
+{
+	return ra_portable_network_serialize(&rb->high_low_container, write_fn, data);
+}

I'm not sure why these methods are created as wrappers instead of renaming the base methods.

roaring/roaring.c void array_container_printf_as_uint32_array(const array_container_t *v, · line 14252

Quoted 12 lines
 roaring_bitmap_t *roaring_bitmap_deserialize(const void *buf) {
     const char *bufaschar = (const char *)buf;
     if (*(const unsigned char *)buf == CROARING_SERIALIZATION_ARRAY_UINT32) {
roaring/roaring.c +2 -2
@@ -13827,9 +14247,9 @@ void array_container_printf_as_uint32_array(const array_container_t *v,
     if (v->cardinality == 0) {
         return;
     }
-    printf("%u", v->array[0] + base);
+    fprintf(stderr, "%u", v->array[0] + base);
     for (int i = 1; i < v->cardinality; ++i) {
-        printf(",%u", v->array[i] + base);
+        fprintf(stderr, ",%u", v->array[i] + base);

Here's another printf to fprintf situation that is unclear to me.

roaring/roaring.c void run_container_printf_as_uint32_array(const run_container_t *cont, · line 15663

Quoted 8 lines
roaring/roaring.c +2 -2
@@ -15208,13 +15659,13 @@ void run_container_printf_as_uint32_array(const run_container_t *cont,
     {
         uint32_t run_start = base + cont->runs[0].value;
         uint16_t le = cont->runs[0].length;
-        printf("%u", run_start);
-        for (uint32_t j = 1; j <= le; ++j) printf(",%u", run_start + j);
+        fprintf(stderr, "%u", run_start);
+        for (uint32_t j = 1; j <= le; ++j) fprintf(stderr, ",%u", run_start + j);

Ditto here. I see we are inheriting off-style code from the original.

roaring/roaring.h void roaring_bitmap_andnot_inplace(roaring_bitmap_t *r1, · line 415

Quoted 4 lines
roaring/roaring.h +4 -0
+/**
+ * Frees the memory if exists
+ */
+void roaring_bitmap_free_safe(roaring_bitmap_t **r);

And nullifies the pointer, don't forget!

In general, I think this change would be a lot smaller if you took the existing implementation and inserted the proper ntohl() and htonl() conversions. Git will never call the other versions, so why keep them in the tree? Why require re-checking all of the format logic here instead of only the places where we write multi-byte words?

Thanks,
-Stolee

#4 lore raw

Re: [PATCH 2/5] roaring.[ch]: apply Git specific changes to the roaring API

Quoted 8 lines · Derrick Stolee

roaring/roaring.c bool array_container_iterate64(const array_container_t *cont, uint32_t base, · line 1797

Quoted 5 lines

int32_t array_container_write(const array_container_t *container, char *buf);

roaring/roaring.c +4 -0
+
+int array_container_network_write(const array_container_t *container,
+				  int (*write_fn) (void *, const void *, size_t),
+				  void *data);

Should we make write_fn a defined type? I'm not sure I've seen this implicit type within a function declaration before.

Unless we can point out why having a named type is a good idea (e.g. we add such a function pointer as a member of a struct, or we keep a variable of that type somewhere), I actually would prefer to do without them.

Perhaps there are some more important reasons I am missing why we often come up with explicit types for callback function pointers in many parts of our API, but if there aren't, my preference actually is to lose them, not add more of them.

Hmph.... could "a typedef can become a place to give definitive documentation for the class of callback functions" be a good reason why we would want one? I dunno.

In the posted patch, readers cannot tell what kind of three parameters they are supposed to give to write_fn().

Thanks.

#5 lore raw

Re: [PATCH 2/5] roaring.[ch]: apply Git specific changes to the roaring API

Quoted 21 lines · Junio C Hamano
Quoted 8 lines · Derrick Stolee

roaring/roaring.c bool array_container_iterate64(const array_container_t *cont, uint32_t base, · line 1797

Quoted 5 lines

int32_t array_container_write(const array_container_t *container, char *buf);

roaring/roaring.c +4 -0
+
+int array_container_network_write(const array_container_t *container,
+				  int (*write_fn) (void *, const void *, size_t),
+				  void *data);

Should we make write_fn a defined type? I'm not sure I've seen this implicit type within a function declaration before.

Unless we can point out why having a named type is a good idea (e.g. we add such a function pointer as a member of a struct, or we keep a variable of that type somewhere), I actually would prefer to do without them.

Perhaps there are some more important reasons I am missing why we often come up with explicit types for callback function pointers in many parts of our API, but if there aren't, my preference actually is to lose them, not add more of them.

Quoted 6 lines

Hmph.... could "a typedef can become a place to give definitive documentation for the class of callback functions" be a good reason why we would want one? I dunno.

In the posted patch, readers cannot tell what kind of three parameters they are supposed to give to write_fn().

This is exactly my reasoning. Having a clear definition gives us an opportunity to document what each parameter is for, even if it is just a variable name.

This anonymous type is used in multiple places, so it can be helpful to know that the type is connected across call sites or a stack of method calls.

In the unlikely event that we needed to modify this callback signature, changing it in one place makes it clear that we cover all connected uses instead of tracking all of these anonymous functions across multiple methods.

Thanks,
-Stolee

#22 lore raw
In reply to Derrick Stolee

Hi Derrick,

Quoted 6 lines · Derrick Stolee

I look forward to your next version. I hope to see some information about the performance characteristics across the two versions. Specifically:

1. How do various test in t/perf/ change between the two formats?
2. For certain test repos (git/git, torvalds/linux, etc.) how much does the .bitmap file change in size across the formats?

Yeah, sure. I will be including the performance test result in the next version :)

Quoted 6 lines
Quoted 3 lines
 Makefile                   |     3 +
 bitmap.c                   |   225 +
 bitmap.h                   |    33 +

...

Quoted 2 lines
 ewah/bitmap.c              |    61 +-
 ewah/ewok.h                |    37 +-
Quoted 7 lines

...

Quoted 2 lines
 roaring/roaring.c          | 20047 +++++++++++++++++++++++++++++++++++
 roaring/roaring.h          |  1028 ++

I wonder if there is value in modifying the structure of these files into a bitmap/ directory and then perhaps ewah/ and roaring/ within each? Just a thought.

Great idea! Thanks! Will change it in the next version..

Thanks :)

#8 lore raw

Re: [PATCH 2/5] roaring.[ch]: apply Git specific changes to the roaring API

In reply to Derrick Stolee
Quoted 4 lines · Derrick Stolee
Quoted 2 lines · Abhradeep Chakraborty via GitGitGadget

From: Abhradeep Chakraborty <[email protected]>

Quoted 8 lines
Quoted 4 lines

Though the Roaring library is introduced in previous commit, the library cannot be used as is. One reason is that the library doesn't support Big endian machines. Besides, Git specific file related functions does use hashwrite() (or similar). So there is a need to modify the library.

There are a few refactorings happening in this single patch, so it might be good to split them out for easier spot-checking from the reviewer's perspective. I'll try to list the ones I see.

True, I will split this commit into two or three parts (as I mentioned in the cover letter). I forgot to commit changes one by one while implementing this part. That's why all changes are packed in one commit.

Quoted 8 lines

roaring/roaring.c bool array_container_iterate64(const array_container_t *cont, uint32_t base, · line 1797

Quoted 5 lines

int32_t array_container_write(const array_container_t *container, char *buf);

roaring/roaring.c +4 -0
+
+int array_container_network_write(const array_container_t *container,
+                               int (*write_fn) (void *, const void *, size_t),
+                               void *data);

Should we make write_fn a defined type? I'm not sure I've seen this implicit type within a function declaration before.

I am not sure about that. This function is highly inspired by ewah's ewah_serialize_to function which also has the same kind of declaration. I have no problem if we make write_fn a defined type though.

Quoted 7 lines
Quoted 7 lines

/**
* Reads the instance from buf, outputs how many bytes were read.
* This is meant to be byte-by-byte compatible with the Java and Go versions of

roaring/roaring.c +0 -0
@@ -1801,6 +1805,9 @@ int32_t array_container_write(const array_container_t *container, char *buf);
 int32_t array_container_read(int32_t cardinality, array_container_t *container,
                              const char *buf);
 
Quoted 8 lines

roaring/roaring.c int32_t array_container_write(const array_container_t *container, char *buf); · line 1810

Quoted 3 lines
roaring/roaring.c +3 -0
+int32_t array_container_network_read(int32_t cardinality, array_container_t *container,
+                                  const char *buf);
+

Both of these functions are creating new implementations instead of modifying the existing implementations. Is there any reason why we should keep both of these in perpetuity? They are likely to drift if we do that.

No, there is no reason behind this. I thought it might be a good idea to have the existing implementations. But it's just my thought.

Quoted 14 lines
Quoted 14 lines
roaring/roaring.c +14 -0
+static int container_network_write(const container_t *c, uint8_t typecode,
+                                int (*write_fn) (void *, const void *, size_t),
+                                void *data)
+{
+     c = container_unwrap_shared(c, &typecode);
+     switch (typecode) {
+             case BITSET_CONTAINER_TYPE:
+                     return bitset_container_network_write(const_CAST_bitset(c), write_fn, data);
+             case ARRAY_CONTAINER_TYPE:
+                     return array_container_network_write(const_CAST_array(c), write_fn, data);
+             case RUN_CONTAINER_TYPE:
+                     return run_container_network_write(const_CAST_run(c), write_fn, data);
+     }
+     assert(false);
Quoted 8 lines

roaring/roaring.c static inline int32_t container_write( · line 4554

Quoted 4 lines
roaring/roaring.c +4 -0
+     __builtin_unreachable();
+     return 0;
+}
+

This similarly is a copy of an existing function. Instead we should probably make all writers/readers expect network byte order (for all multi-word integers).

Ok, sure.

Quoted 10 lines

roaring/roaring.c size_t ra_portable_size_in_bytes(const roaring_array_t *ra) { · line 7733

Quoted 5 lines
roaring/roaring.c +5 -0
+static size_t ra_portable_network_size_in_bytes(const roaring_array_t *ra)
+{
+     size_t count = ra_portable_network_header_size(ra);
+
+     for (int32_t k = 0; k < ra->size; ++k)

We have not loosened the restriction on defining iterator variables within the for and instead would need this in the outer block. One possible refactoring would be to move these definitions everywhere within roaring.c.

The problem I faced with roaring.c is that it doesn't follow any kind of style convention. E.g. in many functions, variables are declared in random positions (instead of initial lines). This is causing errors like "forbids mixed declarations and code", "git log --check failed" etc.

Quoted 10 lines
Quoted 10 lines
roaring/roaring.c +1 -1
@@ -8603,16 +8981,16 @@ extern inline void roaring_bitmap_remove_range(roaring_bitmap_t *r, uint64_t min
 void roaring_bitmap_printf(const roaring_bitmap_t *r) {
     const roaring_array_t *ra = &r->high_low_container;
 
-    printf("{");
+    fprintf(stderr, "{");
     for (int i = 0; i < ra->size; ++i) {
         container_printf_as_uint32_array(ra->containers[i], ra->typecodes[i],
                                          ((uint32_t)ra->keys[i]) << 16);
 
Quoted 13 lines

roaring/roaring.c extern inline void roaring_bitmap_remove_range(roaring_bitmap_t *r, uint64_t min · line 8994

Quoted 8 lines
roaring/roaring.c +2 -2
         if (i + 1 < ra->size) {
-            printf(",");
+            fprintf(stderr, ",");
         }
     }
-    printf("}");
+    fprintf(stderr, "}");
 }

This change is confusing to me. I epxect the printf() to print to stdout, and this might be used in a test helper or something. If you really want this to go somewhere other than stdout, then the method should be changed to take an arbitrary FILE*.

I think it's better to undo the changes. I was using it for debugging.

Quoted 9 lines

roaring/roaring.c void roaring_bitmap_free(const roaring_bitmap_t *r) { · line 9121

Quoted 6 lines
roaring/roaring.c +5 -0
+void roaring_bitmap_free_safe(roaring_bitmap_t **r)
+{
+     if (*r) {
+             roaring_bitmap_free((const roaring_bitmap_t *)*r);
+             r = NULL;

I think you want "*r = NULL" here, if you are intending to free and NULL the given address.

Thanks for pointing this out!

Quoted 1 line

This method seems separate from the network-byte-order changes.

Yeah, I will split them in the next version.

Quoted 6 lines

roaring/roaring.c size_t roaring_bitmap_portable_size_in_bytes(const roaring_bitmap_t *r) { · line 10092

Quoted 4 lines
roaring/roaring.c +4 -0
+size_t roaring_bitmap_network_portable_size_in_bytes(const roaring_bitmap_t *r)
+{
+     return ra_portable_network_size_in_bytes(&r->high_low_container);
+}

Does network order change the potential size of the bitmap?

Yeah, network order bitmap size is 4 byte shorter than its non-network ordered bitmap counterpart.

Quoted 10 lines

roaring/roaring.c roaring_bitmap_t *roaring_bitmap_portable_deserialize_safe(const char *buf, size · line 10120

Quoted 3 lines
roaring/roaring.c +2 -0
+     size_t bytesread;
+     bool is_ok = ra_portable_network_deserialize(&ans->high_low_container, buf, maxbytes, &bytesread);

Declare all variables before your logic. I think this will fail if you run "make DEVELOPER=1".

Quoted 1 line

+ if(is_ok) assert(bytesread <= maxbytes);

nit: break lines for if bodies.

I copied it from the original one and as I said before the coding styles are bad. Anyways, I will modify the original ones.

Quoted 7 lines

roaring/roaring.h void roaring_bitmap_andnot_inplace(roaring_bitmap_t *r1, · line 415

Quoted 5 lines
roaring/roaring.h +4 -0
+/**
+ * Frees the memory if exists
+ */
+void roaring_bitmap_free_safe(roaring_bitmap_t **r);

And nullifies the pointer, don't forget!

Oh, thanks!

Quoted 6 lines

In general, I think this change would be a lot smaller if you took the existing implementation and inserted the proper ntohl() and htonl() conversions. Git will never call the other versions, so why keep them in the tree? Why require re-checking all of the format logic here instead of only the places where we write multi-byte words?

Got it. Thanks :)

#6 lore raw

Re: [PATCH 2/5] roaring.[ch]: apply Git specific changes to the roaring API

In reply to Derrick Stolee
Quoted 27 lines · Derrick Stolee
Quoted 25 lines · Junio C Hamano
Quoted 8 lines · Derrick Stolee

roaring/roaring.c bool array_container_iterate64(const array_container_t *cont, uint32_t base, · line 1797

Quoted 5 lines

int32_t array_container_write(const array_container_t *container, char *buf);

roaring/roaring.c +4 -0
+
+int array_container_network_write(const array_container_t *container,
+                             int (*write_fn) (void *, const void *, size_t),
+                             void *data);

Should we make write_fn a defined type? I'm not sure I've seen this implicit type within a function declaration before.

Unless we can point out why having a named type is a good idea (e.g. we add such a function pointer as a member of a struct, or we keep a variable of that type somewhere), I actually would prefer to do without them.

Perhaps there are some more important reasons I am missing why we often come up with explicit types for callback function pointers in many parts of our API, but if there aren't, my preference actually is to lose them, not add more of them.

Hmph.... could "a typedef can become a place to give definitive documentation for the class of callback functions" be a good reason why we would want one? I dunno.

Quoted 6 lines
Quoted 2 lines

In the posted patch, readers cannot tell what kind of three parameters they are supposed to give to write_fn().

This is exactly my reasoning. Having a clear definition gives us an opportunity to document what each parameter is for, even if it is just a variable name.

Agreed.

Quoted 8 lines

This anonymous type is used in multiple places, so it can be helpful to know that the type is connected across call sites or a stack of method calls.

In the unlikely event that we needed to modify this callback signature, changing it in one place makes it clear that we cover all connected uses instead of tracking all of these anonymous functions across multiple methods.

Got it. Thanks!

#23 lore raw
In reply to Abhradeep Chakraborty via GitGitGadget
Quoted 7 lines · Abhradeep Chakraborty via GitGitGadget

This patch series is currently in RFC state and it aims to let Git use roaring bitmaps. As this is an RFC patch series (for now), the code are not fully accurate (i.e. some tests are failing). But it is backward-compatible (tests related to ewah bitmaps are passing). Some commit messages might need more explanation and some commits may need a split (specially the one that implement writing roaring bitmaps). Overall, the structure and code are near to ready to make the series a formal patch series.

Extremely exciting. Congratulations on all of your work so far. I'm hopeful that you'll continue working on this after GSoC is over (for those playing along at home, Abhradeep's coding period was extended by a couple of weeks).

But even if you don't, this is a great artifact to leave around on the list for somebody else who is interested in this area to pick up in the future, and benefit from all of the work that you've done so far.

I am still working through my post-Git Merge backlog, but I'm looking forward to reading these patches soon. I'm glad that other reviewers have already started to dive in :-).

Well done!

Thanks,
Taylor

#24 lore raw
Quoted 10 lines · Taylor Blau
Quoted 7 lines · Abhradeep Chakraborty via GitGitGadget

This patch series is currently in RFC state and it aims to let Git use roaring bitmaps. As this is an RFC patch series (for now), the code are not fully accurate (i.e. some tests are failing). But it is backward-compatible (tests related to ewah bitmaps are passing). Some commit messages might need more explanation and some commits may need a split (specially the one that implement writing roaring bitmaps). Overall, the structure and code are near to ready to make the series a formal patch series.

Quoted 4 lines

Extremely exciting. Congratulations on all of your work so far. I'm hopeful that you'll continue working on this after GSoC is over (for those playing along at home, Abhradeep's coding period was extended by a couple of weeks).

Yeah, I will continue (or better to say I am continuing) my work. I hope that I can submit the next version in the upcoming few days.

Thanks for supporting and guiding me throughout the GSoC period. I have learned a lot of new things during this period.

Quoted 3 lines

I am still working through my post-Git Merge backlog, but I'm looking forward to reading these patches soon. I'm glad that other reviewers have already started to dive in :-).

No problem, I am doing some improvements by this time.
By the way, I am very excited to see the Youtube Git-Merge recordings ;)

Thanks :)

#7 lore raw

Re: [PATCH 2/5] roaring.[ch]: apply Git specific changes to the roaring API

In reply to Derrick Stolee
Quoted 10 lines · Derrick Stolee
Quoted 6 lines

Hmph.... could "a typedef can become a place to give definitive documentation for the class of callback functions" be a good reason why we would want one? I dunno.

In the posted patch, readers cannot tell what kind of three parameters they are supposed to give to write_fn().

This is exactly my reasoning. Having a clear definition gives us an opportunity to document what each parameter is for, even if it is just a variable name.

Yeah, I completely agree with you on that line of reasoning.

Quoted 4 lines

In the unlikely event that we needed to modify this callback signature, changing it in one place makes it clear that we cover all connected uses instead of tracking all of these anonymous functions across multiple methods.

Well, the compiler will help flagging a caller that forgot to convert, even if there is no typedef.

The parameter list may not have to be updated for a function that takes a callback function of this type as its parameter if you did not use a typedef, but where it in turn makes a call to that callback function, the compiler will notice the argument mismatch, which you have to adjust to the new calling convention anyway.

What is somewhat sad is that even with typedef, the implementation of a callback function cannot use the type, but the longhand that underlies the typedef, to define it, but that is not something we can fix, or are interested in fixing ;-).

Thanks.

#10 lore raw

Re: [PATCH 3/5] roaring: teach Git to write roaring bitmaps

In reply to Abhradeep Chakraborty via GitGitGadget

"Abhradeep Chakraborty via GitGitGadget" <[email protected]>

Quoted 9 lines · writes:

From: Abhradeep Chakraborty <[email protected]>

Roaring bitmaps are said to be more efficient (most of the time) than ewah bitmaps. So Git might gain some optimization if it support roaring bitmaps. As Roaring library has all the changes it needed to implement roaring bitmaps in Git, Git can learn to write roaring bitmaps. However, all the changes are backward-compatible.

Teach Git to write roaring bitmaps.

That is way underexplained. At least cover what the plans are, so
that readers do not have to ask these questions:

* When is the choice of bitmap type is made? Is it fixed at repository initialization time and once chosen other kinds cannot be used?

* Is the bitmap file self describing? How does a reader know between ewah and roaring codepaths to use to read a given bitmap file? Is there enough room for extending the set of bitmap formats, or we cannot add other formats easily?

Quoted 36 lines
---
 Makefile                |   1 +
 bitmap.c                | 225 +++++++++++++++++++++++++++
 bitmap.h                |  33 ++++
 builtin/diff.c          |  10 +-
 ewah/bitmap.c           |  61 +++++---
 ewah/ewok.h             |  37 ++---
 pack-bitmap-write.c     | 326 ++++++++++++++++++++++++++++++----------
 pack-bitmap.c           | 114 +++++++-------
 pack-bitmap.h           |  22 ++-
 t/t5310-pack-bitmaps.sh |  17 +++
 10 files changed, 664 insertions(+), 182 deletions(-)
 create mode 100644 bitmap.c
 create mode 100644 bitmap.h
Makefile +1 -0
index e9537951105..9ca19b3ca8d 100644
@@ -900,6 +900,7 @@ LIB_OBJS += archive.o
 LIB_OBJS += attr.o
 LIB_OBJS += base85.o
 LIB_OBJS += bisect.o
+LIB_OBJS += bitmap.o
 LIB_OBJS += blame.o
 LIB_OBJS += blob.o
 LIB_OBJS += bloom.o
bitmap.c new file +0 -0
new file mode 100644
index 00000000000..7d547eb9f53

bitmap.c line 4

Quoted 4 lines
bitmap.c new file +4 -0
@@ -0,0 +1,225 @@
+#include "bitmap.h"
+#include "cache.h"
+
+static enum bitmap_type bitmap_type = INIT_BITMAP_TYPE;

"INIT" is a strange name for "UNINITIALIZED". Especially ...

bitmap.c line 19

Quoted 4 lines
bitmap.c new file +4 -0
+void *roaring_or_ewah_bitmap_init(void)
+{
+	switch (bitmap_type)
+	{

(Style)

bitmap.c line 24

Quoted 5 lines
bitmap.c new file +5 -0
+	case EWAH:
+		return ewah_new();
+	case ROARING:
+		return roaring_bitmap_create();
+	default:

... here, you use it to mean exactly that.

bitmap.c line 15

Quoted 2 lines
bitmap.c new file +2 -0
+		error(_("bitmap type not initialized\n"));
+		return NULL;

Do you really need the global variable that holds the bitmap type?

Wouldn't it be easier to write code that needs to deal with both types (e.g. in a repository with existing ewah bitmap, you want to do a repack and index the result using the roaring bitmap) if you passed the type through the callchain as a parameter?

It may be that the codepath that reads from an existing bitmap file says "ah, the file given to us seems to be in format X (either EWAH or ROARING or perhaps something else), so let's call bitmap_init(X) to obtain the in-core data structure to deal with that file". When that happens, you may probably need to have two cases in the default:
arm of this switch statement, i.e. one to diagnose a BUG() to pass an uninitialized bitmap type to the codepath, and the other to diagnose a runtime error() to have read a bitmap file whose format this version of Git does not understand.

Quoted 13 lines
bitmap.c new file +12 -0
+void *roaring_or_raw_bitmap_copy(void *bitmap)
+{
+	switch (bitmap_type)
+	{
+	case EWAH:
...
+int roaring_or_ewah_bitmap_set(void *bitmap, uint32_t i)
+{
+	switch (bitmap_type) {
+	case EWAH:
+...
+void roaring_or_raw_bitmap_set(void *bitmap, uint32_t i)
+{

bitmap.c

Quoted 10 lines
bitmap.c new file +10 -0
+	switch (bitmap_type)
+	{
+	case EWAH:
+...
+void roaring_or_raw_bitmap_unset(void *bitmap, uint32_t i)
+{
+	switch (bitmap_type)
+	{
+	case EWAH:
+...

These repetitive patterns makes me wonder if void *bitmap is a good type to be passing around. Shouldn't it be a struct with its first member being a bitmap_type, and another member being what these functions are passing to the underlying bitmap format specific functions as "bitmap"? E.g.

    void bitmap_unset(struct bitmap *bm, uint32_t i)
    {
	switch (bm->type) {
	case EWAH:
		ewah_bitmap_remove(bm->u.ewah, i);
		break;
	...
Quoted 1 line

\ No newline at end of file

Careful.

Quoted 6 lines
bitmap.h new file +0 -0
new file mode 100644
index 00000000000..d75400922cc

bitmap.h line 10

Quoted 10 lines
bitmap.h new file +10 -0
@@ -0,0 +1,33 @@
+#ifndef __BITMAP_H__
+#define __BITMAP_H__
+
+
+#include "git-compat-util.h"
+#include "ewah/ewok.h"
+#include "roaring/roaring.h"
+
+enum bitmap_type {
+	INIT_BITMAP_TYPE = 0,

"UNINITIALIZED_BITMAP_TYPE", probably.

bitmap.h line 17

Quoted 1 line
bitmap.h new file +1 -0
+void *roaring_or_ewah_bitmap_init(void);

I would strongly suggest reconsider these names. What if you later want to add the third variant? roaring_or_ewah_or_xyzzy_bitmap_init()?

Instead just use the most generic name, like "bitmap_init", perhaps something along the lines of ...

    struct bitmap {
	enum bitmap_type type;
	union {
	    struct ewah_bitmap *ewah;
	    struct roaring_bitmap *roaring;
	} u;	
    };

    struct bitmap *bitmap_new(enum bitmap_type type)
    {
	struct bitmap *bm = xmalloc(sizeof(*bm));

	bm->type = type;
	switch (bm->type) {
	case EWAH:
	    bm->u.ewah = ewah_new();
	    break;
	case ROARING:
	    bm->u.roaring = roaring_bitmap_create();
	    break;
        default:
	    die(_("unknown bitmap type %d"), (int)type);
	}
	return bm;
    }

I dunno.

#11 lore raw

Re: [PATCH 3/5] roaring: teach Git to write roaring bitmaps

Quoted 17 lines · Junio C Hamano

"Abhradeep Chakraborty via GitGitGadget" <[email protected]>

Quoted 9 lines · writes:

From: Abhradeep Chakraborty <[email protected]>

Roaring bitmaps are said to be more efficient (most of the time) than ewah bitmaps. So Git might gain some optimization if it support roaring bitmaps. As Roaring library has all the changes it needed to implement roaring bitmaps in Git, Git can learn to write roaring bitmaps. However, all the changes are backward-compatible.

Teach Git to write roaring bitmaps.

That is way underexplained. At least cover what the plans are, so
that readers do not have to ask these questions:

Quoted 8 lines

* When is the choice of bitmap type is made? Is it fixed at repository initialization time and once chosen other kinds cannot be used?

* Is the bitmap file self describing? How does a reader know between ewah and roaring codepaths to use to read a given bitmap file? Is there enough room for extending the set of bitmap formats, or we cannot add other formats easily?

Hey Junio,

First of all, sorry that the next version is taking so much time to land. We have a festival ("Durga Puja"; it is the biggest festival for Bengalis) going on here now. So I am not that active.

I will explain briefly in the next version.

Quoted 7 lines

Do you really need the global variable that holds the bitmap type?

Wouldn't it be easier to write code that needs to deal with both types (e.g. in a repository with existing ewah bitmap, you want to do a repack and index the result using the roaring bitmap) if you passed the type through the callchain as a parameter?

I didn't want to go for "passing the type through the callchain as a parameter" because that would cause changes to every affected function definition. I found the "global variable" approach simpler for this reason. Here we have to initialize the type once and the affected functions will work accordingly.

If you like the "callchain" approach, I have no problem to implement it.

Quoted 9 lines

It may be that the codepath that reads from an existing bitmap file says "ah, the file given to us seems to be in format X (either EWAH or ROARING or perhaps something else), so let's call bitmap_init(X) to obtain the in-core data structure to deal with that file". When that happens, you may probably need to have two cases in the default:
arm of this switch statement, i.e. one to diagnose a BUG() to pass an uninitialized bitmap type to the codepath, and the other to diagnose a runtime error() to have read a bitmap file whose format this version of Git does not understand.

Ok, understood. Thanks.

Quoted 6 lines

These repetitive patterns makes me wonder if void *bitmap is a good type to be passing around. Shouldn't it be a struct with its first member being a bitmap_type, and another member being what these functions are passing to the underlying bitmap format specific functions as "bitmap"? E.g.

Quoted 7 lines
    void bitmap_unset(struct bitmap *bm, uint32_t i)
    {
        switch (bm->type) {
        case EWAH:
                ewah_bitmap_remove(bm->u.ewah, i);
                break;
        ...

Good idea! Thanks.

Quoted 5 lines

bitmap.h line 10

Quoted 3 lines
bitmap.h new file +3 -0
+
+enum bitmap_type {
+     INIT_BITMAP_TYPE = 0,

"UNINITIALIZED_BITMAP_TYPE", probably.

Ok.

Quoted 25 lines
Quoted 1 line

+void *roaring_or_ewah_bitmap_init(void);

I would strongly suggest reconsider these names. What if you later want to add the third variant? roaring_or_ewah_or_xyzzy_bitmap_init()?

Instead just use the most generic name, like "bitmap_init", perhaps something along the lines of ...

    struct bitmap {
        enum bitmap_type type;
        union {
            struct ewah_bitmap *ewah;
            struct roaring_bitmap *roaring;
        } u;
    };

    struct bitmap *bitmap_new(enum bitmap_type type)
    {
        struct bitmap *bm = xmalloc(sizeof(*bm));

        bm->type = type;
        switch (bm->type) {
        case EWAH:
            bm->u.ewah = ewah_new();
            break;
Quoted 8 lines
        case ROARING:
            bm->u.roaring = roaring_bitmap_create();
            break;
        default:
            die(_("unknown bitmap type %d"), (int)type);
        }
        return bm;
    }

Got it. It seems a better option than the current one.

Thanks )

#12 lore raw

Re: [PATCH 3/5] roaring: teach Git to write roaring bitmaps

Hello all,

It has been a month since I didn't get involved in any open source contributions (including Git). This is due to the fact that I was focusing more on mastering theories and also that it was a festive month. So, I am now resuming my work. There are many things I have to cover (including this patch series).
But before that I want to ask you a question - As you have noticed already, the Roaring library has a lot of styling issues (Moreover it is using C11). So Should I fix all these issues? or Should I make a new library (using Git's compatibility library "git-compat-util.h") by taking CRoaring as a reference? The pros are that it would be easier to format the bitmap library specific files and it can use Git compatible functions.

I would love to hear your opinions. Thanks :)

#13 lore raw

Re: [PATCH 3/5] roaring: teach Git to write roaring bitmaps

Quoted 15 lines · Abhradeep Chakraborty

Hello all,

It has been a month since I didn't get involved in any open source contributions (including Git). This is due to the fact that I was focusing more on mastering theories and also that it was a festive month. So, I am now resuming my work. There are many things I have to cover (including this patch series).
But before that I want to ask you a question - As you have noticed already, the Roaring library has a lot of styling issues (Moreover it is using C11). So Should I fix all these issues? or Should I make a new library (using Git's compatibility library "git-compat-util.h") by taking CRoaring as a reference? The pros are that it would be easier to format the bitmap library specific files and it can use Git compatible functions.

Quoted 1 line

I would love to hear your opinions. Thanks :)

I HAVE OPINIONS! :D

Mostly, there are two things I'd like for you to keep in mind:

1. Using the library as-is is a great way to prototype and dig in on the performance measurement side. Can you construct or clone enough interesting repositories to get a feeling of the effect of the roaring format compared to the EWAH format? If there is no benefit to switching, then we can save everyone a lot of work by marking that as an incorrect road. However, if there is sufficient evidence that it's working well, then we have established a baseline that the full implementation should match (at least, if not do better).

2. Once deciding to do the work, we can think about the reasons to use the existing library over writing our own. The most basic reason is that the library is extensively tested, so we gain all of those benefits. Can we incorporate their test suite into our own? The next main benefit is that we can take any changes from their version into our code with minimal fuss. How often do you think that they have bug fixes or enhancements in the repo? How would those changes translate into our mailing list workflow? If we restyled the library, then we are unlikely to get easy benefits from taking upstream changes, but we could recreate them with manual effort.

3. After carefully considering the benefits/drawbacks of using the existing library, consider the same for writing one from scratch. The most important thing I will say here is that the core idea is rather simple. There may even be ways that we can take advantage of the format and its data structures with the expectations we have in Git repositories that are not always possible for generic databases. We should be able to build a much smaller library that's limited to our needs and customized to our use case. However, we would need to test it carefully, both for correctness and for performance, and that is not a small undertaking.

Hopefully this gives you something to chew on. Investigating each of these directions should help you come to a conclusion that you can bring to the community as the expert, then we can examine your findings to see if we agree.

Remember that code speaks. If you're willing to build it one way, then that concrete implementation is already worth more than a hypothetical alternative in many regards. That can be a starting point to move forward.

Thanks,
-Stolee

#14 lore raw

Re: [PATCH 3/5] roaring: teach Git to write roaring bitmaps

Quoted 23 lines · Derrick Stolee
Quoted 15 lines · Abhradeep Chakraborty

Hello all,

It has been a month since I didn't get involved in any open source contributions (including Git). This is due to the fact that I was focusing more on mastering theories and also that it was a festive month. So, I am now resuming my work. There are many things I have to cover (including this patch series).
But before that I want to ask you a question - As you have noticed already, the Roaring library has a lot of styling issues (Moreover it is using C11). So Should I fix all these issues? or Should I make a new library (using Git's compatibility library "git-compat-util.h") by taking CRoaring as a reference? The pros are that it would be easier to format the bitmap library specific files and it can use Git compatible functions.

Quoted 1 line

I would love to hear your opinions. Thanks :)

I HAVE OPINIONS! :D

Mostly, there are two things I'd like for you to keep in mind:

Quoted 8 lines

1. Using the library as-is is a great way to prototype and dig in on the performance measurement side. Can you construct or clone enough interesting repositories to get a feeling of the effect of the roaring format compared to the EWAH format? If there is no benefit to switching, then we can save everyone a lot of work by marking that as an incorrect road. However, if there is sufficient evidence that it's working well, then we have established a baseline that the full implementation should match (at least, if not do better).

Got it. Yeah, I can do it.

Now I am very much clear about how to proceed with it ;-) Thanks for your reply!!

#15 lore raw

Re: [PATCH 3/5] roaring: teach Git to write roaring bitmaps

In reply to Derrick Stolee
Quoted 3 lines · Derrick Stolee

I HAVE OPINIONS! :D

Mostly, there are two things I'd like for you to keep in mind:

Nicely summarised.

Stepping back a bit, we do not care about how the sources to some pieces of software we depend on, say OpenSSL, match our style guide. It is because we do not even have to see them while working on Git, but also because we do not have to maintain it.

So a third-option could be to fill pieces missing from the upstream (e.g. big endian support) and contribute them back, and after that treat them as just one of the external dependencies, just like we happen to have a copy of sha1dc code for convenience but have an option to use the upstream code as a submodule.

Assuming that such a "they are just one of our external dependencies, just like OpenSSL or cURL libraries" happens, I would not worry too much about C11, as long as use of roaring bitmaps can be made an optional feature that can be disabled at compile time. Bitmaps are used only for local optimization and never transferred across repositories, so you having only ewah would not prevent you from talking with other people with both ewah and roaring.

#16 lore raw

C99 -> C11 or C17? (was: [PATCH 3/5] roaring: teach Git to write roaring bitmaps)

Quoted 19 lines · Junio C Hamano
Quoted 3 lines · Derrick Stolee

I HAVE OPINIONS! :D

Mostly, there are two things I'd like for you to keep in mind:

Nicely summarised.

Stepping back a bit, we do not care about how the sources to some pieces of software we depend on, say OpenSSL, match our style guide. It is because we do not even have to see them while working on Git, but also because we do not have to maintain it.

So a third-option could be to fill pieces missing from the upstream (e.g. big endian support) and contribute them back, and after that treat them as just one of the external dependencies, just like we happen to have a copy of sha1dc code for convenience but have an option to use the upstream code as a submodule.

Quoted 7 lines

Assuming that such a "they are just one of our external dependencies, just like OpenSSL or cURL libraries" happens, I would not worry too much about C11, as long as use of roaring bitmaps can be made an optional feature that can be disabled at compile time. Bitmaps are used only for local optimization and never transferred across repositories, so you having only ewah would not prevent you from talking with other people with both ewah and roaring.

As an aside: We might think about just requiring C11 or C17 sooner than later.

For the longest time we couldn't, because of MSVC, but it now supports it.

Per[1] we've now ended up with a bit of an odd scenario, where on MSCV we ask to compile with C11, but everywhere else with C99, even though "everywhere else" is likely to support at least C11 by now.

This is because MSVC doesn't and hasn't ever supported C99, they jumped straight from C89 to C11/C17 (I'm not certain it was in one go, but that's my understanding of [1]).

Of course there may be platforms, compilers etc. that have C99 support, but not C11. So we'd need to tread carefully. I haven't e.g. tested on the usual older RHEL versions we tend to care about.

1. 7bc341e21b5 (git-compat-util: add a test balloon for C99 support, 2021-12-01)

#17 lore raw

RE: C99 -> C11 or C17? (was: [PATCH 3/5] roaring: teach Git to write roaring bitmaps)

Quoted 29 lines · Ævar Arnfjörð Bjarmason
Quoted 19 lines · Junio C Hamano
Quoted 3 lines · Derrick Stolee

I HAVE OPINIONS! :D

Mostly, there are two things I'd like for you to keep in mind:

Nicely summarised.

Stepping back a bit, we do not care about how the sources to some pieces of software we depend on, say OpenSSL, match our style guide. It is because we do not even have to see them while working on Git, but also because we do not have to maintain it.

So a third-option could be to fill pieces missing from the upstream (e.g. big endian support) and contribute them back, and after that treat them as just one of the external dependencies, just like we happen to have a copy of sha1dc code for convenience but have an option to use the upstream code as a submodule.

Quoted 7 lines

Assuming that such a "they are just one of our external dependencies, just like OpenSSL or cURL libraries" happens, I would not worry too much about C11, as long as use of roaring bitmaps can be made an optional feature that can be disabled at compile time. Bitmaps are used only for local optimization and never transferred across repositories, so you having only ewah would not prevent you from talking with other people with both ewah and roaring.

Quoted 1 line

As an aside: We might think about just requiring C11 or C17 sooner than later.

As a request, there is no C11 or C17 on NonStop Itanium, which does not go off support until at least mid-2025. Requiring C11 or C17 will cut git updates off for that platform variant. NonStop x86 does not yet have C17, although it might in future (and I can report when it does - nonetheless, C11 will be required for some supported revisions of the operating system until 2030). Please do not do this.

C99 is the maximum guaranteed version available on all supported NonStop OS versions as of today. Git has thousands of NonStop users who would potentially be impacted and stuck without being able to obtain fixes for CVEs. In my opinion, and for many other Open-Source projects on which I maintain the platform, C11 and C17 do not contain a significant set of constructs that justify moving past C99, but it is obviously the git team's choice. This decision would leave us in the cold and would have to patch C99 code in to make git work.

Sincerely,
Randall

#18 lore raw

Re: [PATCH 3/5] roaring: teach Git to write roaring bitmaps

In reply to Junio C Hamano
Quoted 20 lines · Junio C Hamano
Quoted 3 lines · Derrick Stolee

I HAVE OPINIONS! :D

Mostly, there are two things I'd like for you to keep in mind:

Nicely summarised.

Stepping back a bit, we do not care about how the sources to some pieces of software we depend on, say OpenSSL, match our style guide. It is because we do not even have to see them while working on Git, but also because we do not have to maintain it.

So a third-option could be to fill pieces missing from the upstream (e.g. big endian support) and contribute them back, and after that treat them as just one of the external dependencies, just like we happen to have a copy of sha1dc code for convenience but have an option to use the upstream code as a submodule.

Quoted 7 lines

Assuming that such a "they are just one of our external dependencies, just like OpenSSL or cURL libraries" happens, I would not worry too much about C11, as long as use of roaring bitmaps can be made an optional feature that can be disabled at compile time. Bitmaps are used only for local optimization and never transferred across repositories, so you having only ewah would not prevent you from talking with other people with both ewah and roaring.

Seems a good option to me. Thanks for the info :)