Skip to content

Commit 77b1fbf

Browse files
Further updates
1 parent 5282f69 commit 77b1fbf

6 files changed

Lines changed: 56 additions & 38 deletions

File tree

content/learning-paths/servers-and-cloud-computing/bitmap_scan_sve2/01-introduction.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
# User change
3-
title: "Optimize Bitmap Scanning with SVE and NEON on Arm Servers"
3+
title: "Optimize bitmap scanning with SVE and NEON on Arm servers"
44

55
weight: 2
66

@@ -58,5 +58,6 @@ Create a directory for your implementations:
5858
mkdir -p bitmap_scan
5959
cd bitmap_scan
6060
```
61-
## Next Steps
61+
## Next steps
62+
6263
In the next section, you’ll define the core bitmap data structure and utility functions for setting, clearing, and inspecting bits.

content/learning-paths/servers-and-cloud-computing/bitmap_scan_sve2/02-bitmap-data-structure.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
---
22
# User change
3-
title: "Build and manage the bit vector Structure"
3+
title: "Build and manage a bit vector in C"
44

55
weight: 3
66

77
layout: "learningpathall"
88

99
---
10-
## Bitmap Data Structure
10+
## Bitmap data structure
1111

1212
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:
1313
- A byte array to store the actual bits
14-
- Tracking of the physical size(bytes)
15-
- Tracking of the logical size(bits)
14+
- Tracking of the physical size (bytes)
15+
- Tracking of the logical size (bits)
1616

1717
For testing the different implementations in this Learning Path, you also need functions to generate and analyze the bitmaps.
1818

19-
Use a file editor of your choice and the copy the code below into `bitvector_scan_benchmark.c`:
19+
Use a file editor of your choice and then copy the code below into `bitvector_scan_benchmark.c`:
2020

2121
```c
2222
// Define a simple bit vector structure
@@ -81,4 +81,6 @@ size_t bitvector_count_scalar(bitvector_t* bv) {
8181
}
8282
return count;
8383
}
84-
```
84+
```
85+
86+
You now have a functional, compact bit vector in C for testing bitmap scanning performance. Next, you'll implement scalar, NEON, and SVE-based scanning routines that operate on this structure.

content/learning-paths/servers-and-cloud-computing/bitmap_scan_sve2/03-scalar-implementations.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
# User change
3-
title: "Scalar Implementations of Bitmap Scanning"
3+
title: "Implement Scalar Bitmap Scanning in C"
44

55
weight: 4
66

@@ -10,11 +10,17 @@ layout: "learningpathall"
1010
---
1111
## Bitmap scanning implementations
1212

13-
Now, let's implement four versions of a bitmap scanning operation that finds all positions where a bit is set:
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.
1414

15-
### 1. Generic Scalar Implementation
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.
1616

17-
This is the most straightforward implementation, checking each bit individually. It serves as our baseline for comparison against the other implementations to follow. Copy the code below into the same file:
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:
1824

1925
```c
2026
// Generic scalar implementation of bit vector scanning (bit-by-bit)
@@ -31,13 +37,15 @@ size_t scan_bitvector_scalar_generic(bitvector_t* bv, uint32_t* result_positions
3137
}
3238
```
3339
34-
You will notice this generic C implementation processes every bit, even when most bits are not set. It has high function call overhead and does not advantage of vector instructions.
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.
3541
36-
In the following implementations, you will address these inefficiencies with more optimized techniques.
42+
In the following implementations, you can address these inefficiencies with more optimized techniques.
3743
38-
### 2. Optimized Scalar Implementation
44+
### Optimized scalar implementation
3945
40-
This implementation adds byte-level skipping to avoid processing empty bytes. Copy this optimized C scalar implementation code into the same file:
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:
4149
4250
```c
4351
// Optimized scalar implementation of bit vector scanning (byte-level)
@@ -66,5 +74,9 @@ size_t result_count = 0;
6674
return result_count;
6775
}
6876
```
69-
Instead of iterating through each bit, 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.
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 steps
80+
81+
Next, you’ll explore how to accelerate bitmap scanning using NEON and SVE SIMD instructions on Arm Neoverse platforms like Graviton4, comparing them directly to these scalar baselines.
7082

