Phân vùng khoảng [0,n] của các chỉ mục 32 bit của bitmap thành các đoạn của 1 0 1 1 0 1 …………0 1 0 0 0 1 …………1 10 0... Bitmap dài nbit với các chỉ
mục 32 bit trong khoảng [0,n]
216 bit 216 bit
Số phần Số phần tử > 4096 tử <= 4096
Bitmap mảng
1 0 1 1 0 1 ……… {1, 5, ...}
{0, 1, …} Chỉ mục cấp 1 của Roaring bitmap (16 bit cao của các chỉ mục 32 bit)
các số nguyên có độ lớn tối ta là 216 chia sẻ cùng 16 bit quan trọng nhất (16 bit cao của mỗi chỉ mục 32 bit). Đoạn thứ nhất từ 0 đến 65535, đoạn thứ 2 từ 65536 đến 131071,… Sử dụng container chuyên dụng để lưu trữ 16 bit ít quan trọng nhất của chúng (16 bit thấp của mỗi chỉ mục).
Khi một đoạn không chứa hơn 4096 số nguyên, sử dụng một mảng được sắp xếp đểchứacác số nguyên16 bit được đóng gói. Khi có nhiều hơn 4096 số nguyên, sử dụng một bitmap 216bit. Như vậy, chúng tacó hai loại container: container mảng cho đoạn thưa thớt và container bitmap cho đoạn dày đặc. Ngưỡng 4096 đảm bảo rằng ở cấp độ củacontainer, mỗi số nguyên sử dụng không quá 16 bit (lớn hơn con số này thì một bitmap có hiệu quảsử dụng bộ nhớ tốt hơn mộtmảng.
Hình3.9 So sánh bộ nhớ được sử dụng cho mảng và bitmap (nguồn http://www.elasticsearch.org/blog/frame-of-reference-and-roaring-bitmaps/)
Các container được lưu trữ trong một mảng độngchia sẻ16 bit quan trong nhất: điều này đóng vai trị như là một chỉ mục cấp 1. Các mảng lưu trữ các container được sắp xếp theo 16 bitquan trọng nhất. Chỉmụccấp1 nàythườnglà nhỏ: khin =
1 000 000, nó chứa ít nhất 16 mục. Vì vậy nó thường được lưu lại trong bộ nhớ cache của CPU. Các container này không bao giờ nên sử dụng nhiều hơn 8 kB.
Mỗi container theo dõi số phần tử của nó (số nguyên) bằng cách sử dụng một bộ đếm. Như vậy việc tính tốn số phần từ của một Roaring bitmap có thể được thực hiện một cách nhanh chóng: nó sử dụng tối đa là n/216biến đếm. Nó cũng làm
cho truy vấn thứ hạng (rank query) và truy vấn chọn (select query) nhanh hơn so với một bitmap thông thường: các truy vấn thứ hạng đếm số lượng các bit 1 trong khoảng [0,i] trong khi các truy vấn chọn tìm kiếm vị trí của bit1 thứi.
Lỗi có thể xảy ra khi các container và mảng động sử dụng bộ nhớ vượt quá 16 bit/số nguyên. Tuy nhiên, miễn là số lượng container là nhỏ so với tổng số các số nguyên cần lưu trữ, chứchúng ta không bao giờ nên sử dụngsố nguyên lớn hơn16 bit. Giả định rằng số lượng container ít hơn rất nhiều so với tổng số nguyên. Chính xác hơn, chúng ta giả định rằng mật độ thường vượt quá 0,1% hoặc n/|S|> 0.001.
Khi cácứng dụng gặp phải bộ số nguyên với mật độ thấp (dưới 0,1%), một bitmap dường như không phải là cấu trúc dữ liệu thích hợp.
Truy cập Roaring bitmap:
Để kiểm tra sự hiện diện của một sốnguyên 32 bit x, đầu tiên chúng ta tìm kiếm
container tương ứng với x/216, sử dụng tìm kiếm nhị phân. Nếu một container bitmap được tìm thấy, chúng ta truy cập bit thứx mod 216. Nếu một container mảng đượctìm thấy, chúng ta tiếp tụctìm kiếm nhị phân một lần nữa.
Tương tự, khi chúng ta thêm hoặc loại bỏ một số nguyên x, đầu tiên chúng ta tìm container tương ứng. Nếu container được tìm thấy là bitmap, chúng ta thiết lập giá trị của bit tương ứng và cập nhật số phần tử của nó. Ngược lại,chúng ta sử dụng tìm kiếm nhị phân rồi thêm hoặc xóa tuyến tính (linear-time).
Khi loại bỏ một số nguyên, một containerbitmap có thể trở thành một container mảng nếu số phần tử của nó chưa vượt quá 4096. Khi thêm một số nguyên, một container mảng có thể trở thành một container bitmap khi số phần tử của nó vượt quá 4096. Khi điều này xảy ra, một container mới được tạo ra chứa dữ liệu đươc cập nhật trong khi container cũ được bỏ đi. Chuyển đổi một container mảng thành một container bitmap được thực hiện bằng cách tạo một container bitmap mới với giá trị khởi tạo là các bit 0 rồi thiết lập các bit tương ứng. Để chuyển đổi một container bitmap thành một container mảng, chúng ta trích ra vị trí của các bit 1 sử dụng thuật toán 2.
Các phép toán logic:
Roaring bitmap cho phép thực hiện hai phép toán logic là phép hội (OR) và phép giao (AND). Một phép toán logic giữa hai Roaring bitmap bao gồm việc lặp và so sánh 16 bit cao (khóa) của các số nguyên trên các chỉ mục cấp 1. Để đạt hiệu quả tốt hơn, các mảng cấp 1 phải ln được sắp xếp. Hai khóa được so sánh ở mỗi lần lặp. Nếu bằng nhau, mộtphép toán logic cấpthứ 2 giữa các container tương ứng được thực hiện. Điều này luôn luôn tạo ra một container mới. Nếu container không phải là rỗng, nó được thêm vào kết quả cùng với khóa. Sau đóbiến lặp lưu vị trí của mảng cấp 1 sẽ tăng lên một. Khi hai khóa khơng bằng nhau, mảng chứa container nhỏ nhất được tăng thêm một vị trí, và nếu một phép hội được thực hiện, khóa nhỏ nhất và một bản sao container tương ứng được thêm vào câu trả lời. Khi tính tốn các phép hội, chúng ta lặp lại cho đến khi đến cuối cảhai mảng cấp 1. Khi tính tốn các phép giao, chúng ta chấm dứt ngay sau khi một mảng được khai thác hết.
Hình3.10 Minh họa phép OR giữa hai Roaring bitmap khi chỉ mục cấp 1 không bằng nhau
Các mảng cấp 1 đãđược sắp xếp có chi phí cho các phép so sánh là O (n1+ n2), với n1 và n2 là chiều dài tương ứng của hai mảng. Chúng ta cũng duy trì các container mảng được sắp xếp để chúng có các ưu điểm tương tự nhau. Một container có thể được biểu diễn bởi hai cấu trúc dữ liệu khác nhau là: bitmap và mảng, mộtphép hộihoặc giaogiữa hai containergồmmột trong ba kịch bản sau:
Bitmap với Bitmap: Chúng ta lặp trên 1024 từ 64 bit. Với phép hội, chúng ta
OR {0, 3, ...} Roaring bitmap 1 {1, 2, ...} Roaring bitmap 2 1 0 1 1 1 0… 0 1 0 0 0 1… {1, 10, 25, …} {0, 15, 28, …} {0, 1, 2, 3, ...} 1 0 1 1 1 0… 0 1 0 0 0 1… {1, 10, 25, …} {0, 15, 28, …}
thực thi 1024 phép OR và ghi kết quả vào container bitmap mới (thuật toán 1). Số phần tử mới được tính tốn một cách hiệu quả trong Java bằng phương thức
Long.bitCount.