Skip to content

Commit 200a890

Browse files
Merge pull request #2032 from madeline-underwood/bitmap_scanning
Bitmap scanning_JA to review
2 parents 39f8615 + 909d5a0 commit 200a890

8 files changed

Lines changed: 683 additions & 575 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
# User change
3+
title: "Optimize bitmap scanning in databases with SVE and NEON on Arm servers"
4+
5+
weight: 2
6+
7+
layout: "learningpathall"
8+
---
9+
## Overview
10+
11+
Bitmap scanning is a core operation in many database systems. It's essential for powering fast filtering in bitmap indexes, Bloom filters, and column filters. However, these scans can become performance bottlenecks in complex analytical queries.
12+
13+
In this Learning Path, you’ll learn how to accelerate bitmap scanning using Arm’s vector processing technologies - NEON and SVE - on Neoverse V2–based servers like AWS Graviton4.
14+
15+
Specifically, you will:
16+
17+
* Explore how to use SVE instructions on Arm Neoverse V2–based servers like AWS Graviton4 to optimize bitmap scanning
18+
* Compare scalar, NEON, and SVE implementations to demonstrate the performance benefits of specialized vector instructions
19+
20+
## What is bitmap scanning in databases?
21+
22+
Bitmap scanning involves searching through a bit vector to find positions where bits are set (`1`) or unset (`0`).
23+
24+
In database systems, bitmaps are commonly used to represent:
25+
26+
* **Bitmap indexes**: each bit represents whether a row satisfies a particular condition
27+
* **Bloom filters**: probabilistic data structures used to test set membership
28+
* **Column filters**: bit vectors indicating which rows match certain predicates
29+
30+
The operation of scanning a bitmap to find set bits is often in the critical path of query execution, making it a prime candidate for optimization.
31+
32+
## The evolution of vector processing for bitmap scanning
33+
34+
Here's how vector processing has evolved to improve bitmap scanning performance:
35+
36+
* **Generic scalar processing**: traditional bit-by-bit processing with conditional branches
37+
* **Optimized scalar processing**: byte-level skipping to avoid processing empty bytes
38+
* **NEON**: fixed-width 128-bit SIMD processing with vector operations
39+
* **SVE**: scalable vector processing with predication and specialized instructions like MATCH
40+
41+
## Set up your Arm development environment
42+
43+
To follow this Learning Path, you will need:
44+
45+
* An AWS Graviton4 instance running `Ubuntu 24.04`.
46+
* A GCC compiler with SVE support
47+
48+
First, install the required development tools:
49+
50+
```bash
51+
sudo apt-get update
52+
sudo apt-get install -y build-essential gcc g++
53+
```
54+
{{% notice Tip %}}
55+
An effective way to achieve optimal performance on Arm is not only through optimal flag usage, but also by using the most recent compiler version. For best performance, use the latest available GCC version with SVE support. This Learning Path was tested with GCC 13, the default on Ubuntu 24.04. Newer versions should also work.
56+
{{% /notice %}}
57+
58+
59+
Create a directory for your implementations:
60+
```bash
61+
mkdir -p bitmap_scan
62+
cd bitmap_scan
63+
```
64+
65+
## Next up: build the bitmap scanning foundation
66+
With your development environment set up, you're ready to dive into the core of bitmap scanning. In the next section, you’ll define a minimal bitmap data structure and implement utility functions to set, clear, and inspect individual bits.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
---
2+
# User change
3+
title: "Build and manage a bit vector in C"
4+
5+
weight: 3
6+
7+
layout: "learningpathall"
8+
9+
---
10+
## Bitmap data structure
11+
12+
Now let's define a simple bitmap data structure that serves as the foundation for the different implementations. The bitmap implementation uses a simple structure with three key components:
13+
- A byte array to store the actual bits
14+
- Tracking of the physical size (bytes)
15+
- Tracking of the logical size (bits)
16+
17+
For testing the different implementations in this Learning Path, you also need functions to generate and analyze the bitmaps.
18+
19+
Use a file editor of your choice and then copy the code below into `bitvector_scan_benchmark.c`:
20+
21+
```c
22+
// Define a simple bit vector structure
23+
typedef struct {
24+
uint8_t* data;
25+
size_t size_bytes;
26+
size_t size_bits;
27+
} bitvector_t;
28+
29+
// Create a new bit vector
30+
bitvector_t* bitvector_create(size_t size_bits) {
31+
bitvector_t* bv = (bitvector_t*)malloc(sizeof(bitvector_t));
32+
bv->size_bits = size_bits;
33+
bv->size_bytes = (size_bits + 7) / 8;
34+
bv->data = (uint8_t*)calloc(bv->size_bytes, 1);
35+
return bv;
36+
}
37+
38+
// Free bit vector resources
39+
void bitvector_free(bitvector_t* bv) {
40+
free(bv->data);
41+
free(bv);
42+
}
43+
44+
// Set a bit in the bit vector
45+
void bitvector_set_bit(bitvector_t* bv, size_t pos) {
46+
if (pos < bv->size_bits) {
47+
bv->data[pos / 8] |= (1 << (pos % 8));
48+
}
49+
}
50+
51+
// Get a bit from the bit vector
52+
bool bitvector_get_bit(bitvector_t* bv, size_t pos) {
53+
if (pos < bv->size_bits) {
54+
return (bv->data[pos / 8] & (1 << (pos % 8))) != 0;
55+
}
56+
return false;
57+
}
58+
59+
// Generate a bit vector with specified density
60+
bitvector_t* generate_bitvector(size_t size_bits, double density) {
61+
bitvector_t* bv = bitvector_create(size_bits);
62+
63+
// Set bits according to density
64+
size_t num_bits_to_set = (size_t)(size_bits * density);
65+
66+
for (size_t i = 0; i < num_bits_to_set; i++) {
67+
size_t pos = rand() % size_bits;
68+
bitvector_set_bit(bv, pos);
69+
}
70+
71+
return bv;
72+
}
73+
74+
// Count set bits in the bit vector
75+
size_t bitvector_count_scalar(bitvector_t* bv) {
76+
size_t count = 0;
77+
for (size_t i = 0; i < bv->size_bits; i++) {
78+
if (bitvector_get_bit(bv, i)) {
79+
count++;
80+
}
81+
}
82+
return count;
83+
}
84+
```
85+
86+
## Next up: implement and benchmark your first scalar bitmap scan
87+
88+
With your bit vector infrastructure in place, you're now ready to scan it for set bits—the core operation that underpins all bitmap-based filters in database systems.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
# User change
3+
title: "Implement scalar bitmap scanning in C"
4+
5+
weight: 4
6+
7+
layout: "learningpathall"
8+
9+
10+
---
11+
## Bitmap scanning implementations
12+
13+
Bitmap scanning is a fundamental operation in performance-critical systems such as databases, search engines, and filtering pipelines. It involves identifying the positions of set bits (`1`s) in a bit vector, which is often used to represent filtered rows, bitmap indexes, or membership flags.
14+
15+
In this section, you'll implement multiple scalar approaches to bitmap scanning in C, starting with a simple per-bit baseline, followed by an optimized version that reduces overhead for sparse data.
16+
17+
Now, let’s walk through the scalar versions of this operation that locate all set bit positions.
18+
19+
### Generic scalar implementation
20+
21+
This is the most straightforward implementation, checking each bit individually. It serves as the baseline for comparison against the other implementations to follow.
22+
23+
Copy the code below into the same file:
24+
25+
```c
26+
// Generic scalar implementation of bit vector scanning (bit-by-bit)
27+
size_t scan_bitvector_scalar_generic(bitvector_t* bv, uint32_t* result_positions) {
28+
size_t result_count = 0;
29+
30+
for (size_t i = 0; i < bv->size_bits; i++) {
31+
if (bitvector_get_bit(bv, i)) {
32+
result_positions[result_count++] = i;
33+
}
34+
}
35+
36+
return result_count;
37+
}
38+
```
39+
40+
You might notice that this generic C implementation processes every bit, even when most bits are not set. It has high per-bit function call overhead and does not take advantage of any vector instructions.
41+
42+
In the following implementations, you can address these inefficiencies with more optimized techniques.
43+
44+
### Optimized scalar implementation
45+
46+
This implementation adds byte-level skipping to avoid processing empty bytes.
47+
48+
Copy this optimized C scalar implementation code into the same file:
49+
50+
```c
51+
// Optimized scalar implementation of bit vector scanning (byte-level)
52+
size_t scan_bitvector_scalar(bitvector_t* bv, uint32_t* result_positions) {
53+
size_t result_count = 0;
54+
55+
for (size_t byte_idx = 0; byte_idx < bv->size_bytes; byte_idx++) {
56+
uint8_t byte = bv->data[byte_idx];
57+
58+
// Skip empty bytes
59+
if (byte == 0) {
60+
continue;
61+
}
62+
63+
// Process each bit in the byte
64+
for (int bit_pos = 0; bit_pos < 8; bit_pos++) {
65+
if (byte & (1 << bit_pos)) {
66+
size_t global_pos = byte_idx * 8 + bit_pos;
67+
if (global_pos < bv->size_bits) {
68+
result_positions[result_count++] = global_pos;
69+
}
70+
}
71+
}
72+
}
73+
74+
return result_count;
75+
}
76+
```
77+
Instead of iterating through each bit individually, this implementation processes one byte (8 bits) at a time. The main optimization over the previous scalar implementation is checking if an entire byte is zero and skipping it entirely. For sparse bitmaps, this can dramatically reduce the number of bit checks.
78+
79+
## Next up: accelerate bitmap scanning with NEON and SVE
80+
81+
You’ve now implemented two scalar scanning routines:
82+
83+
* A generic per-bit loop for correctness and simplicity
84+
85+
* An optimized scalar version that improves performance using byte-level skipping
86+
87+
These provide a solid foundation and performance baseline—but scalar methods can only take you so far. To unlock real throughput gains, it’s time to leverage SIMD (Single Instruction, Multiple Data) execution.
88+
89+
In the next section, you’ll explore how to use Arm NEON and SVE vector instructions to accelerate bitmap scanning. These approaches will process multiple bytes at once and significantly outperform scalar loops—especially on modern Arm-based CPUs like AWS Graviton4.

0 commit comments

Comments
 (0)