content/learning-paths/servers-and-cloud-computing/bitmap_scan_sve2/04-vector-implementations.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ layout: "learningpathall"
88

99

1010
---
11-
### 3. NEON Implementation
11+
Modern Arm CPUs like Neoverse V2 support SIMD (Single Instruction, Multiple Data) extensions that allow processing multiple bytes in parallel. In this section, you'll explore how NEON and SVE vector instructions can dramatically accelerate bitmap scanning by skipping over large regions of unset data and reducing per-bit processing overhead.
12+
13+
## NEON Implementation
1214

1315
This implementation uses NEON SIMD (Single Instruction, Multiple Data) instructions to process 16 bytes (128 bits) at a time, significantly accelerating the scanning process. Copy the NEON implementation shown below into the same file:
1416
```c
@@ -81,7 +83,7 @@ size_t scan_bitvector_neon(bitvector_t* bv, uint32_t* result_positions) {
8183
```
8284
This NEON implementation processes 16 bytes at a time with vector instructions. For sparse bitmaps, entire 16-byte chunks can be skipped at once, providing a significant speedup over byte-level skipping. After vector processing, it falls back to scalar code for any remaining bytes that don't fill a complete 16-byte chunk.
8385
84-
### 4. SVE Implementation
86+
## SVE Implementation
8587
8688
This implementation uses SVE instructions which are available in the Arm Neoverse V2 based AWS Graviton 4 processor. Copy this SVE implementation into the same file:
8789

content/learning-paths/servers-and-cloud-computing/bitmap_scan_sve2/05-benchmarking-and-results.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
---
22
# User change
3-
title: "Benchmarking Bitmap Scanning Across Implementations"
3+
title: "Benchmarking bitmap scanning across implementations"
44

55
weight: 6
66

77
layout: "learningpathall"
88

99

1010
---
11-
## Benchmarking Code
11+
## Benchmarking code
1212

13-
Now, that you have created four different implementations of a bitmap scanning algorithm, let's create a benchmarking framework to compare the performance of our implementations. Copy the code shown below into `bitvector_scan_benchmark.c` :
13+
Now that you've created four different bitmap scanning implementations, let’s build a benchmarking framework to compare their performance.
14+
15+
Copy the code shown below into `bitvector_scan_benchmark.c` :
1416

1517
```c
1618
// Timing function for bit vector scanning
@@ -37,7 +39,7 @@ double benchmark_scan(size_t (*scan_func)(bitvector_t*, uint32_t*),
3739
}
3840
```
3941
40-
## Main Function
42+
## Main function
4143
The main function of your program is responsible for setting up the test environment, running the benchmarking code for the four different implementations across various bit densities, and reporting the results. In the context of bitmap scanning, bit density refers to the percentage or proportion of bits that are set (have a value of 1) in the bitmap. Copy the main function code below into `bitvector_scan_benchmark.c`:
4244
4345
```C
@@ -122,7 +124,7 @@ int main() {
122124
}
123125
```
124126

125-
## Compiling and Running
127+
## Compiling and running
126128

127129
You are now ready to compile and run your bitmap scanning implementations.
128130

@@ -132,11 +134,11 @@ To compile our bitmap scanning implementations with the appropriate flags, run:
132134
gcc -O3 -march=armv9-a+sve2 -o bitvector_scan_benchmark bitvector_scan_benchmark.c -lm
133135
```
134136

135-
## Performance Results
137+
## Performance results
136138

137139
When running on a Graviton4 c8g.large instance with Ubuntu 24.04, the results should look similar to:
138140

139-
### Execution Time (ms)
141+
### Execution time (ms)
140142

141143
| Density | Set Bits | Scalar Generic | Scalar Optimized | NEON | SVE |
142144
|---------|----------|----------------|------------------|-------|------------|
@@ -146,7 +148,7 @@ When running on a Graviton4 c8g.large instance with Ubuntu 24.04, the results sh
146148
| 0.0100 | 99,511 | 7.821 | 1.570 | 2.252 | 1.353 |
147149
| 0.1000 | 951,491 | 12.817 | 8.336 | 9.106 | 6.770 |
148150

149-
### Speedup vs Generic Scalar
151+
### Speed-up vs generic Scalar
150152

151153
| Density | Scalar Optimized | NEON | SVE |
152154
|---------|------------------|---------|------------|
@@ -156,25 +158,25 @@ When running on a Graviton4 c8g.large instance with Ubuntu 24.04, the results sh
156158
| 0.0100 | 5.02x | 3.49x | 5.78x |
157159
| 0.1000 | 1.54x | 1.40x | 1.90x |
158160

159-
## Understanding the Performance Results
161+
## Understanding the results
160162

161-
### Generic Scalar vs Optimized Scalar
163+
## Generic Scalar vs Optimized Scalar
162164

163165
The optimized scalar implementation shows significant improvements over the generic scalar implementation due to:
164166

165167
1. **Byte-level Skipping**: Avoiding processing empty bytes
166168
2. **Reduced Function Calls**: Accessing bits directly rather than through function calls
167169
3. **Better Cache Utilization**: More sequential memory access patterns
168170

169-
### Optimized Scalar vs NEON
171+
## Optimized Scalar vs NEON
170172

171173
The NEON implementation shows further improvements over the optimized scalar implementation for sparse bit vectors due to:
172174

173175
1. **Chunk-level Skipping**: Quickly skipping 16 empty bytes at once
174176
2. **Vectorized Comparison**: Checking multiple bytes in parallel
175177
3. **Early Termination**: Quickly determining if a chunk contains any set bits
176178

177-
### NEON vs SVE
179+
## NEON vs SVE
178180

179181
The performance comparison between NEON and SVE depends on the bit density:
180182

@@ -188,7 +190,7 @@ The performance comparison between NEON and SVE depends on the bit density:
188190
- SVE consistently outperforms NEON
189191
- SVE achieves up to 1.66x speedup over NEON at 0.01% density
190192

191-
# Key Optimizations in SVE Implementation
193+
## Key Optimizations in SVE Implementation
192194

193195
The SVE implementation includes several key optimizations:
194196

content/learning-paths/servers-and-cloud-computing/bitmap_scan_sve2/06-application-and-best-practices.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
# User change
3-
title: "Applications and Optimization Best Practices"
3+
title: "Applications and optimization best practices"
44

55
weight: 7
66

@@ -10,21 +10,20 @@ layout: "learningpathall"
1010

1111
These bitmap scanning optimizations can be applied to various database operations:
1212

13-
### 1. Bitmap Index Scans
14-
13+
### Bitmap Index Scans
1514
Bitmap indexes are commonly used in analytical databases to accelerate queries with multiple filter conditions. The NEON and SVE implementations can significantly speed up the scanning of these bitmap indexes, especially for queries with low selectivity.
1615

17-
### 2. Bloom Filter Checks
16+
### Bloom Filter Checks
1817

1918
Bloom filters are probabilistic data structures used to test set membership. They are often used in database systems to quickly filter out rows that don't match certain conditions. The NEON and SVE implementations can accelerate these bloom filter checks.
2019

21-
### 3. Column Filtering
20+
### Column Filtering
2221

2322
In column-oriented databases, bitmap filters are often used to represent which rows match certain predicates. The NEON and SVE implementation can speed up the scanning of these bitmap filters, improving query performance.
2423

2524
## Best Practices
2625

27-
Based on our benchmark results, here are some best practices for optimizing bitmap scanning operations:
26+
Based on the benchmark results, here are some best practices for optimizing bitmap scanning operations:
2827

2928
1. **Choose the Right Implementation**: Select the appropriate implementation based on the expected bit density:
3029
- For empty bit vectors: NEON is optimal

0 commit comments

Comments
 (0)