Redis native compressed bitmaps (redis-roaring) (#15331)

Fixes #15296

## Native Roaring bitmaps

Adds a native **Roaring bitmap** representation for Redis bitmaps,
backed by vendored **CRoaring v4.7.2** (`deps/croaring`, built with SIMD
disabled and symbols hidden for portability). A string bitmap costs one
byte per 8 bit positions up to its highest offset, regardless of how
many bits are set; sparse or large bitmaps are instead stored
compressed, cutting memory dramatically while preserving exact bit
semantics.

### Type & object model
- New object type `OBJ_BITMAP` with encoding
`OBJ_ENCODING_BITMAP_ROARING`. `TYPE` → `bitmap`, `OBJECT ENCODING` →
`bitmap-roaring`.
- Object header: `encoding` widened 4→5 bits to fit the new encoding
value; to keep `redisObject` the same width, `refcount` drops 23→22
bits, lowering the maximum non-shared refcount from 8388605 to 4194301
(still far above any real per-object refcount).

### Enabling it — `bitmap-default-roaring`
- New boolean config (default **no**, runtime-modifiable). When **off**,
bitmap writes keep creating plain strings. Native bitmaps remain
supported and can enter the keyspace through `BITOP` with a Roaring
source, `RESTORE`, replication, or loading an RDB/AOF containing them.
- When **on**, bit-writes that create or extend a bitmap produce a
native Roaring bitmap, an existing string bitmap converts in place on
the next bit-write (TTL and module metadata preserved), and `BITOP`
destinations are Roaring. Automatic creation/conversion is disabled on
replicas and when a write's propagation excludes either AOF or replicas
— notably `RM_Call()` without `!`, selective `RM_Call()` flags, and
scripts using `redis.set_repl()`; such writes keep plain strings on
every node.

### Command surface & string boundary
- Bit commands operate on both representations transparently: `SETBIT`,
`GETBIT`, `BITCOUNT`, `BITPOS`, `BITOP`, `BITFIELD`, `BITFIELD_RO`.
- A native Roaring bitmap is a **distinct type**: plain string commands
(`GET`, `GETRANGE`, `APPEND`, `SETRANGE`, `STRLEN`, …) return
`WRONGTYPE`. `DEBUG BITMAP-RAW` exposes the materialized bytes.
- Bit offsets follow the historical string rule on both representations:
capped at `proto-max-bulk-len` × 8. Internal representability is bounded
at 2^60−1 logical bytes (`BITROAR_MAX_BYTES`). `BITOP NOT` over a
Roaring source bounds missing-chunk allocation with a budget derived
from `proto-max-bulk-len` (minimum 65,536 chunks of 8 KiB). Replication
and AOF replay bypass this budget to apply accepted operations; dense
string sources are exempt.

### Persistence
- Adds `RDB_TYPE_BITMAP` (33) under the existing RDB version 16
introduced for 8.12 (this PR does not bump it). This relies on shipping
in 8.12: 8.12 milestone builds without this PR reject type 33 cleanly,
and if the PR slips past 8.12, `RDB_VERSION` must be bumped. The
RDB/DUMP payload is the logical byte length followed by the CRoaring64
portable serialization — compact, not a dense byte expansion. Loading
enforces the representability bound and validates the blob.

### Replication & AOF — deterministic by construction
- Representation decisions are never re-derived from a replica's or
AOF-loader's own config. The transition travels in the stream as the
internal `BITCONVERT <key>` and `BITROAROP` commands (hidden from
`COMMAND`, internal-client gated, `since 8.12.0`; `ACL CAT` and `ACL
DRYRUN` are not yet filtered and still list them):
- `SETBIT`/`BITFIELD` creating or converting a bitmap replay as
`BITCONVERT` followed by the original command, grouped in
`MULTI`/`EXEC`;
- a `BITOP` whose destination is Roaring only by configuration replays
as internal `BITROAROP`, which stores the native result directly and
preserves notification and callback ordering;
  - operations on already-Roaring operands propagate verbatim.
- `BITCONVERT` applies the same in-place transition the primary
performed: TTL and module metadata retained, no value payload in the
stream.

### Notifications, modules, cluster
- `NOTIFY_BITMAP` (class `b`) keyspace events; conversion is an
observable in-place type change: `type_changed` (class `c`) then the
command's own event when it changes the logical value — never
`overwritten`. A conversion that leaves the logical value unchanged
still emits `type_changed`, counts as a write, and is propagated. A
newly created native key emits `new` (class `n`) rather than
`type_changed`.
- Modules observe the transition via the type-changed callback;
`REDISMODULE_KEYTYPE_BITMAP` added.
- Works under Cluster, including atomic slot migration
(`tests/unit/cluster/bitmap.tcl`, `atomic-slot-migration.tcl` coverage).

### Memory accounting & active defrag
- Full memory accounting; all CRoaring allocations route through
`zmalloc`. Active defrag walks the Roaring64 ART and its ARRAY, BITSET,
and RUN containers (via a small vendor patch exposing `roaring64`
internals), and fork-child memory is dismissed correctly. INFO keysizes
gains a `distrib_bitmaps_sizes` histogram of **logical** byte length,
not memory: a sparse Roaring bitmap can be orders of magnitude smaller
than its bucket, unlike `distrib_<collection>_sizes`, which reports
allocation size. Bitmap allocation sizes are tracked internally but, as
for strings, not reported by INFO.

### Documentation
- End-user documentation is tracked in redis/docs#3715, targeting Redis
8.12.

---------

Signed-off-by: Antonio Viggiano <agfviggiano@gmail.com>
Co-authored-by: debing.sun <debing.sun@redis.com>
This commit is contained in:
Antonio Viggianoanddebing.sun authored and GitHub committed 2026-10-14 10:52:00 +08:00
1 parent fcc9b536dd
commit f3b9b68ec6
106 files changed
+45104 -344

No files matched your search

+1
View File
@@ -31,6 +31,7 @@ deps/lua/src/liblua.a
deps/hdr_histogram/libhdrhistogram.a
deps/fpconv/libfpconv.a
deps/tre/libtre.a
deps/croaring/libroaring.a
tests/tls/*
.make-*
.prerequisites
+14 -1
View File
@@ -61,6 +61,7 @@ distclean:
-(cd fpconv && $(MAKE) clean) > /dev/null || true
-(cd tre && $(MAKE) clean) > /dev/null || true
-(cd xxhash && $(MAKE) clean) > /dev/null || true
-(cd croaring && $(MAKE) clean) > /dev/null || true
-(rm -f .make-*)
.PHONY: distclean
@@ -108,6 +109,18 @@ xxhash: .make-prerequisites
cd xxhash && $(MAKE) lib CFLAGS="$(XXHASH_CFLAGS)" LDFLAGS="$(DEPS_LDFLAGS)"
.PHONY: xxhash
CROARING_CFLAGS = $(DEPS_CFLAGS) -DROARING_DISABLE_X64=1 -DDISABLENEON=1
# Do not inherit Redis' LTO flags here: bitcode archives require mutually
# compatible compiler, archiver and linker plugins, which supported CI and
# cross-build environments cannot guarantee. CRoaring tracks the resulting
# flags in its build settings file, so incremental builds still rebuild the
# archive when needed.
croaring: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
cd croaring && $(MAKE) CFLAGS="$(CROARING_CFLAGS)" LDFLAGS="$(DEPS_LDFLAGS)" ENABLE_LTO= AR="$(AR)"
.PHONY: croaring
ifeq ($(uname_S),SunOS)
# Make isinf() available
LUA_CFLAGS= -D__C99FEATURES__=1
@@ -128,7 +141,7 @@ endif
# lua's Makefile defines AR="ar rcu", which is unusual, and makes it more
# challenging to cross-compile lua (and redis). These defines make it easier
# to fit redis into cross-compilation environments, which typically set AR.
AR=ar
AR?=ar
ARFLAGS=rc
lua: .make-prerequisites
+31
View File
@@ -6,6 +6,9 @@ should be provided by the operating system.
* **linenoise** is a readline replacement. It is developed by the same authors of Redis but is managed as a separated project and updated as needed.
* **lua** is Lua 5.1 with minor changes for security and additional libraries.
* **hdr_histogram** Used for per-command latency tracking histograms.
* **CRoaring** is the C implementation of Roaring bitmaps used by native Redis
bitmap encodings. Redis vendors the C headers and source from upstream and
builds it only through the Redis dependency Makefile.
How to upgrade the above dependencies
===
@@ -104,3 +107,31 @@ We use a customized version based on master branch commit e4448cf6d1cd08fff51981
2. Copy updated files from newer version onto files in /hdr_histogram.
3. Apply the changes from 1 above to the updated files.
CRoaring
---
Updated source can be found here: https://github.com/RoaringBitmap/CRoaring
We currently vendor v4.7.2 with a few local patches, each marked with a
`Local Redis patch` comment at the patch site.
1. Replace `deps/croaring/include` and `deps/croaring/src` with the upstream
`include` and `src` trees (C sources and headers only; Redis does not use
the upstream CMake build), and refresh `LICENSE`, `AUTHORS`, `README.md`
and `SECURITY.md`.
2. If upstream added, removed or renamed C sources, mirror the change in the
source list of `deps/croaring/Makefile`. The Makefile itself is fully
Redis-authored (hidden symbol visibility, portable implementation only,
flag-change rebuild tracking, refcount regression test) and is kept as is.
3. Re-apply the local patches unless upstream has independently fixed them.
Run `git grep 'Local Redis patch' deps/croaring` before replacing the
tree to enumerate them. Currently:
* `include/roaring/portability.h`: portability fixes for platforms and
toolchains upstream does not exercise the same way (endianness
detection, a `__sync`-builtin atomics fallback, a `__has_include`
polyfill, an unaligned-access gate, a non-atomic refcount fix).
* `include/roaring/roaring64_internal.h` (Redis-added file) and
`src/roaring64.c`: expose the private 64-bit bitmap layout so
`src/bitroar.c` can walk every allocation for MEMORY USAGE accounting,
fork-child page dismissal and active defragmentation.
4. Run `make -C deps/croaring test` and the bitmap test suites under
`tests/` (`bitroar`, `bitops`).
+15
View File
@@ -0,0 +1,15 @@
# List of authors for copyright purposes
Tom Cornebize
Luca Deri
Jacob Evans
Owen Kaser
Nathan Kurz
Daniel Lemire
Wojciech Muła
Chris O'Hara
François Saint-Jacques
Gregory Ssi-Yan-Kai
Andrei Gudkov
Guillaume Holley
Brian Esch
+236
View File
@@ -0,0 +1,236 @@
The CRoaring project is under a dual license (Apache/MIT).
Users of the library may choose one or the other license.
------------------
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016-2022 The CRoaring authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-----------------------------------
MIT License
Copyright 2016-2022 The CRoaring authors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+73
View File
@@ -0,0 +1,73 @@
# Redis CRoaring dependency Makefile
STD = -std=gnu11
WARN = -Wall
OPT = -O3
DEBUG = -g
AR ?= ar
ARFLAGS = rcs
CROARING_CFLAGS = $(STD) $(WARN) $(OPT) $(DEBUG) $(ENABLE_LTO) $(CFLAGS) -fPIC \
-fvisibility=hidden -DROARING_DISABLE_X64=1 -DDISABLENEON=1 \
-Iinclude -Isrc
CROARING_BUILD_SETTINGS = .make-build-settings
REFCOUNT_NONE_TEST = .make-refcount-none-test
ROARING_SRC = \
src/isadetection.c \
src/array_util.c \
src/bitset_util.c \
src/art/art.c \
src/bitset.c \
src/containers/array.c \
src/containers/bitset.c \
src/containers/containers.c \
src/containers/convert.c \
src/containers/mixed_intersection.c \
src/containers/mixed_union.c \
src/containers/mixed_equal.c \
src/containers/mixed_subset.c \
src/containers/mixed_negation.c \
src/containers/mixed_xor.c \
src/containers/mixed_andnot.c \
src/containers/run.c \
src/memory.c \
src/roaring.c \
src/roaring64.c \
src/roaring_priority_queue.c \
src/roaring_array.c
ROARING_OBJ = $(ROARING_SRC:.c=.o)
libroaring.a: $(ROARING_OBJ)
$(AR) $(ARFLAGS) $@ $+
$(CROARING_BUILD_SETTINGS): FORCE
@printf '%s\n' 'CROARING_CFLAGS=$(CROARING_CFLAGS)' > $@.tmp
@if test -r $@ && cmp -s $@ $@.tmp; then \
rm $@.tmp; \
else \
mv $@.tmp $@; \
rm -f $(ROARING_OBJ) libroaring.a; \
fi
%.o: %.c $(CROARING_BUILD_SETTINGS)
$(CC) $(CROARING_CFLAGS) -c -o $@ $<
$(REFCOUNT_NONE_TEST): tests/refcount_none.c include/roaring/portability.h
$(CC) $(CROARING_CFLAGS) $(LDFLAGS) \
-DCROARING_ATOMIC_IMPL=CROARING_ATOMIC_IMPL_NONE -o $@ $<
test: $(REFCOUNT_NONE_TEST)
./$(REFCOUNT_NONE_TEST)
clean:
rm -f $(ROARING_OBJ)
rm -f libroaring.a
rm -f $(REFCOUNT_NONE_TEST)
rm -f $(CROARING_BUILD_SETTINGS) $(CROARING_BUILD_SETTINGS).tmp
FORCE:
.PHONY: clean test FORCE
+1327
View File
File diff suppressed because it is too large. Load diff
+13
View File
@@ -0,0 +1,13 @@
# Security Policy
## Reporting a Vulnerability
Please use the following contact information for reporting a vulnerability:
- [Daniel Lemire]( https://www.teluq.ca/siteweb/univ/en/dlemire.html) - daniel@lemire.me
Please be advised that we are getting too many invalid bug reports causes by AI.
This is a drain on our maintainers and a disservice to the community.
We asl that you consult our policy: `AI_USAGE_POLICY.md`.
+348
View File
@@ -0,0 +1,348 @@
/*
* array_util.h
*
* This header provides low-level utility routines for sorted arrays of
* 16-bit integers, which are used heavily by CRoaring's array-based
* containers and set-operation kernels. It includes search helpers, counting
* helpers, and array intersection/difference primitives.
*
* Some of the routines also have SIMD-accelerated implementations on supported
* platforms, allowing efficient operations on sorted integer arrays that form
* the basis of sparse container processing.
*/
#ifndef CROARING_ARRAY_UTIL_H
#define CROARING_ARRAY_UTIL_H
#include <stddef.h> // for size_t
#include <stdint.h>
#include <roaring/portability.h>
#if CROARING_IS_X64
#ifndef CROARING_COMPILER_SUPPORTS_AVX512
#error "CROARING_COMPILER_SUPPORTS_AVX512 needs to be defined."
#endif // CROARING_COMPILER_SUPPORTS_AVX512
#endif
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuninitialized"
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/*
* Sorted-array search.
* Assumes that array is sorted, has logarithmic complexity.
* if the result is x, then:
* if ( x>0 ) you have array[x] = ikey
* if ( x<0 ) then inserting ikey at position -x-1 in array (insuring that
* array[-x-1]=ikey) keys the array sorted.
*
* Adapted from array_container_contains: a SIMD-quad block-narrowing
* search at gap=16 (Daniel Lemire,
* https://lemire.me/blog/2026/04/27/you-can-beat-the-binary-search/)
* followed by a scalar in-block scan that recovers the exact insertion
* point required by the binarySearch contract.
*/
inline int32_t binarySearch(const uint16_t *array, int32_t lenarray,
uint16_t ikey) {
const int32_t gap = 16;
if (lenarray < gap) {
for (int32_t j = 0; j < lenarray; j++) {
if (array[j] >= ikey) {
return (array[j] == ikey) ? j : -(j + 1);
}
}
return -(lenarray + 1);
}
const int32_t num_blocks = lenarray / gap;
int32_t base = 0;
int32_t n = num_blocks;
while (n > 3) {
int32_t quarter = n >> 2;
int32_t k1 = array[(base + quarter + 1) * gap - 1];
int32_t k2 = array[(base + 2 * quarter + 1) * gap - 1];
int32_t k3 = array[(base + 3 * quarter + 1) * gap - 1];
int32_t c1 = (k1 < ikey);
int32_t c2 = (k2 < ikey);
int32_t c3 = (k3 < ikey);
base += (c1 + c2 + c3) * quarter;
n -= 3 * quarter;
}
while (n > 1) {
int32_t half = n >> 1;
base = (array[(base + half + 1) * gap - 1] < ikey) ? base + half : base;
n -= half;
}
int32_t lo = (array[(base + 1) * gap - 1] < ikey) ? base + 1 : base;
if (lo < num_blocks) {
const int32_t start = lo * gap;
#if defined(CROARING_IS_X64)
// SSE2: subs_epu16 yields zero where lane >= ikey. movemask of an
// epi16 compare gives 2 bits per lane; ctz>>1 = lane index. Scan
// the first 8 lanes first and exit early when they contain the
// answer; otherwise the block-narrowing invariant guarantees the
// second-half mask is non-zero.
__m128i needle = _mm_set1_epi16((short)ikey);
__m128i zero = _mm_setzero_si128();
__m128i v0 = _mm_loadu_si128((const __m128i *)(array + start));
__m128i ge0 = _mm_cmpeq_epi16(_mm_subs_epu16(needle, v0), zero);
unsigned m0 = (unsigned)_mm_movemask_epi8(ge0);
if (m0 != 0) {
int32_t j = start + (int32_t)(roaring_trailing_zeroes(m0) >> 1);
return (array[j] == ikey) ? j : -(j + 1);
}
__m128i v1 = _mm_loadu_si128((const __m128i *)(array + start + 8));
__m128i ge1 = _mm_cmpeq_epi16(_mm_subs_epu16(needle, v1), zero);
unsigned m1 = (unsigned)_mm_movemask_epi8(ge1);
int32_t j = start + 8 + (int32_t)(roaring_trailing_zeroes(m1) >> 1);
return (array[j] == ikey) ? j : -(j + 1);
#else
const int32_t end = start + gap;
for (int32_t j = start; j < end; j++) {
if (array[j] >= ikey) {
return (array[j] == ikey) ? j : -(j + 1);
}
}
// Unreachable: the narrowing guarantees the last element of the
// selected block is >= ikey.
return -(end + 1);
#endif
}
for (int32_t j = num_blocks * gap; j < lenarray; j++) {
if (array[j] >= ikey) {
return (array[j] == ikey) ? j : -(j + 1);
}
}
return -(lenarray + 1);
}
/**
* Galloping search
* Assumes that array is sorted, has logarithmic complexity.
* if the result is x, then if x = length, you have that all values in array
* between pos and length are smaller than min. otherwise returns the first
* index x such that array[x] >= min.
*/
static inline int32_t advanceUntil(const uint16_t *array, int32_t pos,
int32_t length, uint16_t min) {
int32_t lower = pos + 1;
if ((lower >= length) || (array[lower] >= min)) {
return lower;
}
int32_t spansize = 1;
while ((lower + spansize < length) && (array[lower + spansize] < min)) {
spansize <<= 1;
}
int32_t upper = (lower + spansize < length) ? lower + spansize : length - 1;
if (array[upper] == min) {
return upper;
}
if (array[upper] < min) {
// means
// array
// has no
// item
// >= min
// pos = array.length;
return length;
}
// we know that the next-smallest span was too small
lower += (spansize >> 1);
int32_t mid = 0;
while (lower + 1 != upper) {
mid = (lower + upper) >> 1;
if (array[mid] == min) {
return mid;
} else if (array[mid] < min) {
lower = mid;
} else {
upper = mid;
}
}
return upper;
}
/**
* Returns number of elements which are less than ikey.
* Array elements must be unique and sorted.
*/
static inline int32_t count_less(const uint16_t *array, int32_t lenarray,
uint16_t ikey) {
if (lenarray == 0) return 0;
int32_t pos = binarySearch(array, lenarray, ikey);
return pos >= 0 ? pos : -(pos + 1);
}
/**
* Returns number of elements which are greater than ikey.
* Array elements must be unique and sorted.
*/
static inline int32_t count_greater(const uint16_t *array, int32_t lenarray,
uint16_t ikey) {
if (lenarray == 0) return 0;
int32_t pos = binarySearch(array, lenarray, ikey);
if (pos >= 0) {
return lenarray - (pos + 1);
} else {
return lenarray - (-pos - 1);
}
}
/**
* From Schlegel et al., Fast Sorted-Set Intersection using SIMD Instructions
* Optimized by D. Lemire on May 3rd 2013
*
* C should have capacity greater than the minimum of s_1 and s_b + 8
* where 8 is sizeof(__m128i)/sizeof(uint16_t).
*/
int32_t intersect_vector16(const uint16_t *A, size_t s_a, const uint16_t *B,
size_t s_b, uint16_t *C);
int32_t intersect_vector16_inplace(uint16_t *A, size_t s_a, const uint16_t *B,
size_t s_b);
/**
* Take an array container and write it out to a 32-bit array, using base
* as the offset.
*/
int array_container_to_uint32_array_vector16(void *vout, const uint16_t *array,
size_t cardinality, uint32_t base);
#if CROARING_COMPILER_SUPPORTS_AVX512
int avx512_array_container_to_uint32_array(void *vout, const uint16_t *array,
size_t cardinality, uint32_t base);
#endif
/**
* Compute the cardinality of the intersection using SSE4 instructions
*/
int32_t intersect_vector16_cardinality(const uint16_t *A, size_t s_a,
const uint16_t *B, size_t s_b);
/* Computes the intersection between one small and one large set of uint16_t.
* Stores the result into buffer and return the number of elements. */
int32_t intersect_skewed_uint16(const uint16_t *smallarray, size_t size_s,
const uint16_t *largearray, size_t size_l,
uint16_t *buffer);
/* Computes the size of the intersection between one small and one large set of
* uint16_t. */
int32_t intersect_skewed_uint16_cardinality(const uint16_t *smallarray,
size_t size_s,
const uint16_t *largearray,
size_t size_l);
/* Check whether the size of the intersection between one small and one large
* set of uint16_t is non-zero. */
bool intersect_skewed_uint16_nonempty(const uint16_t *smallarray, size_t size_s,
const uint16_t *largearray,
size_t size_l);
/**
* Generic intersection function.
*/
int32_t intersect_uint16(const uint16_t *A, const size_t lenA,
const uint16_t *B, const size_t lenB, uint16_t *out);
/**
* Compute the size of the intersection (generic).
*/
int32_t intersect_uint16_cardinality(const uint16_t *A, const size_t lenA,
const uint16_t *B, const size_t lenB);
/**
* Checking whether the size of the intersection is non-zero.
*/
bool intersect_uint16_nonempty(const uint16_t *A, const size_t lenA,
const uint16_t *B, const size_t lenB);
/**
* Generic union function.
*/
size_t union_uint16(const uint16_t *set_1, size_t size_1, const uint16_t *set_2,
size_t size_2, uint16_t *buffer);
/**
* Generic XOR function.
*/
int32_t xor_uint16(const uint16_t *array_1, int32_t card_1,
const uint16_t *array_2, int32_t card_2, uint16_t *out);
/**
* Generic difference function (ANDNOT).
*/
int difference_uint16(const uint16_t *a1, int length1, const uint16_t *a2,
int length2, uint16_t *a_out);
/**
* Generic intersection function.
*/
size_t intersection_uint32(const uint32_t *A, const size_t lenA,
const uint32_t *B, const size_t lenB, uint32_t *out);
/**
* Generic intersection function, returns just the cardinality.
*/
size_t intersection_uint32_card(const uint32_t *A, const size_t lenA,
const uint32_t *B, const size_t lenB);
/**
* Generic union function.
*/
size_t union_uint32(const uint32_t *set_1, size_t size_1, const uint32_t *set_2,
size_t size_2, uint32_t *buffer);
/**
* A fast SSE-based union function.
*/
uint32_t union_vector16(const uint16_t *set_1, uint32_t size_1,
const uint16_t *set_2, uint32_t size_2,
uint16_t *buffer);
/**
* A fast SSE-based XOR function.
*/
uint32_t xor_vector16(const uint16_t *array1, uint32_t length1,
const uint16_t *array2, uint32_t length2,
uint16_t *output);
/**
* A fast SSE-based difference function.
*/
int32_t difference_vector16(const uint16_t *A, size_t s_a, const uint16_t *B,
size_t s_b, uint16_t *C);
/**
* Generic union function, returns just the cardinality.
*/
size_t union_uint32_card(const uint32_t *set_1, size_t size_1,
const uint32_t *set_2, size_t size_2);
/**
* combines union_uint16 and union_vector16 optimally
*/
size_t fast_union_uint16(const uint16_t *set_1, size_t size_1,
const uint16_t *set_2, size_t size_2,
uint16_t *buffer);
bool memequals(const void *s1, const void *s2, size_t n);
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
#endif
+241
View File
@@ -0,0 +1,241 @@
#ifndef ART_ART_H
#define ART_ART_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/*
* This file contains an implementation of an Adaptive Radix Tree as described
* in https://db.in.tum.de/~leis/papers/ART.pdf.
*
* The ART contains the keys in _byte lexographical_ order.
*
* Other features:
* * Fixed 48 bit key length: all keys are assumed to be be 48 bits in size.
* This allows us to put the key and key prefixes directly in nodes, reducing
* indirection at no additional memory overhead.
* * Key compression: the only inner nodes created are at points where key
* chunks _differ_. This means that if there are two entries with different
* high 48 bits, then there is only one inner node containing the common key
* prefix, and two leaves.
* * Mostly pointer-free: nodes are referred to by index rather than pointer,
* so that the structure can be deserialized with a backing buffer.
*/
// Fixed length of keys in the ART. All keys are assumed to be of this length.
#define ART_KEY_BYTES 6
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
typedef uint8_t art_key_chunk_t;
// Internal node reference type. Contains the node typecode in the low 8 bits,
// and the index in the relevant node array in the high 48 bits. Has a value of
// CROARING_ART_NULL_REF when pointing to a non-existent node.
typedef uint64_t art_ref_t;
typedef void art_node_t;
/**
* The ART is empty when root is a null ref.
*
* Each node type has its own dynamic array of node structs, indexed by
* art_ref_t. The arrays are expanded as needed, and shrink only when
* `shrink_to_fit` is called.
*/
typedef struct art_s {
art_ref_t root;
// Indexed by node typecode, thus 1 larger than they need to be for
// convenience. `first_free` indicates the index where the first free node
// lives, which may be equal to the capacity.
uint64_t first_free[6];
uint64_t capacities[6];
art_node_t *nodes[6];
} art_t;
typedef uint64_t art_val_t;
/**
* Compares two keys, returns their relative order:
* * Key 1 < key 2: returns a negative value
* * Key 1 == key 2: returns 0
* * Key 1 > key 2: returns a positive value
*/
int art_compare_keys(const art_key_chunk_t key1[],
const art_key_chunk_t key2[]);
/**
* Initializes the ART.
*/
void art_init_cleared(art_t *art);
/**
* Inserts the given key and value. Returns a pointer to the value inserted,
* valid as long as the ART is not modified.
*/
art_val_t *art_insert(art_t *art, const art_key_chunk_t *key, art_val_t val);
/**
* Returns true if a value was erased. Sets `*erased_val` to the value erased,
* if any.
*/
bool art_erase(art_t *art, const art_key_chunk_t *key, art_val_t *erased_val);
/**
* Returns the value associated with the given key, NULL if not found.
*/
art_val_t *art_find(const art_t *art, const art_key_chunk_t *key);
/**
* Returns true if the ART is empty.
*/
bool art_is_empty(const art_t *art);
/**
* Frees the contents of the ART. Should not be called when using
* `art_deserialize_frozen_safe`.
*/
void art_free(art_t *art);
/**
* Prints the ART using printf, useful for debugging.
*/
void art_printf(const art_t *art);
/**
* Callback for validating the value stored in a leaf. `context` is a
* user-provided value passed to the callback without modification.
*
* Should return true if the value is valid, false otherwise
* If false is returned, `*reason` should be set to a static string describing
* the reason for the failure.
*/
typedef bool (*art_validate_cb_t)(const art_val_t val, const char **reason,
void *context);
/**
* Validate the ART tree, ensuring it is internally consistent. `context` is a
* user-provided value passed to the callback without modification.
*/
bool art_internal_validate(const art_t *art, const char **reason,
art_validate_cb_t validate_cb, void *context);
/**
* ART-internal iterator bookkeeping. Users should treat this as an opaque type.
*/
typedef struct art_iterator_frame_s {
art_ref_t ref;
uint8_t index_in_node;
} art_iterator_frame_t;
/**
* Users should only access `key` and `value` in iterators. The iterator is
* valid when `value != NULL`.
*/
typedef struct art_iterator_s {
art_key_chunk_t key[ART_KEY_BYTES];
art_val_t *value;
art_t *art;
uint8_t depth; // Key depth
uint8_t frame; // Node depth
// State for each node in the ART the iterator has travelled from the root.
// This is `ART_KEY_BYTES + 1` because it includes state for the leaf too.
art_iterator_frame_t frames[ART_KEY_BYTES + 1];
} art_iterator_t;
/**
* Creates an iterator initialzed to the first or last entry in the ART,
* depending on `first`. The iterator is not valid if there are no entries in
* the ART.
*/
art_iterator_t art_init_iterator(art_t *art, bool first);
/**
* Returns an initialized iterator positioned at a key equal to or greater than
* the given key, if it exists.
*/
art_iterator_t art_lower_bound(art_t *art, const art_key_chunk_t *key);
/**
* Returns an initialized iterator positioned at a key greater than the given
* key, if it exists.
*/
art_iterator_t art_upper_bound(art_t *art, const art_key_chunk_t *key);
/**
* The following iterator movement functions return true if a new entry was
* encountered.
*/
bool art_iterator_move(art_iterator_t *iterator, bool forward);
bool art_iterator_next(art_iterator_t *iterator);
bool art_iterator_prev(art_iterator_t *iterator);
/**
* Moves the iterator forward to a key equal to or greater than the given key.
*/
bool art_iterator_lower_bound(art_iterator_t *iterator,
const art_key_chunk_t *key);
/**
* Insert the value and positions the iterator at the key.
*/
void art_iterator_insert(art_iterator_t *iterator, const art_key_chunk_t *key,
art_val_t val);
/**
* Erase the value pointed at by the iterator. Moves the iterator to the next
* leaf.
* Returns true if a value was erased. Sets `*erased_val` to the value erased,
* if any.
*/
bool art_iterator_erase(art_iterator_t *iterator, art_val_t *erased_val);
/**
* Shrinks the internal arrays in the ART to remove any unused elements. Returns
* the number of bytes freed.
*/
size_t art_shrink_to_fit(art_t *art);
/**
* Returns true if the ART has no unused elements.
*/
bool art_is_shrunken(const art_t *art);
/**
* Returns the serialized size in bytes.
* Requires `art_shrink_to_fit` to be called first.
*/
size_t art_size_in_bytes(const art_t *art);
/**
* Serializes the ART and returns the number of bytes written. Returns 0 on
* error. Requires `art_shrink_to_fit` to be called first.
*/
size_t art_serialize(const art_t *art, char *buf);
/**
* Deserializes the ART from a serialized buffer, reading up to `maxbytes`
* bytes. Returns 0 on error. Requires `buf` to be 8 byte aligned.
*
* An ART deserialized in this way should only be used in a readonly context.The
* underlying buffer must not be freed before the ART. `art_free` should not be
* called on the ART deserialized in this way.
*/
size_t art_frozen_view(const char *buf, size_t maxbytes, art_t *art);
#ifdef __cplusplus
} // extern "C"
} // namespace roaring
} // namespace internal
#endif
#endif
+312
View File
@@ -0,0 +1,312 @@
/*
* bitset.h
*
* This bitset is a general-purpose dynamic bitmap storing bits in a contiguous
* array of 64-bit words. The array field points to the word buffer, arraysize
* records how many words are currently in use, and capacity records how many
* words are allocated.
*
* Unlike the fixed 16-bit-domain container bitset, this structure can grow to
* cover an arbitrary number of bit positions. It is useful when callers need a
* resizable bitmap with efficient bitwise operations, scans, and shifts over a
* larger or runtime-defined domain.
*/
#ifndef CROARING_CBITSET_BITSET_H
#define CROARING_CBITSET_BITSET_H
// For compatibility with MSVC with the use of `restrict`
#ifdef __cplusplus
#define CROARING_CBITSET_RESTRICT
#elif (__STDC_VERSION__ >= 199901L) || \
(defined(__GNUC__) && defined(__STDC_VERSION__))
#define CROARING_CBITSET_RESTRICT restrict
#else
#define CROARING_CBITSET_RESTRICT
#endif
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <roaring/portability.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace api {
#endif
struct bitset_s {
uint64_t *CROARING_CBITSET_RESTRICT array;
/* For simplicity and performance, we prefer to have a size and a capacity
* that is a multiple of 64 bits. Thus we only track the size and the
* capacity in terms of 64-bit words allocated */
size_t arraysize;
size_t capacity;
};
typedef struct bitset_s bitset_t;
/* Create a new bitset. Return NULL in case of failure. */
bitset_t *bitset_create(void);
/* Create a new bitset able to contain size bits. Return NULL in case of
* failure. */
bitset_t *bitset_create_with_capacity(size_t size);
/* Free memory. */
void bitset_free(bitset_t *bitset);
/* Set all bits to zero. */
void bitset_clear(bitset_t *bitset);
/* Set all bits to one. */
void bitset_fill(bitset_t *bitset);
/* Create a copy */
bitset_t *bitset_copy(const bitset_t *bitset);
/* For advanced users: Resize the bitset so that it can support newarraysize *
* 64 bits. Return true in case of success, false for failure. Pad with zeroes
* new buffer areas if requested. */
bool bitset_resize(bitset_t *bitset, size_t newarraysize, bool padwithzeroes);
/* returns how many bytes of memory the backend buffer uses */
inline size_t bitset_size_in_bytes(const bitset_t *bitset) {
return bitset->arraysize * sizeof(uint64_t);
}
/* returns how many bits can be accessed */
inline size_t bitset_size_in_bits(const bitset_t *bitset) {
return bitset->arraysize * 64;
}
/* returns how many words (64-bit) of memory the backend buffer uses */
inline size_t bitset_size_in_words(const bitset_t *bitset) {
return bitset->arraysize;
}
/* For advanced users: Grow the bitset so that it can support newarraysize * 64
* bits with padding. Return true in case of success, false for failure. */
bool bitset_grow(bitset_t *bitset, size_t newarraysize);
/* attempts to recover unused memory, return false in case of
* roaring_reallocation failure */
bool bitset_trim(bitset_t *bitset);
/* shifts all bits by 's' positions so that the bitset representing values
* 1,2,10 would represent values 1+s, 2+s, 10+s */
void bitset_shift_left(bitset_t *bitset, size_t s);
/* shifts all bits by 's' positions so that the bitset representing values
* 1,2,10 would represent values 1-s, 2-s, 10-s, negative values are deleted */
void bitset_shift_right(bitset_t *bitset, size_t s);
/* Set the ith bit. Attempts to resize the bitset if needed (may silently fail)
*/
inline void bitset_set(bitset_t *bitset, size_t i) {
size_t shiftedi = i / 64;
if (shiftedi >= bitset->arraysize) {
if (!bitset_grow(bitset, shiftedi + 1)) {
return;
}
}
bitset->array[shiftedi] |= ((uint64_t)1) << (i % 64);
}
/* Set the ith bit to the specified value. Attempts to resize the bitset if
* needed (may silently fail) */
inline void bitset_set_to_value(bitset_t *bitset, size_t i, bool flag) {
size_t shiftedi = i / 64;
uint64_t mask = ((uint64_t)1) << (i % 64);
uint64_t dynmask = ((uint64_t)flag) << (i % 64);
if (shiftedi >= bitset->arraysize) {
if (!bitset_grow(bitset, shiftedi + 1)) {
return;
}
}
uint64_t w = bitset->array[shiftedi];
w &= ~mask;
w |= dynmask;
bitset->array[shiftedi] = w;
}
/* Get the value of the ith bit. */
inline bool bitset_get(const bitset_t *bitset, size_t i) {
size_t shiftedi = i / 64;
if (shiftedi >= bitset->arraysize) {
return false;
}
return (bitset->array[shiftedi] & (((uint64_t)1) << (i % 64))) != 0;
}
/* Count number of bits set. */
size_t bitset_count(const bitset_t *bitset);
/* Returns true if no bit is set. */
bool bitset_empty(const bitset_t *bitset);
/* Find the index of the first bit set. Or SIZE_MAX if the bitset is empty. */
size_t bitset_minimum(const bitset_t *bitset);
/* Find the index of the last bit set. Or zero if the bitset is empty. */
size_t bitset_maximum(const bitset_t *bitset);
/* compute the union in-place (to b1), returns true if successful, to generate a
* new bitset first call bitset_copy */
bool bitset_inplace_union(bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2);
/* report the size of the union (without materializing it) */
size_t bitset_union_count(const bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2);
/* compute the intersection in-place (to b1), to generate a new bitset first
* call bitset_copy */
void bitset_inplace_intersection(bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2);
/* report the size of the intersection (without materializing it) */
size_t bitset_intersection_count(const bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2);
/* returns true if the bitsets contain no common elements */
bool bitsets_disjoint(const bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2);
/* returns true if the bitsets contain any common elements */
bool bitsets_intersect(const bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2);
/* returns true if b1 contains all of the set bits of b2 */
bool bitset_contains_all(const bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2);
/* compute the difference in-place (to b1), to generate a new bitset first call
* bitset_copy */
void bitset_inplace_difference(bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2);
/* compute the size of the difference */
size_t bitset_difference_count(const bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2);
/* compute the symmetric difference in-place (to b1), return true if successful,
* to generate a new bitset first call bitset_copy */
bool bitset_inplace_symmetric_difference(
bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2);
/* compute the size of the symmetric difference */
size_t bitset_symmetric_difference_count(
const bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2);
/* iterate over the set bits
like so :
for(size_t i = 0; bitset_next_set_bit(b,&i) ; i++) {
//.....
}
*/
inline bool bitset_next_set_bit(const bitset_t *bitset, size_t *i) {
size_t x = *i / 64;
if (x >= bitset->arraysize) {
return false;
}
uint64_t w = bitset->array[x];
w >>= (*i & 63);
if (w != 0) {
*i += roaring_trailing_zeroes(w);
return true;
}
x++;
while (x < bitset->arraysize) {
w = bitset->array[x];
if (w != 0) {
*i = x * 64 + roaring_trailing_zeroes(w);
return true;
}
x++;
}
return false;
}
/* iterate over the set bits
like so :
size_t buffer[256];
size_t howmany = 0;
for(size_t startfrom = 0; (howmany = bitset_next_set_bits(b,buffer,256,
&startfrom)) > 0 ; startfrom++) {
//.....
}
*/
inline size_t bitset_next_set_bits(const bitset_t *bitset, size_t *buffer,
size_t capacity, size_t *startfrom) {
if (capacity == 0) return 0; // sanity check
size_t x = *startfrom / 64;
if (x >= bitset->arraysize) {
return 0; // nothing more to iterate over
}
uint64_t w = bitset->array[x];
// unset low bits inside the word less than *startfrom
w &= ~((UINT64_C(1) << (*startfrom & 63)) - 1);
size_t howmany = 0;
size_t base = x << 6;
while (howmany < capacity) {
while (w != 0) {
uint64_t t = w & (~w + 1);
int r = roaring_trailing_zeroes(w);
buffer[howmany++] = r + base;
if (howmany == capacity) goto end;
w ^= t;
}
x += 1;
if (x == bitset->arraysize) {
break;
}
base += 64;
w = bitset->array[x];
}
end:
if (howmany > 0) {
*startfrom = buffer[howmany - 1];
}
return howmany;
}
typedef bool (*bitset_iterator)(size_t value, void *param);
// return true if uninterrupted
inline bool bitset_for_each(const bitset_t *b, bitset_iterator iterator,
void *ptr) {
size_t base = 0;
for (size_t i = 0; i < b->arraysize; ++i) {
uint64_t w = b->array[i];
while (w != 0) {
uint64_t t = w & (~w + 1);
int r = roaring_trailing_zeroes(w);
if (!iterator(r + base, ptr)) return false;
w ^= t;
}
base += 64;
}
return true;
}
inline void bitset_print(const bitset_t *b) {
printf("{");
for (size_t i = 0; bitset_next_set_bit(b, &i); i++) {
printf("%zu, ", i);
}
printf("}");
}
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace api {
#endif
#endif
+731
View File
@@ -0,0 +1,731 @@
/*
* bitset_util.h
*
* This header collects low-level utility functions for operating on raw
* bitsets represented as arrays of 64-bit words. It includes helpers for
* setting, clearing, flipping, counting, extracting, and combining bit ranges,
* along with architecture-specific SIMD implementations for performance-
* critical routines.
*
* In particular, this file contains accelerated extraction and population-
* count code paths for x64 targets using AVX2 and, when supported by the
* compiler and hardware, AVX-512.
*/
#ifndef CROARING_BITSET_UTIL_H
#define CROARING_BITSET_UTIL_H
#include <stdint.h>
#include <roaring/portability.h>
#include <roaring/utilasm.h>
#if CROARING_IS_X64
#ifndef CROARING_COMPILER_SUPPORTS_AVX512
#error "CROARING_COMPILER_SUPPORTS_AVX512 needs to be defined."
#endif // CROARING_COMPILER_SUPPORTS_AVX512
#endif
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuninitialized"
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/*
* Set all bits in indexes [begin,end) to true.
*/
static inline void bitset_set_range(uint64_t *words, uint32_t start,
uint32_t end) {
if (start == end) return;
uint32_t firstword = start / 64;
uint32_t endword = (end - 1) / 64;
if (firstword == endword) {
words[firstword] |= ((~UINT64_C(0)) << (start % 64)) &
((~UINT64_C(0)) >> ((~end + 1) % 64));
return;
}
words[firstword] |= (~UINT64_C(0)) << (start % 64);
for (uint32_t i = firstword + 1; i < endword; i++) {
words[i] = ~UINT64_C(0);
}
words[endword] |= (~UINT64_C(0)) >> ((~end + 1) % 64);
}
/*
* Find the cardinality of the bitset in [begin,begin+lenminusone]
*/
static inline int bitset_lenrange_cardinality(const uint64_t *words,
uint32_t start,
uint32_t lenminusone) {
uint32_t firstword = start / 64;
uint32_t endword = (start + lenminusone) / 64;
if (firstword == endword) {
return roaring_hamming(words[firstword] &
((~UINT64_C(0)) >> ((63 - lenminusone) % 64))
<< (start % 64));
}
int answer =
roaring_hamming(words[firstword] & ((~UINT64_C(0)) << (start % 64)));
for (uint32_t i = firstword + 1; i < endword; i++) {
answer += roaring_hamming(words[i]);
}
answer += roaring_hamming(words[endword] &
(~UINT64_C(0)) >>
(((~start + 1) - lenminusone - 1) % 64));
return answer;
}
/*
* Check whether the cardinality of the bitset in [begin,begin+lenminusone] is 0
*/
static inline bool bitset_lenrange_empty(const uint64_t *words, uint32_t start,
uint32_t lenminusone) {
uint32_t firstword = start / 64;
uint32_t endword = (start + lenminusone) / 64;
if (firstword == endword) {
return (words[firstword] & ((~UINT64_C(0)) >> ((63 - lenminusone) % 64))
<< (start % 64)) == 0;
}
if (((words[firstword] & ((~UINT64_C(0)) << (start % 64)))) != 0) {
return false;
}
for (uint32_t i = firstword + 1; i < endword; i++) {
if (words[i] != 0) {
return false;
}
}
if ((words[endword] &
(~UINT64_C(0)) >> (((~start + 1) - lenminusone - 1) % 64)) != 0) {
return false;
}
return true;
}
/*
* Set all bits in indexes [begin,begin+lenminusone] to true.
*/
static inline void bitset_set_lenrange(uint64_t *words, uint32_t start,
uint32_t lenminusone) {
uint32_t firstword = start / 64;
uint32_t endword = (start + lenminusone) / 64;
if (firstword == endword) {
words[firstword] |= ((~UINT64_C(0)) >> ((63 - lenminusone) % 64))
<< (start % 64);
return;
}
uint64_t temp = words[endword];
words[firstword] |= (~UINT64_C(0)) << (start % 64);
for (uint32_t i = firstword + 1; i < endword; i += 2)
words[i] = words[i + 1] = ~UINT64_C(0);
words[endword] =
temp | (~UINT64_C(0)) >> (((~start + 1) - lenminusone - 1) % 64);
}
/*
* Flip all the bits in indexes [begin,end).
*/
static inline void bitset_flip_range(uint64_t *words, uint32_t start,
uint32_t end) {
if (start == end) return;
uint32_t firstword = start / 64;
uint32_t endword = (end - 1) / 64;
words[firstword] ^= ~((~UINT64_C(0)) << (start % 64));
for (uint32_t i = firstword; i < endword; i++) {
words[i] = ~words[i];
}
words[endword] ^= ((~UINT64_C(0)) >> ((~end + 1) % 64));
}
/*
* Set all bits in indexes [begin,end) to false.
*/
static inline void bitset_reset_range(uint64_t *words, uint32_t start,
uint32_t end) {
if (start == end) return;
uint32_t firstword = start / 64;
uint32_t endword = (end - 1) / 64;
if (firstword == endword) {
words[firstword] &= ~(((~UINT64_C(0)) << (start % 64)) &
((~UINT64_C(0)) >> ((~end + 1) % 64)));
return;
}
words[firstword] &= ~((~UINT64_C(0)) << (start % 64));
for (uint32_t i = firstword + 1; i < endword; i++) {
words[i] = UINT64_C(0);
}
words[endword] &= ~((~UINT64_C(0)) >> ((~end + 1) % 64));
}
/*
* Given a bitset containing "length" 64-bit words, write out the position
* of all the set bits to "out", values start at "base".
*
* The "out" pointer should be sufficient to store the actual number of bits
* set.
*
* Returns how many values were actually decoded.
*
* This function should only be expected to be faster than
* bitset_extract_setbits
* when the density of the bitset is high.
*
* This function uses AVX2 decoding.
*/
size_t bitset_extract_setbits_avx2(const uint64_t *words, size_t length,
uint32_t *out, size_t outcapacity,
uint32_t base);
size_t bitset_extract_setbits_avx512(const uint64_t *words, size_t length,
uint32_t *out, size_t outcapacity,
uint32_t base);
/*
* Given a bitset containing "length" 64-bit words, write out the position
* of all the set bits to "out", values start at "base".
*
* The "out" pointer should be sufficient to store the actual number of bits
*set.
*
* Returns how many values were actually decoded.
*/
size_t bitset_extract_setbits(const uint64_t *words, size_t length,
uint32_t *out, uint32_t base);
/*
* Given a bitset containing "length" 64-bit words, write out the position
* of all the set bits to "out" as 16-bit integers, values start at "base" (can
*be set to zero)
*
* The "out" pointer should be sufficient to store the actual number of bits
*set.
*
* Returns how many values were actually decoded.
*
* This function should only be expected to be faster than
*bitset_extract_setbits_uint16
* when the density of the bitset is high.
*
* This function uses SSE decoding.
*/
size_t bitset_extract_setbits_sse_uint16(const uint64_t *words, size_t length,
uint16_t *out, size_t outcapacity,
uint16_t base);
size_t bitset_extract_setbits_avx512_uint16(const uint64_t *words,
size_t length, uint16_t *out,
size_t outcapacity, uint16_t base);
/*
* Given a bitset containing "length" 64-bit words, write out the position
* of all the set bits to "out", values start at "base"
* (can be set to zero)
*
* The "out" pointer should be sufficient to store the actual number of bits
*set.
*
* Returns how many values were actually decoded.
*/
size_t bitset_extract_setbits_uint16(const uint64_t *words, size_t length,
uint16_t *out, uint16_t base);
/*
* Given two bitsets containing "length" 64-bit words, write out the position
* of all the common set bits to "out", values start at "base"
* (can be set to zero)
*
* The "out" pointer should be sufficient to store the actual number of bits
* set.
*
* Returns how many values were actually decoded.
*/
size_t bitset_extract_intersection_setbits_uint16(
const uint64_t *__restrict__ words1, const uint64_t *__restrict__ words2,
size_t length, uint16_t *out, uint16_t base);
/*
* Given a bitset having cardinality card, set all bit values in the list (there
* are length of them)
* and return the updated cardinality. This evidently assumes that the bitset
* already contained data.
*/
uint64_t bitset_set_list_withcard(uint64_t *words, uint64_t card,
const uint16_t *list, uint64_t length);
/*
* Given a bitset, set all bit values in the list (there
* are length of them).
*/
void bitset_set_list(uint64_t *words, const uint16_t *list, uint64_t length);
/*
* Given a bitset having cardinality card, unset all bit values in the list
* (there are length of them)
* and return the updated cardinality. This evidently assumes that the bitset
* already contained data.
*/
uint64_t bitset_clear_list(uint64_t *words, uint64_t card, const uint16_t *list,
uint64_t length);
/*
* Given a bitset having cardinality card, toggle all bit values in the list
* (there are length of them)
* and return the updated cardinality. This evidently assumes that the bitset
* already contained data.
*/
uint64_t bitset_flip_list_withcard(uint64_t *words, uint64_t card,
const uint16_t *list, uint64_t length);
void bitset_flip_list(uint64_t *words, const uint16_t *list, uint64_t length);
#if CROARING_IS_X64
/***
* BEGIN Harley-Seal popcount functions.
*/
CROARING_TARGET_AVX2
/**
* Compute the population count of a 256-bit word
* This is not especially fast, but it is convenient as part of other functions.
*/
static inline __m256i popcount256(__m256i v) {
const __m256i lookuppos = _mm256_setr_epi8(
/* 0 */ 4 + 0, /* 1 */ 4 + 1, /* 2 */ 4 + 1, /* 3 */ 4 + 2,
/* 4 */ 4 + 1, /* 5 */ 4 + 2, /* 6 */ 4 + 2, /* 7 */ 4 + 3,
/* 8 */ 4 + 1, /* 9 */ 4 + 2, /* a */ 4 + 2, /* b */ 4 + 3,
/* c */ 4 + 2, /* d */ 4 + 3, /* e */ 4 + 3, /* f */ 4 + 4,
/* 0 */ 4 + 0, /* 1 */ 4 + 1, /* 2 */ 4 + 1, /* 3 */ 4 + 2,
/* 4 */ 4 + 1, /* 5 */ 4 + 2, /* 6 */ 4 + 2, /* 7 */ 4 + 3,
/* 8 */ 4 + 1, /* 9 */ 4 + 2, /* a */ 4 + 2, /* b */ 4 + 3,
/* c */ 4 + 2, /* d */ 4 + 3, /* e */ 4 + 3, /* f */ 4 + 4);
const __m256i lookupneg = _mm256_setr_epi8(
/* 0 */ 4 - 0, /* 1 */ 4 - 1, /* 2 */ 4 - 1, /* 3 */ 4 - 2,
/* 4 */ 4 - 1, /* 5 */ 4 - 2, /* 6 */ 4 - 2, /* 7 */ 4 - 3,
/* 8 */ 4 - 1, /* 9 */ 4 - 2, /* a */ 4 - 2, /* b */ 4 - 3,
/* c */ 4 - 2, /* d */ 4 - 3, /* e */ 4 - 3, /* f */ 4 - 4,
/* 0 */ 4 - 0, /* 1 */ 4 - 1, /* 2 */ 4 - 1, /* 3 */ 4 - 2,
/* 4 */ 4 - 1, /* 5 */ 4 - 2, /* 6 */ 4 - 2, /* 7 */ 4 - 3,
/* 8 */ 4 - 1, /* 9 */ 4 - 2, /* a */ 4 - 2, /* b */ 4 - 3,
/* c */ 4 - 2, /* d */ 4 - 3, /* e */ 4 - 3, /* f */ 4 - 4);
const __m256i low_mask = _mm256_set1_epi8(0x0f);
const __m256i lo = _mm256_and_si256(v, low_mask);
const __m256i hi = _mm256_and_si256(_mm256_srli_epi16(v, 4), low_mask);
const __m256i popcnt1 = _mm256_shuffle_epi8(lookuppos, lo);
const __m256i popcnt2 = _mm256_shuffle_epi8(lookupneg, hi);
return _mm256_sad_epu8(popcnt1, popcnt2);
}
CROARING_UNTARGET_AVX2
CROARING_TARGET_AVX2
/**
* Simple CSA over 256 bits
*/
static inline void CSA(__m256i *h, __m256i *l, __m256i a, __m256i b,
__m256i c) {
const __m256i u = _mm256_xor_si256(a, b);
*h = _mm256_or_si256(_mm256_and_si256(a, b), _mm256_and_si256(u, c));
*l = _mm256_xor_si256(u, c);
}
CROARING_UNTARGET_AVX2
CROARING_TARGET_AVX2
/**
* Fast Harley-Seal AVX population count function
*/
inline static uint64_t avx2_harley_seal_popcount256(const __m256i *data,
const uint64_t size) {
__m256i total = _mm256_setzero_si256();
__m256i ones = _mm256_setzero_si256();
__m256i twos = _mm256_setzero_si256();
__m256i fours = _mm256_setzero_si256();
__m256i eights = _mm256_setzero_si256();
__m256i sixteens = _mm256_setzero_si256();
__m256i twosA, twosB, foursA, foursB, eightsA, eightsB;
const uint64_t limit = size - size % 16;
uint64_t i = 0;
for (; i < limit; i += 16) {
CSA(&twosA, &ones, ones, _mm256_lddqu_si256(data + i),
_mm256_lddqu_si256(data + i + 1));
CSA(&twosB, &ones, ones, _mm256_lddqu_si256(data + i + 2),
_mm256_lddqu_si256(data + i + 3));
CSA(&foursA, &twos, twos, twosA, twosB);
CSA(&twosA, &ones, ones, _mm256_lddqu_si256(data + i + 4),
_mm256_lddqu_si256(data + i + 5));
CSA(&twosB, &ones, ones, _mm256_lddqu_si256(data + i + 6),
_mm256_lddqu_si256(data + i + 7));
CSA(&foursB, &twos, twos, twosA, twosB);
CSA(&eightsA, &fours, fours, foursA, foursB);
CSA(&twosA, &ones, ones, _mm256_lddqu_si256(data + i + 8),
_mm256_lddqu_si256(data + i + 9));
CSA(&twosB, &ones, ones, _mm256_lddqu_si256(data + i + 10),
_mm256_lddqu_si256(data + i + 11));
CSA(&foursA, &twos, twos, twosA, twosB);
CSA(&twosA, &ones, ones, _mm256_lddqu_si256(data + i + 12),
_mm256_lddqu_si256(data + i + 13));
CSA(&twosB, &ones, ones, _mm256_lddqu_si256(data + i + 14),
_mm256_lddqu_si256(data + i + 15));
CSA(&foursB, &twos, twos, twosA, twosB);
CSA(&eightsB, &fours, fours, foursA, foursB);
CSA(&sixteens, &eights, eights, eightsA, eightsB);
total = _mm256_add_epi64(total, popcount256(sixteens));
}
total = _mm256_slli_epi64(total, 4); // * 16
total = _mm256_add_epi64(
total, _mm256_slli_epi64(popcount256(eights), 3)); // += 8 * ...
total = _mm256_add_epi64(
total, _mm256_slli_epi64(popcount256(fours), 2)); // += 4 * ...
total = _mm256_add_epi64(
total, _mm256_slli_epi64(popcount256(twos), 1)); // += 2 * ...
total = _mm256_add_epi64(total, popcount256(ones));
for (; i < size; i++)
total =
_mm256_add_epi64(total, popcount256(_mm256_lddqu_si256(data + i)));
return (uint64_t)(_mm256_extract_epi64(total, 0)) +
(uint64_t)(_mm256_extract_epi64(total, 1)) +
(uint64_t)(_mm256_extract_epi64(total, 2)) +
(uint64_t)(_mm256_extract_epi64(total, 3));
}
CROARING_UNTARGET_AVX2
#define CROARING_AVXPOPCNTFNC(opname, avx_intrinsic) \
static inline uint64_t avx2_harley_seal_popcount256_##opname( \
const __m256i *data1, const __m256i *data2, const uint64_t size) { \
__m256i total = _mm256_setzero_si256(); \
__m256i ones = _mm256_setzero_si256(); \
__m256i twos = _mm256_setzero_si256(); \
__m256i fours = _mm256_setzero_si256(); \
__m256i eights = _mm256_setzero_si256(); \
__m256i sixteens = _mm256_setzero_si256(); \
__m256i twosA, twosB, foursA, foursB, eightsA, eightsB; \
__m256i A1, A2; \
const uint64_t limit = size - size % 16; \
uint64_t i = 0; \
for (; i < limit; i += 16) { \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i), \
_mm256_lddqu_si256(data2 + i)); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 1), \
_mm256_lddqu_si256(data2 + i + 1)); \
CSA(&twosA, &ones, ones, A1, A2); \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 2), \
_mm256_lddqu_si256(data2 + i + 2)); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 3), \
_mm256_lddqu_si256(data2 + i + 3)); \
CSA(&twosB, &ones, ones, A1, A2); \
CSA(&foursA, &twos, twos, twosA, twosB); \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 4), \
_mm256_lddqu_si256(data2 + i + 4)); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 5), \
_mm256_lddqu_si256(data2 + i + 5)); \
CSA(&twosA, &ones, ones, A1, A2); \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 6), \
_mm256_lddqu_si256(data2 + i + 6)); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 7), \
_mm256_lddqu_si256(data2 + i + 7)); \
CSA(&twosB, &ones, ones, A1, A2); \
CSA(&foursB, &twos, twos, twosA, twosB); \
CSA(&eightsA, &fours, fours, foursA, foursB); \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 8), \
_mm256_lddqu_si256(data2 + i + 8)); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 9), \
_mm256_lddqu_si256(data2 + i + 9)); \
CSA(&twosA, &ones, ones, A1, A2); \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 10), \
_mm256_lddqu_si256(data2 + i + 10)); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 11), \
_mm256_lddqu_si256(data2 + i + 11)); \
CSA(&twosB, &ones, ones, A1, A2); \
CSA(&foursA, &twos, twos, twosA, twosB); \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 12), \
_mm256_lddqu_si256(data2 + i + 12)); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 13), \
_mm256_lddqu_si256(data2 + i + 13)); \
CSA(&twosA, &ones, ones, A1, A2); \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 14), \
_mm256_lddqu_si256(data2 + i + 14)); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 15), \
_mm256_lddqu_si256(data2 + i + 15)); \
CSA(&twosB, &ones, ones, A1, A2); \
CSA(&foursB, &twos, twos, twosA, twosB); \
CSA(&eightsB, &fours, fours, foursA, foursB); \
CSA(&sixteens, &eights, eights, eightsA, eightsB); \
total = _mm256_add_epi64(total, popcount256(sixteens)); \
} \
total = _mm256_slli_epi64(total, 4); \
total = _mm256_add_epi64(total, \
_mm256_slli_epi64(popcount256(eights), 3)); \
total = \
_mm256_add_epi64(total, _mm256_slli_epi64(popcount256(fours), 2)); \
total = \
_mm256_add_epi64(total, _mm256_slli_epi64(popcount256(twos), 1)); \
total = _mm256_add_epi64(total, popcount256(ones)); \
for (; i < size; i++) { \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i), \
_mm256_lddqu_si256(data2 + i)); \
total = _mm256_add_epi64(total, popcount256(A1)); \
} \
return (uint64_t)(_mm256_extract_epi64(total, 0)) + \
(uint64_t)(_mm256_extract_epi64(total, 1)) + \
(uint64_t)(_mm256_extract_epi64(total, 2)) + \
(uint64_t)(_mm256_extract_epi64(total, 3)); \
} \
static inline uint64_t avx2_harley_seal_popcount256andstore_##opname( \
const __m256i *__restrict__ data1, const __m256i *__restrict__ data2, \
__m256i *__restrict__ out, const uint64_t size) { \
__m256i total = _mm256_setzero_si256(); \
__m256i ones = _mm256_setzero_si256(); \
__m256i twos = _mm256_setzero_si256(); \
__m256i fours = _mm256_setzero_si256(); \
__m256i eights = _mm256_setzero_si256(); \
__m256i sixteens = _mm256_setzero_si256(); \
__m256i twosA, twosB, foursA, foursB, eightsA, eightsB; \
__m256i A1, A2; \
const uint64_t limit = size - size % 16; \
uint64_t i = 0; \
for (; i < limit; i += 16) { \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i), \
_mm256_lddqu_si256(data2 + i)); \
_mm256_storeu_si256(out + i, A1); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 1), \
_mm256_lddqu_si256(data2 + i + 1)); \
_mm256_storeu_si256(out + i + 1, A2); \
CSA(&twosA, &ones, ones, A1, A2); \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 2), \
_mm256_lddqu_si256(data2 + i + 2)); \
_mm256_storeu_si256(out + i + 2, A1); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 3), \
_mm256_lddqu_si256(data2 + i + 3)); \
_mm256_storeu_si256(out + i + 3, A2); \
CSA(&twosB, &ones, ones, A1, A2); \
CSA(&foursA, &twos, twos, twosA, twosB); \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 4), \
_mm256_lddqu_si256(data2 + i + 4)); \
_mm256_storeu_si256(out + i + 4, A1); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 5), \
_mm256_lddqu_si256(data2 + i + 5)); \
_mm256_storeu_si256(out + i + 5, A2); \
CSA(&twosA, &ones, ones, A1, A2); \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 6), \
_mm256_lddqu_si256(data2 + i + 6)); \
_mm256_storeu_si256(out + i + 6, A1); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 7), \
_mm256_lddqu_si256(data2 + i + 7)); \
_mm256_storeu_si256(out + i + 7, A2); \
CSA(&twosB, &ones, ones, A1, A2); \
CSA(&foursB, &twos, twos, twosA, twosB); \
CSA(&eightsA, &fours, fours, foursA, foursB); \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 8), \
_mm256_lddqu_si256(data2 + i + 8)); \
_mm256_storeu_si256(out + i + 8, A1); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 9), \
_mm256_lddqu_si256(data2 + i + 9)); \
_mm256_storeu_si256(out + i + 9, A2); \
CSA(&twosA, &ones, ones, A1, A2); \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 10), \
_mm256_lddqu_si256(data2 + i + 10)); \
_mm256_storeu_si256(out + i + 10, A1); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 11), \
_mm256_lddqu_si256(data2 + i + 11)); \
_mm256_storeu_si256(out + i + 11, A2); \
CSA(&twosB, &ones, ones, A1, A2); \
CSA(&foursA, &twos, twos, twosA, twosB); \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 12), \
_mm256_lddqu_si256(data2 + i + 12)); \
_mm256_storeu_si256(out + i + 12, A1); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 13), \
_mm256_lddqu_si256(data2 + i + 13)); \
_mm256_storeu_si256(out + i + 13, A2); \
CSA(&twosA, &ones, ones, A1, A2); \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 14), \
_mm256_lddqu_si256(data2 + i + 14)); \
_mm256_storeu_si256(out + i + 14, A1); \
A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 15), \
_mm256_lddqu_si256(data2 + i + 15)); \
_mm256_storeu_si256(out + i + 15, A2); \
CSA(&twosB, &ones, ones, A1, A2); \
CSA(&foursB, &twos, twos, twosA, twosB); \
CSA(&eightsB, &fours, fours, foursA, foursB); \
CSA(&sixteens, &eights, eights, eightsA, eightsB); \
total = _mm256_add_epi64(total, popcount256(sixteens)); \
} \
total = _mm256_slli_epi64(total, 4); \
total = _mm256_add_epi64(total, \
_mm256_slli_epi64(popcount256(eights), 3)); \
total = \
_mm256_add_epi64(total, _mm256_slli_epi64(popcount256(fours), 2)); \
total = \
_mm256_add_epi64(total, _mm256_slli_epi64(popcount256(twos), 1)); \
total = _mm256_add_epi64(total, popcount256(ones)); \
for (; i < size; i++) { \
A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i), \
_mm256_lddqu_si256(data2 + i)); \
_mm256_storeu_si256(out + i, A1); \
total = _mm256_add_epi64(total, popcount256(A1)); \
} \
return (uint64_t)(_mm256_extract_epi64(total, 0)) + \
(uint64_t)(_mm256_extract_epi64(total, 1)) + \
(uint64_t)(_mm256_extract_epi64(total, 2)) + \
(uint64_t)(_mm256_extract_epi64(total, 3)); \
}
CROARING_TARGET_AVX2
CROARING_AVXPOPCNTFNC(or, _mm256_or_si256)
CROARING_UNTARGET_AVX2
CROARING_TARGET_AVX2
CROARING_AVXPOPCNTFNC(union, _mm256_or_si256)
CROARING_UNTARGET_AVX2
CROARING_TARGET_AVX2
CROARING_AVXPOPCNTFNC(and, _mm256_and_si256)
CROARING_UNTARGET_AVX2
CROARING_TARGET_AVX2
CROARING_AVXPOPCNTFNC(intersection, _mm256_and_si256)
CROARING_UNTARGET_AVX2
CROARING_TARGET_AVX2
CROARING_AVXPOPCNTFNC(xor, _mm256_xor_si256)
CROARING_UNTARGET_AVX2
CROARING_TARGET_AVX2
CROARING_AVXPOPCNTFNC(andnot, _mm256_andnot_si256)
CROARING_UNTARGET_AVX2
#define VPOPCNT_AND_ADD(ptr, i, accu) \
const __m512i v##i = _mm512_loadu_si512((const __m512i *)ptr + i); \
const __m512i p##i = _mm512_popcnt_epi64(v##i); \
accu = _mm512_add_epi64(accu, p##i);
#if CROARING_COMPILER_SUPPORTS_AVX512
CROARING_TARGET_AVX512
static inline uint64_t sum_epu64_256(const __m256i v) {
return (uint64_t)(_mm256_extract_epi64(v, 0)) +
(uint64_t)(_mm256_extract_epi64(v, 1)) +
(uint64_t)(_mm256_extract_epi64(v, 2)) +
(uint64_t)(_mm256_extract_epi64(v, 3));
}
static inline uint64_t simd_sum_epu64(const __m512i v) {
__m256i lo = _mm512_extracti64x4_epi64(v, 0);
__m256i hi = _mm512_extracti64x4_epi64(v, 1);
return sum_epu64_256(lo) + sum_epu64_256(hi);
}
static inline uint64_t avx512_vpopcount(const __m512i *data,
const uint64_t size) {
const uint64_t limit = size - size % 4;
__m512i total = _mm512_setzero_si512();
uint64_t i = 0;
for (; i < limit; i += 4) {
VPOPCNT_AND_ADD(data + i, 0, total);
VPOPCNT_AND_ADD(data + i, 1, total);
VPOPCNT_AND_ADD(data + i, 2, total);
VPOPCNT_AND_ADD(data + i, 3, total);
}
for (; i < size; i++) {
total = _mm512_add_epi64(
total, _mm512_popcnt_epi64(_mm512_loadu_si512(data + i)));
}
return simd_sum_epu64(total);
}
CROARING_UNTARGET_AVX512
#endif
#define CROARING_AVXPOPCNTFNC512(opname, avx_intrinsic) \
static inline uint64_t avx512_harley_seal_popcount512_##opname( \
const __m512i *data1, const __m512i *data2, const uint64_t size) { \
__m512i total = _mm512_setzero_si512(); \
const uint64_t limit = size - size % 4; \
uint64_t i = 0; \
for (; i < limit; i += 4) { \
__m512i a1 = avx_intrinsic(_mm512_loadu_si512(data1 + i), \
_mm512_loadu_si512(data2 + i)); \
total = _mm512_add_epi64(total, _mm512_popcnt_epi64(a1)); \
__m512i a2 = avx_intrinsic(_mm512_loadu_si512(data1 + i + 1), \
_mm512_loadu_si512(data2 + i + 1)); \
total = _mm512_add_epi64(total, _mm512_popcnt_epi64(a2)); \
__m512i a3 = avx_intrinsic(_mm512_loadu_si512(data1 + i + 2), \
_mm512_loadu_si512(data2 + i + 2)); \
total = _mm512_add_epi64(total, _mm512_popcnt_epi64(a3)); \
__m512i a4 = avx_intrinsic(_mm512_loadu_si512(data1 + i + 3), \
_mm512_loadu_si512(data2 + i + 3)); \
total = _mm512_add_epi64(total, _mm512_popcnt_epi64(a4)); \
} \
for (; i < size; i++) { \
__m512i a = avx_intrinsic(_mm512_loadu_si512(data1 + i), \
_mm512_loadu_si512(data2 + i)); \
total = _mm512_add_epi64(total, _mm512_popcnt_epi64(a)); \
} \
return simd_sum_epu64(total); \
} \
static inline uint64_t avx512_harley_seal_popcount512andstore_##opname( \
const __m512i *__restrict__ data1, const __m512i *__restrict__ data2, \
__m512i *__restrict__ out, const uint64_t size) { \
__m512i total = _mm512_setzero_si512(); \
const uint64_t limit = size - size % 4; \
uint64_t i = 0; \
for (; i < limit; i += 4) { \
__m512i a1 = avx_intrinsic(_mm512_loadu_si512(data1 + i), \
_mm512_loadu_si512(data2 + i)); \
_mm512_storeu_si512(out + i, a1); \
total = _mm512_add_epi64(total, _mm512_popcnt_epi64(a1)); \
__m512i a2 = avx_intrinsic(_mm512_loadu_si512(data1 + i + 1), \
_mm512_loadu_si512(data2 + i + 1)); \
_mm512_storeu_si512(out + i + 1, a2); \
total = _mm512_add_epi64(total, _mm512_popcnt_epi64(a2)); \
__m512i a3 = avx_intrinsic(_mm512_loadu_si512(data1 + i + 2), \
_mm512_loadu_si512(data2 + i + 2)); \
_mm512_storeu_si512(out + i + 2, a3); \
total = _mm512_add_epi64(total, _mm512_popcnt_epi64(a3)); \
__m512i a4 = avx_intrinsic(_mm512_loadu_si512(data1 + i + 3), \
_mm512_loadu_si512(data2 + i + 3)); \
_mm512_storeu_si512(out + i + 3, a4); \
total = _mm512_add_epi64(total, _mm512_popcnt_epi64(a4)); \
} \
for (; i < size; i++) { \
__m512i a = avx_intrinsic(_mm512_loadu_si512(data1 + i), \
_mm512_loadu_si512(data2 + i)); \
_mm512_storeu_si512(out + i, a); \
total = _mm512_add_epi64(total, _mm512_popcnt_epi64(a)); \
} \
return simd_sum_epu64(total); \
}
#if CROARING_COMPILER_SUPPORTS_AVX512
CROARING_TARGET_AVX512
CROARING_AVXPOPCNTFNC512(or, _mm512_or_si512)
CROARING_AVXPOPCNTFNC512(union, _mm512_or_si512)
CROARING_AVXPOPCNTFNC512(and, _mm512_and_si512)
CROARING_AVXPOPCNTFNC512(intersection, _mm512_and_si512)
CROARING_AVXPOPCNTFNC512(xor, _mm512_xor_si512)
CROARING_AVXPOPCNTFNC512(andnot, _mm512_andnot_si512)
CROARING_UNTARGET_AVX512
#endif
/***
* END Harley-Seal popcount functions.
*/
#endif // CROARING_IS_X64
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal
#endif
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
#endif
+571
View File
@@ -0,0 +1,571 @@
/*
* array.h
*
* Array containers store a sparse set of 16-bit integers as a sorted dynamic
* array. The cardinality field tracks how many values are present, capacity
* tracks the allocated length, and array points to the sorted values.
*
* This representation is used for low-cardinality containers because it is
* compact and supports fast iteration and binary-search-based membership
* tests. When the number of stored values grows beyond DEFAULT_MAX_SIZE,
* CRoaring will typically switch to a denser container representation.
*/
#ifndef INCLUDE_CONTAINERS_ARRAY_H_
#define INCLUDE_CONTAINERS_ARRAY_H_
#include <string.h>
#include <roaring/roaring_types.h> // roaring_iterator
// Include other headers after roaring_types.h
#include <roaring/array_util.h> // binarySearch()/memequals() for inlining
#include <roaring/containers/container_defs.h> // container_t, perfparameters
#include <roaring/portability.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
// Note: in pure C++ code, you should avoid putting `using` in header files
using api::roaring_iterator;
using api::roaring_iterator64;
namespace internal {
#endif
/* Containers with DEFAULT_MAX_SIZE or less integers should be arrays */
enum { DEFAULT_MAX_SIZE = 4096 };
/* struct array_container - sparse representation of a bitmap
*
* @cardinality: number of indices in `array` (and the bitmap)
* @capacity: allocated size of `array`
* @array: sorted list of integers
*/
STRUCT_CONTAINER(array_container_s) {
int32_t cardinality;
int32_t capacity;
uint16_t *array;
};
typedef struct array_container_s array_container_t;
#define CAST_array(c) CAST(array_container_t *, c) // safer downcast
#define const_CAST_array(c) CAST(const array_container_t *, c)
#define movable_CAST_array(c) movable_CAST(array_container_t **, c)
/* Create a new array with default. Return NULL in case of failure. See also
* array_container_create_given_capacity. */
array_container_t *array_container_create(void);
/* Create a new array with a specified capacity size. Return NULL in case of
* failure. */
array_container_t *array_container_create_given_capacity(int32_t size);
/* Create a new array containing all values in [min,max). */
array_container_t *array_container_create_range(uint32_t min, uint32_t max);
/*
* Shrink the capacity to the actual size, return the number of bytes saved.
*/
int array_container_shrink_to_fit(array_container_t *src);
/* Free memory owned by `array'. */
void array_container_free(array_container_t *array);
/* Duplicate container */
array_container_t *array_container_clone(const array_container_t *src);
/* Get the cardinality of `array'. */
CROARING_ALLOW_UNALIGNED
static inline int array_container_cardinality(const array_container_t *array) {
return array->cardinality;
}
static inline bool array_container_nonzero_cardinality(
const array_container_t *array) {
return array->cardinality > 0;
}
/* Copy one container into another. We assume that they are distinct. */
void array_container_copy(const array_container_t *src, array_container_t *dst);
/* Add all the values in [min,max) (included) at a distance k*step from min.
The container must have a size less or equal to DEFAULT_MAX_SIZE after this
addition. */
void array_container_add_from_range(array_container_t *arr, uint32_t min,
uint32_t max, uint16_t step);
static inline bool array_container_empty(const array_container_t *array) {
return array->cardinality == 0;
}
/* check whether the cardinality is equal to the capacity (this does not mean
* that it contains 1<<16 elements) */
static inline bool array_container_full(const array_container_t *array) {
return array->cardinality == array->capacity;
}
/* Compute the union of `src_1' and `src_2' and write the result to `dst'
* It is assumed that `dst' is distinct from both `src_1' and `src_2'. */
void array_container_union(const array_container_t *src_1,
const array_container_t *src_2,
array_container_t *dst);
/* symmetric difference, see array_container_union */
void array_container_xor(const array_container_t *array_1,
const array_container_t *array_2,
array_container_t *out);
/* Computes the intersection of src_1 and src_2 and write the result to
* dst. It is assumed that dst is distinct from both src_1 and src_2. */
void array_container_intersection(const array_container_t *src_1,
const array_container_t *src_2,
array_container_t *dst);
/* Check whether src_1 and src_2 intersect. */
bool array_container_intersect(const array_container_t *src_1,
const array_container_t *src_2);
/* computers the size of the intersection between two arrays.
*/
int array_container_intersection_cardinality(const array_container_t *src_1,
const array_container_t *src_2);
/* computes the intersection of array1 and array2 and write the result to
* array1.
* */
void array_container_intersection_inplace(array_container_t *src_1,
const array_container_t *src_2);
/*
* Write out the 16-bit integers contained in this container as a list of 32-bit
* integers using base
* as the starting value (it might be expected that base has zeros in its 16
* least significant bits).
* The function returns the number of values written.
* The caller is responsible for allocating enough memory in out.
*/
int array_container_to_uint32_array(void *vout, const array_container_t *cont,
uint32_t base);
/* Compute the number of runs */
int32_t array_container_number_of_runs(const array_container_t *ac);
/*
* Print this container using printf (useful for debugging).
*/
void array_container_printf(const array_container_t *v);
/*
* Print this container using printf as a comma-separated list of 32-bit
* integers starting at base.
*/
void array_container_printf_as_uint32_array(const array_container_t *v,
uint32_t base);
bool array_container_validate(const array_container_t *v, const char **reason);
/**
* Return the serialized size in bytes of a container having cardinality "card".
*/
static inline int32_t array_container_serialized_size_in_bytes(int32_t card) {
return card * sizeof(uint16_t);
}
/**
* Increase capacity to at least min.
* Whether the existing data needs to be copied over depends on the "preserve"
* parameter. If preserve is false, then the new content will be uninitialized,
* otherwise the old content is copied.
*/
void array_container_grow(array_container_t *container, int32_t min,
bool preserve);
bool array_container_iterate(const array_container_t *cont, uint32_t base,
roaring_iterator iterator, void *ptr);
bool array_container_iterate64(const array_container_t *cont, uint32_t base,
roaring_iterator64 iterator, uint64_t high_bits,
void *ptr);
/**
* Writes the underlying array to buf, outputs how many bytes were written.
* This is meant to be byte-by-byte compatible with the Java and Go versions of
* Roaring.
* The number of bytes written should be
* array_container_size_in_bytes(container).
*
*/
int32_t array_container_write(const array_container_t *container, char *buf);
/**
* 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.
* The number of bytes read should be array_container_size_in_bytes(container).
* You need to provide the (known) cardinality.
*/
int32_t array_container_read(int32_t cardinality, array_container_t *container,
const char *buf);
/**
* Return the serialized size in bytes of a container (see
* bitset_container_write)
* This is meant to be compatible with the Java and Go versions of Roaring and
* assumes
* that the cardinality of the container is already known.
*
*/
CROARING_ALLOW_UNALIGNED
static inline int32_t array_container_size_in_bytes(
const array_container_t *container) {
return container->cardinality * sizeof(uint16_t);
}
/**
* Return true if the two arrays have the same content.
*/
CROARING_ALLOW_UNALIGNED
static inline bool array_container_equals(const array_container_t *container1,
const array_container_t *container2) {
if (container1->cardinality != container2->cardinality) {
return false;
}
return memequals(container1->array, container2->array,
container1->cardinality * 2);
}
/**
* Return true if container1 is a subset of container2.
*/
bool array_container_is_subset(const array_container_t *container1,
const array_container_t *container2);
/**
* If the element of given rank is in this container, supposing that the first
* element has rank start_rank, then the function returns true and sets element
* accordingly.
* Otherwise, it returns false and update start_rank.
*/
static inline bool array_container_select(const array_container_t *container,
uint32_t *start_rank, uint32_t rank,
uint32_t *element) {
int card = array_container_cardinality(container);
if (*start_rank + card <= rank) {
*start_rank += card;
return false;
} else {
*element = container->array[rank - *start_rank];
return true;
}
}
/* Computes the difference of array1 and array2 and write the result
* to array out.
* Array out does not need to be distinct from array_1
*/
void array_container_andnot(const array_container_t *array_1,
const array_container_t *array_2,
array_container_t *out);
/* Append x to the set. Assumes that the value is larger than any preceding
* values. */
static inline void array_container_append(array_container_t *arr,
uint16_t pos) {
const int32_t capacity = arr->capacity;
if (array_container_full(arr)) {
array_container_grow(arr, capacity + 1, true);
}
arr->array[arr->cardinality++] = pos;
}
/**
* Add value to the set if final cardinality doesn't exceed max_cardinality.
* Return code:
* 1 -- value was added
* 0 -- value was already present
* -1 -- value was not added because cardinality would exceed max_cardinality
*/
static inline int array_container_try_add(array_container_t *arr,
uint16_t value,
int32_t max_cardinality) {
const int32_t cardinality = arr->cardinality;
// best case, we can append.
if ((array_container_empty(arr) || arr->array[cardinality - 1] < value) &&
cardinality < max_cardinality) {
array_container_append(arr, value);
return 1;
}
const int32_t loc = binarySearch(arr->array, cardinality, value);
if (loc >= 0) {
return 0;
} else if (cardinality < max_cardinality) {
if (array_container_full(arr)) {
array_container_grow(arr, arr->capacity + 1, true);
}
const int32_t insert_idx = -loc - 1;
memmove(arr->array + insert_idx + 1, arr->array + insert_idx,
(cardinality - insert_idx) * sizeof(uint16_t));
arr->array[insert_idx] = value;
arr->cardinality++;
return 1;
} else {
return -1;
}
}
/* Add value to the set. Returns true if x was not already present. */
static inline bool array_container_add(array_container_t *arr, uint16_t value) {
return array_container_try_add(arr, value, INT32_MAX) == 1;
}
/* Remove x from the set. Returns true if x was present. */
static inline bool array_container_remove(array_container_t *arr,
uint16_t pos) {
const int32_t idx = binarySearch(arr->array, arr->cardinality, pos);
const bool is_present = idx >= 0;
if (is_present) {
memmove(arr->array + idx, arr->array + idx + 1,
(arr->cardinality - idx - 1) * sizeof(uint16_t));
arr->cardinality--;
}
return is_present;
}
/* Check whether x is present. */
inline bool array_container_contains(const array_container_t *arr,
uint16_t pos) {
/**
* SIMD Quad algorithm
* Daniel Lemire, "You can beat the binary search," in Daniel Lemire's blog,
* April 27, 2026,
* https://lemire.me/blog/2026/04/27/you-can-beat-the-binary-search/.
*/
const int32_t gap = 16;
const uint16_t *carr = arr->array;
int32_t cardinality = arr->cardinality;
if (cardinality < gap) {
for (int32_t j = 0; j < cardinality; j++) {
if (carr[j] >= pos) return carr[j] == pos;
}
return false;
}
int32_t num_blocks = cardinality / gap;
int32_t base = 0;
int32_t n = num_blocks;
while (n > 3) {
int32_t quarter = n >> 2;
int32_t k1 = carr[(base + quarter + 1) * gap - 1];
int32_t k2 = carr[(base + 2 * quarter + 1) * gap - 1];
int32_t k3 = carr[(base + 3 * quarter + 1) * gap - 1];
int32_t c1 = (k1 < pos);
int32_t c2 = (k2 < pos);
int32_t c3 = (k3 < pos);
base += (c1 + c2 + c3) * quarter;
n -= 3 * quarter;
}
while (n > 1) {
int32_t half = n >> 1;
base = (carr[(base + half + 1) * gap - 1] < pos) ? base + half : base;
n -= half;
}
int32_t lo = (carr[(base + 1) * gap - 1] < pos) ? base + 1 : base;
if (lo < num_blocks) {
const uint16_t *blk = carr + lo * gap;
#ifdef CROARING_USENEON
uint16x8_t needle = vdupq_n_u16(pos);
uint16x8_t v0 = vld1q_u16(blk);
uint16x8_t v1 = vld1q_u16(blk + 8);
uint16x8_t hit =
vorrq_u16(vceqq_u16(v0, needle), vceqq_u16(v1, needle));
return vmaxvq_u16(hit) != 0;
#elif defined(CROARING_IS_X64)
__m128i needle = _mm_set1_epi16((short)pos);
__m128i v0 = _mm_loadu_si128((const __m128i *)blk);
__m128i v1 = _mm_loadu_si128((const __m128i *)(blk + 8));
__m128i hit = _mm_or_si128(_mm_cmpeq_epi16(v0, needle),
_mm_cmpeq_epi16(v1, needle));
return _mm_movemask_epi8(hit) != 0;
#else
for (int32_t j = 0; j < gap; j++) {
if (blk[j] >= pos) return blk[j] == pos;
}
return false;
#endif
}
for (int32_t j = num_blocks * gap; j < cardinality; j++) {
uint16_t v = carr[j];
if (v >= pos) return (v == pos);
}
return false;
}
void array_container_offset(const array_container_t *c, container_t **loc,
container_t **hic, uint16_t offset);
//* Check whether a range of values from range_start (included) to range_end
//(excluded) is present. */
static inline bool array_container_contains_range(const array_container_t *arr,
uint32_t range_start,
uint32_t range_end) {
const int32_t range_count = range_end - range_start;
const uint16_t rs_included = (uint16_t)range_start;
const uint16_t re_included = (uint16_t)(range_end - 1);
// Empty range is always included
if (range_count <= 0) {
return true;
}
if (range_count > arr->cardinality) {
return false;
}
const int32_t start =
binarySearch(arr->array, arr->cardinality, rs_included);
// If this sorted array contains all items in the range:
// * the start item must be found
// * the last item in range range_count must exist, and be the expected end
// value
return (start >= 0) && (arr->cardinality >= start + range_count) &&
(arr->array[start + range_count - 1] == re_included);
}
/* Returns the smallest value (assumes not empty) */
inline uint16_t array_container_minimum(const array_container_t *arr) {
if (arr->cardinality == 0) return 0;
return arr->array[0];
}
/* Returns the largest value (assumes not empty) */
inline uint16_t array_container_maximum(const array_container_t *arr) {
if (arr->cardinality == 0) return 0;
return arr->array[arr->cardinality - 1];
}
/* Returns the number of values equal or smaller than x */
inline int array_container_rank(const array_container_t *arr, uint16_t x) {
const int32_t idx = binarySearch(arr->array, arr->cardinality, x);
const bool is_present = idx >= 0;
if (is_present) {
return idx + 1;
} else {
return -idx - 1;
}
}
/* bulk version of array_container_rank(); return number of consumed elements
*/
inline uint32_t array_container_rank_many(const array_container_t *arr,
uint64_t start_rank,
const uint32_t *begin,
const uint32_t *end, uint64_t *ans) {
const uint16_t high = (uint16_t)((*begin) >> 16);
uint32_t pos = 0;
const uint32_t *iter = begin;
for (; iter != end; iter++) {
uint32_t x = *iter;
uint16_t xhigh = (uint16_t)(x >> 16);
if (xhigh != high) return iter - begin; // stop at next container
const int32_t idx =
binarySearch(arr->array + pos, arr->cardinality - pos, (uint16_t)x);
const bool is_present = idx >= 0;
if (is_present) {
*(ans++) = start_rank + pos + (idx + 1);
pos = idx + 1;
} else {
*(ans++) = start_rank + pos + (-idx - 1);
}
}
return iter - begin;
}
/* Returns the index of x , if not exsist return -1 */
inline int array_container_get_index(const array_container_t *arr, uint16_t x) {
const int32_t idx = binarySearch(arr->array, arr->cardinality, x);
const bool is_present = idx >= 0;
if (is_present) {
return idx;
} else {
return -1;
}
}
/* Returns the index of the first value equal or larger than x, or -1 */
inline int array_container_index_equalorlarger(const array_container_t *arr,
uint16_t x) {
const int32_t idx = binarySearch(arr->array, arr->cardinality, x);
const bool is_present = idx >= 0;
if (is_present) {
return idx;
} else {
int32_t candidate = -idx - 1;
if (candidate < arr->cardinality) return candidate;
return -1;
}
}
/*
* Adds all values in range [min,max] using hint:
* nvals_less is the number of array values less than $min
* nvals_greater is the number of array values greater than $max
*/
static inline void array_container_add_range_nvals(array_container_t *array,
uint32_t min, uint32_t max,
int32_t nvals_less,
int32_t nvals_greater) {
int32_t union_cardinality = nvals_less + (max - min + 1) + nvals_greater;
if (union_cardinality > array->capacity) {
array_container_grow(array, union_cardinality, true);
}
memmove(&(array->array[union_cardinality - nvals_greater]),
&(array->array[array->cardinality - nvals_greater]),
nvals_greater * sizeof(uint16_t));
for (uint32_t i = 0; i <= max - min; i++) {
array->array[nvals_less + i] = (uint16_t)(min + i);
}
array->cardinality = union_cardinality;
}
/**
* Adds all values in range [min,max]. This function is currently unused
* and left as a documentation.
*/
/*static inline void array_container_add_range(array_container_t *array,
uint32_t min, uint32_t max) {
int32_t nvals_greater = count_greater(array->array, array->cardinality,
max); int32_t nvals_less = count_less(array->array, array->cardinality -
nvals_greater, min); array_container_add_range_nvals(array, min, max,
nvals_less, nvals_greater);
}*/
/*
* Removes all elements array[pos] .. array[pos+count-1]
*/
static inline void array_container_remove_range(array_container_t *array,
uint32_t pos, uint32_t count) {
if (count != 0) {
memmove(&(array->array[pos]), &(array->array[pos + count]),
(array->cardinality - pos - count) * sizeof(uint16_t));
array->cardinality -= count;
}
}
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
#endif /* INCLUDE_CONTAINERS_ARRAY_H_ */
+523
View File
@@ -0,0 +1,523 @@
/*
* bitset.h
*
* Bitset containers store a set of 16-bit integers as a fixed-size bitmap.
* The words pointer references an array of 64-bit words covering the full
* 16-bit domain, with one bit per possible value. The cardinality field tracks
* the number of set bits; when it is BITSET_UNKNOWN_CARDINALITY, the count must
* be recomputed from the bitmap contents.
*
* This representation is used for denser containers because membership tests,
* set operations, and sequential scans can be implemented efficiently with
* word-level bitwise operations.
*/
#ifndef INCLUDE_CONTAINERS_BITSET_H_
#define INCLUDE_CONTAINERS_BITSET_H_
#include <stdbool.h>
#include <stdint.h>
#include <roaring/roaring_types.h> // roaring_iterator
// Include other headers after roaring_types.h
#include <roaring/containers/container_defs.h> // container_t, perfparameters
#include <roaring/portability.h>
#include <roaring/roaring_types.h> // roaring_iterator
#include <roaring/utilasm.h> // ASM_XXX macros
#ifdef __cplusplus
extern "C" {
namespace roaring {
// Note: in pure C++ code, you should avoid putting `using` in header files
using api::roaring_iterator;
using api::roaring_iterator64;
namespace internal {
#endif
enum {
BITSET_CONTAINER_SIZE_IN_WORDS = (1 << 16) / 64,
BITSET_UNKNOWN_CARDINALITY = -1
};
STRUCT_CONTAINER(bitset_container_s) {
int32_t cardinality;
uint64_t *words;
};
typedef struct bitset_container_s bitset_container_t;
#define CAST_bitset(c) CAST(bitset_container_t *, c) // safer downcast
#define const_CAST_bitset(c) CAST(const bitset_container_t *, c)
#define movable_CAST_bitset(c) movable_CAST(bitset_container_t **, c)
/* Create a new bitset. Return NULL in case of failure. */
bitset_container_t *bitset_container_create(void);
/* Free memory. */
void bitset_container_free(bitset_container_t *bitset);
/* Clear bitset (sets bits to 0). */
void bitset_container_clear(bitset_container_t *bitset);
/* Set all bits to 1. */
void bitset_container_set_all(bitset_container_t *bitset);
/* Duplicate bitset */
bitset_container_t *bitset_container_clone(const bitset_container_t *src);
/* Set the bit in [begin,end). WARNING: as of April 2016, this method is slow
* and
* should not be used in performance-sensitive code. Ever. */
void bitset_container_set_range(bitset_container_t *bitset, uint32_t begin,
uint32_t end);
#if defined(CROARING_ASMBITMANIPOPTIMIZATION) && defined(__AVX2__)
/* Set the ith bit. */
static inline void bitset_container_set(bitset_container_t *bitset,
uint16_t pos) {
uint64_t shift = 6;
uint64_t offset;
uint64_t p = pos;
ASM_SHIFT_RIGHT(p, shift, offset);
uint64_t load = bitset->words[offset];
ASM_SET_BIT_INC_WAS_CLEAR(load, p, bitset->cardinality);
bitset->words[offset] = load;
}
/* Unset the ith bit. Currently unused. Could be used for optimization. */
/*static inline void bitset_container_unset(bitset_container_t *bitset,
uint16_t pos) {
uint64_t shift = 6;
uint64_t offset;
uint64_t p = pos;
ASM_SHIFT_RIGHT(p, shift, offset);
uint64_t load = bitset->words[offset];
ASM_CLEAR_BIT_DEC_WAS_SET(load, p, bitset->cardinality);
bitset->words[offset] = load;
}*/
/* Add `pos' to `bitset'. Returns true if `pos' was not present. Might be slower
* than bitset_container_set. */
static inline bool bitset_container_add(bitset_container_t *bitset,
uint16_t pos) {
uint64_t shift = 6;
uint64_t offset;
uint64_t p = pos;
ASM_SHIFT_RIGHT(p, shift, offset);
uint64_t load = bitset->words[offset];
// could be possibly slightly further optimized
const int32_t oldcard = bitset->cardinality;
ASM_SET_BIT_INC_WAS_CLEAR(load, p, bitset->cardinality);
bitset->words[offset] = load;
return bitset->cardinality - oldcard;
}
/* Remove `pos' from `bitset'. Returns true if `pos' was present. Might be
* slower than bitset_container_unset. */
static inline bool bitset_container_remove(bitset_container_t *bitset,
uint16_t pos) {
uint64_t shift = 6;
uint64_t offset;
uint64_t p = pos;
ASM_SHIFT_RIGHT(p, shift, offset);
uint64_t load = bitset->words[offset];
// could be possibly slightly further optimized
const int32_t oldcard = bitset->cardinality;
ASM_CLEAR_BIT_DEC_WAS_SET(load, p, bitset->cardinality);
bitset->words[offset] = load;
return oldcard - bitset->cardinality;
}
/* Get the value of the ith bit. */
inline bool bitset_container_get(const bitset_container_t *bitset,
uint16_t pos) {
uint64_t word = bitset->words[pos >> 6];
const uint64_t p = pos;
ASM_INPLACESHIFT_RIGHT(word, p);
return word & 1;
}
#else
/* Set the ith bit. */
static inline void bitset_container_set(bitset_container_t *bitset,
uint16_t pos) {
const uint64_t old_word = bitset->words[pos >> 6];
const int index = pos & 63;
const uint64_t new_word = old_word | (UINT64_C(1) << index);
bitset->cardinality += (uint32_t)((old_word ^ new_word) >> index);
bitset->words[pos >> 6] = new_word;
}
/* Unset the ith bit. Currently unused. */
/*static inline void bitset_container_unset(bitset_container_t *bitset,
uint16_t pos) {
const uint64_t old_word = bitset->words[pos >> 6];
const int index = pos & 63;
const uint64_t new_word = old_word & (~(UINT64_C(1) << index));
bitset->cardinality -= (uint32_t)((old_word ^ new_word) >> index);
bitset->words[pos >> 6] = new_word;
}*/
/* Add `pos' to `bitset'. Returns true if `pos' was not present. Might be slower
* than bitset_container_set. */
static inline bool bitset_container_add(bitset_container_t *bitset,
uint16_t pos) {
const uint64_t old_word = bitset->words[pos >> 6];
const int index = pos & 63;
const uint64_t new_word = old_word | (UINT64_C(1) << index);
const uint64_t increment = (old_word ^ new_word) >> index;
bitset->cardinality += (uint32_t)increment;
bitset->words[pos >> 6] = new_word;
return increment > 0;
}
/* Remove `pos' from `bitset'. Returns true if `pos' was present. Might be
* slower than bitset_container_unset. */
static inline bool bitset_container_remove(bitset_container_t *bitset,
uint16_t pos) {
const uint64_t old_word = bitset->words[pos >> 6];
const int index = pos & 63;
const uint64_t new_word = old_word & (~(UINT64_C(1) << index));
const uint64_t increment = (old_word ^ new_word) >> index;
bitset->cardinality -= (uint32_t)increment;
bitset->words[pos >> 6] = new_word;
return increment > 0;
}
/* Get the value of the ith bit. */
inline bool bitset_container_get(const bitset_container_t *bitset,
uint16_t pos) {
const uint64_t word = bitset->words[pos >> 6];
return (word >> (pos & 63)) & 1;
}
#endif
/*
* Check if all bits are set in a range of positions from pos_start (included)
* to pos_end (excluded).
*/
static inline bool bitset_container_get_range(const bitset_container_t *bitset,
uint32_t pos_start,
uint32_t pos_end) {
const uint32_t start = pos_start >> 6;
const uint32_t end = pos_end >> 6;
const uint64_t first = ~((1ULL << (pos_start & 0x3F)) - 1);
const uint64_t last = (1ULL << (pos_end & 0x3F)) - 1;
if (start == end)
return ((bitset->words[end] & first & last) == (first & last));
if ((bitset->words[start] & first) != first) return false;
if ((end < BITSET_CONTAINER_SIZE_IN_WORDS) &&
((bitset->words[end] & last) != last)) {
return false;
}
for (uint32_t i = start + 1;
(i < BITSET_CONTAINER_SIZE_IN_WORDS) && (i < end); ++i) {
if (bitset->words[i] != UINT64_C(0xFFFFFFFFFFFFFFFF)) return false;
}
return true;
}
/* Check whether `bitset' is present in `array'. Calls bitset_container_get. */
inline bool bitset_container_contains(const bitset_container_t *bitset,
uint16_t pos) {
return bitset_container_get(bitset, pos);
}
/*
* Check whether a range of bits from position `pos_start' (included) to
* `pos_end' (excluded) is present in `bitset'. Calls bitset_container_get_all.
*/
static inline bool bitset_container_contains_range(
const bitset_container_t *bitset, uint32_t pos_start, uint32_t pos_end) {
return bitset_container_get_range(bitset, pos_start, pos_end);
}
/* Get the number of bits set */
CROARING_ALLOW_UNALIGNED
static inline int bitset_container_cardinality(
const bitset_container_t *bitset) {
return bitset->cardinality;
}
/* Copy one container into another. We assume that they are distinct. */
void bitset_container_copy(const bitset_container_t *source,
bitset_container_t *dest);
/* Add all the values [min,max) at a distance k*step from min: min,
* min+step,.... */
void bitset_container_add_from_range(bitset_container_t *bitset, uint32_t min,
uint32_t max, uint16_t step);
/* Get the number of bits set (force computation). This does not modify bitset.
* To update the cardinality, you should do
* bitset->cardinality = bitset_container_compute_cardinality(bitset).*/
int bitset_container_compute_cardinality(const bitset_container_t *bitset);
/* Check whether this bitset is empty,
* it never modifies the bitset struct. */
static inline bool bitset_container_empty(const bitset_container_t *bitset) {
if (bitset->cardinality == BITSET_UNKNOWN_CARDINALITY) {
for (int i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; i++) {
if ((bitset->words[i]) != 0) return false;
}
return true;
}
return bitset->cardinality == 0;
}
/* Get whether there is at least one bit set (see bitset_container_empty for
the reverse), the bitset is never modified */
static inline bool bitset_container_const_nonzero_cardinality(
const bitset_container_t *bitset) {
return !bitset_container_empty(bitset);
}
/*
* Check whether the two bitsets intersect
*/
bool bitset_container_intersect(const bitset_container_t *src_1,
const bitset_container_t *src_2);
/* Computes the union of bitsets `src_1' and `src_2' into `dst' and return the
* cardinality. */
int bitset_container_or(const bitset_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* Computes the union of bitsets `src_1' and `src_2' and return the cardinality.
*/
int bitset_container_or_justcard(const bitset_container_t *src_1,
const bitset_container_t *src_2);
/* Computes the union of bitsets `src_1' and `src_2' into `dst' and return the
* cardinality. Same as bitset_container_or. */
int bitset_container_union(const bitset_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* Computes the union of bitsets `src_1' and `src_2' and return the
* cardinality. Same as bitset_container_or_justcard. */
int bitset_container_union_justcard(const bitset_container_t *src_1,
const bitset_container_t *src_2);
/* Computes the union of bitsets `src_1' and `src_2' into `dst', but does
* not update the cardinality. Provided to optimize chained operations. */
int bitset_container_union_nocard(const bitset_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* Computes the union of bitsets `src_1' and `src_2' into `dst', but does not
* update the cardinality. Provided to optimize chained operations. */
int bitset_container_or_nocard(const bitset_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* Computes the intersection of bitsets `src_1' and `src_2' into `dst' and
* return the cardinality. */
int bitset_container_and(const bitset_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* Computes the intersection of bitsets `src_1' and `src_2' and return the
* cardinality. */
int bitset_container_and_justcard(const bitset_container_t *src_1,
const bitset_container_t *src_2);
/* Computes the intersection of bitsets `src_1' and `src_2' into `dst' and
* return the cardinality. Same as bitset_container_and. */
int bitset_container_intersection(const bitset_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* Computes the intersection of bitsets `src_1' and `src_2' and return the
* cardinality. Same as bitset_container_and_justcard. */
int bitset_container_intersection_justcard(const bitset_container_t *src_1,
const bitset_container_t *src_2);
/* Computes the intersection of bitsets `src_1' and `src_2' into `dst', but does
* not update the cardinality. Provided to optimize chained operations. */
int bitset_container_intersection_nocard(const bitset_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* Computes the intersection of bitsets `src_1' and `src_2' into `dst', but does
* not update the cardinality. Provided to optimize chained operations. */
int bitset_container_and_nocard(const bitset_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* Computes the exclusive or of bitsets `src_1' and `src_2' into `dst' and
* return the cardinality. */
int bitset_container_xor(const bitset_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* Computes the exclusive or of bitsets `src_1' and `src_2' and return the
* cardinality. */
int bitset_container_xor_justcard(const bitset_container_t *src_1,
const bitset_container_t *src_2);
/* Computes the exclusive or of bitsets `src_1' and `src_2' into `dst', but does
* not update the cardinality. Provided to optimize chained operations. */
int bitset_container_xor_nocard(const bitset_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* Computes the and not of bitsets `src_1' and `src_2' into `dst' and return the
* cardinality. */
int bitset_container_andnot(const bitset_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* Computes the and not of bitsets `src_1' and `src_2' and return the
* cardinality. */
int bitset_container_andnot_justcard(const bitset_container_t *src_1,
const bitset_container_t *src_2);
/* Computes the and not or of bitsets `src_1' and `src_2' into `dst', but does
* not update the cardinality. Provided to optimize chained operations. */
int bitset_container_andnot_nocard(const bitset_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
void bitset_container_offset(const bitset_container_t *c, container_t **loc,
container_t **hic, uint16_t offset);
/*
* Write out the 16-bit integers contained in this container as a list of 32-bit
* integers using base
* as the starting value (it might be expected that base has zeros in its 16
* least significant bits).
* The function returns the number of values written.
* The caller is responsible for allocating enough memory in out.
* The out pointer should point to enough memory (the cardinality times 32
* bits).
*/
int bitset_container_to_uint32_array(uint32_t *out,
const bitset_container_t *bc,
uint32_t base);
/*
* Print this container using printf (useful for debugging).
*/
void bitset_container_printf(const bitset_container_t *v);
/*
* Print this container using printf as a comma-separated list of 32-bit
* integers starting at base.
*/
void bitset_container_printf_as_uint32_array(const bitset_container_t *v,
uint32_t base);
bool bitset_container_validate(const bitset_container_t *v,
const char **reason);
/**
* Return the serialized size in bytes of a container.
*/
static inline int32_t bitset_container_serialized_size_in_bytes(void) {
return BITSET_CONTAINER_SIZE_IN_WORDS * 8;
}
/**
* Return the the number of runs.
*/
int bitset_container_number_of_runs(bitset_container_t *bc);
bool bitset_container_iterate(const bitset_container_t *cont, uint32_t base,
roaring_iterator iterator, void *ptr);
bool bitset_container_iterate64(const bitset_container_t *cont, uint32_t base,
roaring_iterator64 iterator, uint64_t high_bits,
void *ptr);
/**
* Writes the underlying array to buf, outputs how many bytes were written.
* This is meant to be byte-by-byte compatible with the Java and Go versions of
* Roaring.
* The number of bytes written should be
* bitset_container_size_in_bytes(container).
*/
int32_t bitset_container_write(const bitset_container_t *container, char *buf);
/**
* 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.
* The number of bytes read should be bitset_container_size_in_bytes(container).
* You need to provide the (known) cardinality.
*/
int32_t bitset_container_read(int32_t cardinality,
bitset_container_t *container, const char *buf);
/**
* Return the serialized size in bytes of a container (see
* bitset_container_write).
* This is meant to be compatible with the Java and Go versions of Roaring and
* assumes
* that the cardinality of the container is already known or can be computed.
*/
static inline int32_t bitset_container_size_in_bytes(
const bitset_container_t *container) {
(void)container;
return BITSET_CONTAINER_SIZE_IN_WORDS * sizeof(uint64_t);
}
/**
* Return true if the two containers have the same content.
*/
bool bitset_container_equals(const bitset_container_t *container1,
const bitset_container_t *container2);
/**
* Return true if container1 is a subset of container2.
*/
bool bitset_container_is_subset(const bitset_container_t *container1,
const bitset_container_t *container2);
/**
* If the element of given rank is in this container, supposing that the first
* element has rank start_rank, then the function returns true and sets element
* accordingly.
* Otherwise, it returns false and update start_rank.
*/
bool bitset_container_select(const bitset_container_t *container,
uint32_t *start_rank, uint32_t rank,
uint32_t *element);
/* Returns the smallest value (assumes not empty) */
uint16_t bitset_container_minimum(const bitset_container_t *container);
/* Returns the largest value (assumes not empty) */
uint16_t bitset_container_maximum(const bitset_container_t *container);
/* Returns the number of values equal or smaller than x */
int bitset_container_rank(const bitset_container_t *container, uint16_t x);
/* bulk version of bitset_container_rank(); return number of consumed elements
*/
uint32_t bitset_container_rank_many(const bitset_container_t *container,
uint64_t start_rank, const uint32_t *begin,
const uint32_t *end, uint64_t *ans);
/* Returns the index of x , if not exsist return -1 */
int bitset_container_get_index(const bitset_container_t *container, uint16_t x);
/* Returns the index of the first value equal or larger than x, or -1 */
int bitset_container_index_equalorlarger(const bitset_container_t *container,
uint16_t x);
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
#endif /* INCLUDE_CONTAINERS_BITSET_H_ */
@@ -0,0 +1,99 @@
/*
* container_defs.h
*
* Unlike containers.h (which is a file aggregating all the container includes,
* like array.h, bitset.h, and run.h) this is a file included BY those headers
* to do things like define the container base class `container_t`.
*/
#ifndef INCLUDE_CONTAINERS_CONTAINER_DEFS_H_
#define INCLUDE_CONTAINERS_CONTAINER_DEFS_H_
#ifdef __cplusplus
#include <type_traits> // used by casting helper for compile-time check
#endif
// The preferences are a separate file to separate out tweakable parameters
#include <roaring/containers/perfparameters.h>
#ifdef __cplusplus
namespace roaring {
namespace internal { // No extern "C" (contains template)
#endif
/*
* Since roaring_array_t's definition is not opaque, the container type is
* part of the API. If it's not going to be `void*` then it needs a name, and
* expectations are to prefix C library-exported names with `roaring_` etc.
*
* Rather than force the whole codebase to use the name `roaring_container_t`,
* the few API appearances use the macro ROARING_CONTAINER_T. Those includes
* are prior to containers.h, so make a short private alias of `container_t`.
* Then undefine the awkward macro so it's not used any more than it has to be.
*/
typedef ROARING_CONTAINER_T container_t;
/*
* See ROARING_CONTAINER_T for notes on using container_t as a base class.
* This macro helps make the following pattern look nicer:
*
* #ifdef __cplusplus
* struct roaring_array_s : public container_t {
* #else
* struct roaring_array_s {
* #endif
* int32_t cardinality;
* int32_t capacity;
* uint16_t *array;
* }
*/
#if defined(__cplusplus)
#define STRUCT_CONTAINER(name) struct name : public container_t /* { ... } */
#else
#define STRUCT_CONTAINER(name) struct name /* { ... } */
#endif
/**
* Since container_t* is not void* in C++, "dangerous" casts are not needed to
* downcast; only a static_cast<> is needed. Define a macro for static casting
* which helps make casts more visible, and catches problems at compile-time
* when building the C sources in C++ mode:
*
* void some_func(container_t **c, ...) { // double pointer, not single
* array_container_t *ac1 = (array_container_t *)(c); // uncaught!!
*
* array_container_t *ac2 = CAST(array_container_t *, c) // C++ errors
* array_container_t *ac3 = CAST_array(c); // shorthand for #2, errors
* }
*
* Trickier to do is a cast from `container**` to `array_container_t**`. This
* needs a reinterpret_cast<>, which sacrifices safety...so a template is used
* leveraging <type_traits> to make sure it's legal in the C++ build.
*/
#ifdef __cplusplus
#define CAST(type, value) static_cast<type>(value)
#define movable_CAST(type, value) movable_CAST_HELPER<type>(value)
template <typename PPDerived, typename Base>
PPDerived movable_CAST_HELPER(Base **ptr_to_ptr) {
typedef typename std::remove_pointer<PPDerived>::type PDerived;
typedef typename std::remove_pointer<PDerived>::type Derived;
static_assert(std::is_base_of<Base, Derived>::value,
"use movable_CAST() for container_t** => xxx_container_t**");
return reinterpret_cast<Derived **>(ptr_to_ptr);
}
#else
#define CAST(type, value) ((type)value)
#define movable_CAST(type, value) ((type)value)
#endif
// Use for converting e.g. an `array_container_t**` to a `container_t**`
//
#define movable_CAST_base(c) movable_CAST(container_t **, c)
#ifdef __cplusplus
}
} // namespace roaring { namespace internal {
#endif
#endif /* INCLUDE_CONTAINERS_CONTAINER_DEFS_H_ */
File diff suppressed because it is too large. Load diff
+82
View File
@@ -0,0 +1,82 @@
/*
* convert.h
*
* This header declares conversion helpers between the different Roaring
* container representations: array, bitset, and run containers. These
* routines are used when an operation produces data better represented in a
* different form, or when the library wants to switch to the most space-
* efficient container type.
*
* In addition to direct conversions, the file also provides helpers that
* choose between candidate result representations based on cardinality and
* storage efficiency.
*/
#ifndef INCLUDE_CONTAINERS_CONVERT_H_
#define INCLUDE_CONTAINERS_CONVERT_H_
#include <roaring/containers/array.h>
#include <roaring/containers/bitset.h>
#include <roaring/containers/run.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/* Convert an array into a bitset. The input container is not freed or modified.
*/
bitset_container_t *bitset_container_from_array(const array_container_t *arr);
/* Convert a run into a bitset. The input container is not freed or modified. */
bitset_container_t *bitset_container_from_run(const run_container_t *arr);
/* Convert a run into an array. The input container is not freed or modified. */
array_container_t *array_container_from_run(const run_container_t *arr);
/* Convert a bitset into an array. The input container is not freed or modified.
*/
array_container_t *array_container_from_bitset(const bitset_container_t *bits);
/* Convert an array into a run. The input container is not freed or modified.
*/
run_container_t *run_container_from_array(const array_container_t *c);
/* convert a run into either an array or a bitset
* might free the container. This does not free the input run container. */
container_t *convert_to_bitset_or_array_container(run_container_t *rc,
int32_t card,
uint8_t *resulttype);
/* convert containers to and from runcontainers, as is most space efficient.
* The container might be freed. */
container_t *convert_run_optimize(container_t *c, uint8_t typecode_original,
uint8_t *typecode_after);
/* converts a run container to either an array or a bitset, IF it saves space.
*/
/* If a conversion occurs, the caller is responsible to free the original
* container and
* he becomes reponsible to free the new one. */
container_t *convert_run_to_efficient_container(run_container_t *c,
uint8_t *typecode_after);
// like convert_run_to_efficient_container but frees the old result if needed
container_t *convert_run_to_efficient_container_and_free(
run_container_t *c, uint8_t *typecode_after);
/**
* Create new container which is a union of run container and
* range [min, max]. Caller is responsible for freeing run container.
*/
container_t *container_from_run_range(const run_container_t *run, uint32_t min,
uint32_t max, uint8_t *typecode_after);
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
#endif /* INCLUDE_CONTAINERS_CONVERT_H_ */
+191
View File
@@ -0,0 +1,191 @@
/*
* mixed_andnot.h
*
* This header declares mixed-container difference operations of the form
* `A \ B` (also called andnot) between Roaring container types such as array,
* bitset, and run containers. These helpers are used when the operands have
* different internal representations and the result may need to change
* representation depending on density.
*
* The file includes both allocating and inplace-oriented variants so callers
* can either materialize a fresh result or reuse storage when that is
* efficient and semantically allowed.
*/
#ifndef INCLUDE_CONTAINERS_MIXED_ANDNOT_H_
#define INCLUDE_CONTAINERS_MIXED_ANDNOT_H_
#include <roaring/containers/array.h>
#include <roaring/containers/bitset.h>
#include <roaring/containers/run.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/* Compute the andnot of src_1 and src_2 and write the result to
* dst, a valid array container that could be the same as dst.*/
void array_bitset_container_andnot(const array_container_t *src_1,
const bitset_container_t *src_2,
array_container_t *dst);
/* Compute the andnot of src_1 and src_2 and write the result to
* src_1 */
void array_bitset_container_iandnot(array_container_t *src_1,
const bitset_container_t *src_2);
/* Compute the andnot of src_1 and src_2 and write the result to
* dst, which does not initially have a valid container.
* Return true for a bitset result; false for array
*/
bool bitset_array_container_andnot(const bitset_container_t *src_1,
const array_container_t *src_2,
container_t **dst);
/* Compute the andnot of src_1 and src_2 and write the result to
* dst (which has no container initially). It will modify src_1
* to be dst if the result is a bitset. Otherwise, it will
* free src_1 and dst will be a new array container. In both
* cases, the caller is responsible for deallocating dst.
* Returns true iff dst is a bitset */
bool bitset_array_container_iandnot(bitset_container_t *src_1,
const array_container_t *src_2,
container_t **dst);
/* Compute the andnot of src_1 and src_2 and write the result to
* dst. Result may be either a bitset or an array container
* (returns "result is bitset"). dst does not initially have
* any container, but becomes either a bitset container (return
* result true) or an array container.
*/
bool run_bitset_container_andnot(const run_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst);
/* Compute the andnot of src_1 and src_2 and write the result to
* dst. Result may be either a bitset or an array container
* (returns "result is bitset"). dst does not initially have
* any container, but becomes either a bitset container (return
* result true) or an array container.
*/
bool run_bitset_container_iandnot(run_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst);
/* Compute the andnot of src_1 and src_2 and write the result to
* dst. Result may be either a bitset or an array container
* (returns "result is bitset"). dst does not initially have
* any container, but becomes either a bitset container (return
* result true) or an array container.
*/
bool bitset_run_container_andnot(const bitset_container_t *src_1,
const run_container_t *src_2,
container_t **dst);
/* Compute the andnot of src_1 and src_2 and write the result to
* dst (which has no container initially). It will modify src_1
* to be dst if the result is a bitset. Otherwise, it will
* free src_1 and dst will be a new array container. In both
* cases, the caller is responsible for deallocating dst.
* Returns true iff dst is a bitset */
bool bitset_run_container_iandnot(bitset_container_t *src_1,
const run_container_t *src_2,
container_t **dst);
/* dst does not indicate a valid container initially. Eventually it
* can become any type of container.
*/
int run_array_container_andnot(const run_container_t *src_1,
const array_container_t *src_2,
container_t **dst);
/* Compute the andnot of src_1 and src_2 and write the result to
* dst (which has no container initially). It will modify src_1
* to be dst if the result is a bitset. Otherwise, it will
* free src_1 and dst will be a new array container. In both
* cases, the caller is responsible for deallocating dst.
* Returns true iff dst is a bitset */
int run_array_container_iandnot(run_container_t *src_1,
const array_container_t *src_2,
container_t **dst);
/* dst must be a valid array container, allowed to be src_1 */
void array_run_container_andnot(const array_container_t *src_1,
const run_container_t *src_2,
array_container_t *dst);
/* dst does not indicate a valid container initially. Eventually it
* can become any kind of container.
*/
void array_run_container_iandnot(array_container_t *src_1,
const run_container_t *src_2);
/* dst does not indicate a valid container initially. Eventually it
* can become any kind of container.
*/
int run_run_container_andnot(const run_container_t *src_1,
const run_container_t *src_2, container_t **dst);
/* Compute the andnot of src_1 and src_2 and write the result to
* dst (which has no container initially). It will modify src_1
* to be dst if the result is a bitset. Otherwise, it will
* free src_1 and dst will be a new array container. In both
* cases, the caller is responsible for deallocating dst.
* Returns true iff dst is a bitset */
int run_run_container_iandnot(run_container_t *src_1,
const run_container_t *src_2, container_t **dst);
/*
* dst is a valid array container and may be the same as src_1
*/
void array_array_container_andnot(const array_container_t *src_1,
const array_container_t *src_2,
array_container_t *dst);
/* inplace array-array andnot will always be able to reuse the space of
* src_1 */
void array_array_container_iandnot(array_container_t *src_1,
const array_container_t *src_2);
/* Compute the andnot of src_1 and src_2 and write the result to
* dst (which has no container initially). Return value is
* "dst is a bitset"
*/
bool bitset_bitset_container_andnot(const bitset_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst);
/* Compute the andnot of src_1 and src_2 and write the result to
* dst (which has no container initially). It will modify src_1
* to be dst if the result is a bitset. Otherwise, it will
* free src_1 and dst will be a new array container. In both
* cases, the caller is responsible for deallocating dst.
* Returns true iff dst is a bitset */
bool bitset_bitset_container_iandnot(bitset_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst);
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
#endif
+42
View File
@@ -0,0 +1,42 @@
/*
* mixed_equal.h
*
*/
#ifndef CONTAINERS_MIXED_EQUAL_H_
#define CONTAINERS_MIXED_EQUAL_H_
#include <roaring/containers/array.h>
#include <roaring/containers/bitset.h>
#include <roaring/containers/run.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/**
* Return true if the two containers have the same content.
*/
bool array_container_equal_bitset(const array_container_t* container1,
const bitset_container_t* container2);
/**
* Return true if the two containers have the same content.
*/
bool run_container_equals_array(const run_container_t* container1,
const array_container_t* container2);
/**
* Return true if the two containers have the same content.
*/
bool run_container_equals_bitset(const run_container_t* container1,
const bitset_container_t* container2);
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
#endif /* CONTAINERS_MIXED_EQUAL_H_ */
@@ -0,0 +1,110 @@
/*
* mixed_intersection.h
*
* This header declares intersection operations between different Roaring
* container types, such as array, bitset, and run containers. These mixed
* routines are used when the input containers have different internal
* representations and the implementation must choose the appropriate result
* form based on the data.
*
* In addition to materializing intersections, the file also provides helpers
* for intersection cardinality, intersection predicates, and selected inplace
* variants.
*/
#ifndef INCLUDE_CONTAINERS_MIXED_INTERSECTION_H_
#define INCLUDE_CONTAINERS_MIXED_INTERSECTION_H_
/* These functions appear to exclude cases where the
* inputs have the same type and the output is guaranteed
* to have the same type as the inputs. Eg, array intersection
*/
#include <roaring/containers/array.h>
#include <roaring/containers/bitset.h>
#include <roaring/containers/run.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/* Compute the intersection of src_1 and src_2 and write the result to
* dst. It is allowed for dst to be equal to src_1. We assume that dst is a
* valid container. */
void array_bitset_container_intersection(const array_container_t *src_1,
const bitset_container_t *src_2,
array_container_t *dst);
/* Compute the size of the intersection of src_1 and src_2. */
int array_bitset_container_intersection_cardinality(
const array_container_t *src_1, const bitset_container_t *src_2);
/* Checking whether src_1 and src_2 intersect. */
bool array_bitset_container_intersect(const array_container_t *src_1,
const bitset_container_t *src_2);
/*
* Compute the intersection between src_1 and src_2 and write the result
* to *dst. If the return function is true, the result is a bitset_container_t
* otherwise is a array_container_t. We assume that dst is not pre-allocated. In
* case of failure, *dst will be NULL.
*/
bool bitset_bitset_container_intersection(const bitset_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst);
/* Compute the intersection between src_1 and src_2 and write the result to
* dst. It is allowed for dst to be equal to src_1. We assume that dst is a
* valid container. */
void array_run_container_intersection(const array_container_t *src_1,
const run_container_t *src_2,
array_container_t *dst);
/* Compute the intersection between src_1 and src_2 and write the result to
* *dst. If the result is true then the result is a bitset_container_t
* otherwise is a array_container_t.
* If *dst == src_2, then an in-place intersection is attempted
**/
bool run_bitset_container_intersection(const run_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst);
/* Compute the size of the intersection between src_1 and src_2 . */
int array_run_container_intersection_cardinality(const array_container_t *src_1,
const run_container_t *src_2);
/* Compute the size of the intersection between src_1 and src_2
**/
int run_bitset_container_intersection_cardinality(
const run_container_t *src_1, const bitset_container_t *src_2);
/* Check that src_1 and src_2 intersect. */
bool array_run_container_intersect(const array_container_t *src_1,
const run_container_t *src_2);
/* Check that src_1 and src_2 intersect.
**/
bool run_bitset_container_intersect(const run_container_t *src_1,
const bitset_container_t *src_2);
/*
* Same as bitset_bitset_container_intersection except that if the output is to
* be a
* bitset_container_t, then src_1 is modified and no allocation is made.
* If the output is to be an array_container_t, then caller is responsible
* to free the container.
* In all cases, the result is in *dst.
*/
bool bitset_bitset_container_intersection_inplace(
bitset_container_t *src_1, const bitset_container_t *src_2,
container_t **dst);
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
#endif /* INCLUDE_CONTAINERS_MIXED_INTERSECTION_H_ */
@@ -0,0 +1,149 @@
/*
* mixed_negation.h
*
* This header declares negation (complement) operations for Roaring
* containers, both over the full 16-bit container domain and over specified
* subranges. Depending on the input representation and the density of the
* complement, the result may need to switch between array, bitset, and run
* containers.
*
* The file includes both allocating and inplace-oriented variants so callers
* can choose between simple result construction and reuse of an existing
* container when that is practical.
*/
#ifndef INCLUDE_CONTAINERS_MIXED_NEGATION_H_
#define INCLUDE_CONTAINERS_MIXED_NEGATION_H_
#include <roaring/containers/array.h>
#include <roaring/containers/bitset.h>
#include <roaring/containers/run.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/* Negation across the entire range of the container.
* Compute the negation of src and write the result
* to *dst. The complement of a
* sufficiently sparse set will always be dense and a hence a bitmap
* We assume that dst is pre-allocated and a valid bitset container
* There can be no in-place version.
*/
void array_container_negation(const array_container_t *src,
bitset_container_t *dst);
/* Negation across the entire range of the container
* Compute the negation of src and write the result
* to *dst. A true return value indicates a bitset result,
* otherwise the result is an array container.
* We assume that dst is not pre-allocated. In
* case of failure, *dst will be NULL.
*/
bool bitset_container_negation(const bitset_container_t *src,
container_t **dst);
/* inplace version */
/*
* Same as bitset_container_negation except that if the output is to
* be a
* bitset_container_t, then src is modified and no allocation is made.
* If the output is to be an array_container_t, then caller is responsible
* to free the container.
* In all cases, the result is in *dst.
*/
bool bitset_container_negation_inplace(bitset_container_t *src,
container_t **dst);
/* Negation across the entire range of container
* Compute the negation of src and write the result
* to *dst.
* Return values are the *_TYPECODES as defined * in containers.h
* We assume that dst is not pre-allocated. In
* case of failure, *dst will be NULL.
*/
int run_container_negation(const run_container_t *src, container_t **dst);
/*
* Same as run_container_negation except that if the output is to
* be a
* run_container_t, and has the capacity to hold the result,
* then src is modified and no allocation is made.
* In all cases, the result is in *dst.
*/
int run_container_negation_inplace(run_container_t *src, container_t **dst);
/* Negation across a range of the container.
* Compute the negation of src and write the result
* to *dst. Returns true if the result is a bitset container
* and false for an array container. *dst is not preallocated.
*/
bool array_container_negation_range(const array_container_t *src,
const int range_start, const int range_end,
container_t **dst);
/* Even when the result would fit, it is unclear how to make an
* inplace version without inefficient copying. Thus this routine
* may be a wrapper for the non-in-place version
*/
bool array_container_negation_range_inplace(array_container_t *src,
const int range_start,
const int range_end,
container_t **dst);
/* Negation across a range of the container
* Compute the negation of src and write the result
* to *dst. A true return value indicates a bitset result,
* otherwise the result is an array container.
* We assume that dst is not pre-allocated. In
* case of failure, *dst will be NULL.
*/
bool bitset_container_negation_range(const bitset_container_t *src,
const int range_start, const int range_end,
container_t **dst);
/* inplace version */
/*
* Same as bitset_container_negation except that if the output is to
* be a
* bitset_container_t, then src is modified and no allocation is made.
* If the output is to be an array_container_t, then caller is responsible
* to free the container.
* In all cases, the result is in *dst.
*/
bool bitset_container_negation_range_inplace(bitset_container_t *src,
const int range_start,
const int range_end,
container_t **dst);
/* Negation across a range of container
* Compute the negation of src and write the result
* to *dst. Return values are the *_TYPECODES as defined * in containers.h
* We assume that dst is not pre-allocated. In
* case of failure, *dst will be NULL.
*/
int run_container_negation_range(const run_container_t *src,
const int range_start, const int range_end,
container_t **dst);
/*
* Same as run_container_negation except that if the output is to
* be a
* run_container_t, and has the capacity to hold the result,
* then src is modified and no allocation is made.
* In all cases, the result is in *dst.
*/
int run_container_negation_range_inplace(run_container_t *src,
const int range_start,
const int range_end,
container_t **dst);
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
#endif /* INCLUDE_CONTAINERS_MIXED_NEGATION_H_ */
+61
View File
@@ -0,0 +1,61 @@
/*
* mixed_subset.h
*
* This header declares subset-checking routines between different Roaring
* container types. These helpers are used when two containers do not share the
* same representation and a direct type-specific subset predicate is needed.
*
* Each function answers whether all values from one container are contained in
* another, across combinations of array, bitset, and run containers.
*/
#ifndef CONTAINERS_MIXED_SUBSET_H_
#define CONTAINERS_MIXED_SUBSET_H_
#include <roaring/containers/array.h>
#include <roaring/containers/bitset.h>
#include <roaring/containers/run.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/**
* Return true if container1 is a subset of container2.
*/
bool array_container_is_subset_bitset(const array_container_t* container1,
const bitset_container_t* container2);
/**
* Return true if container1 is a subset of container2.
*/
bool run_container_is_subset_array(const run_container_t* container1,
const array_container_t* container2);
/**
* Return true if container1 is a subset of container2.
*/
bool array_container_is_subset_run(const array_container_t* container1,
const run_container_t* container2);
/**
* Return true if container1 is a subset of container2.
*/
bool run_container_is_subset_bitset(const run_container_t* container1,
const bitset_container_t* container2);
/**
* Return true if container1 is a subset of container2.
*/
bool bitset_container_is_subset_run(const bitset_container_t* container1,
const run_container_t* container2);
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
#endif /* CONTAINERS_MIXED_SUBSET_H_ */
+125
View File
@@ -0,0 +1,125 @@
/*
* mixed_union.h
*
* This header declares union operations between different Roaring container
* types, such as array, bitset, and run containers. These mixed-operation
* helpers are used when the two input containers do not share the same
* representation and the result may need to stay in, or be converted to, a
* representation chosen according to the data.
*
* The file includes regular, lazy, and inplace variants so callers can select
* between fully maintained results and faster deferred-maintenance paths.
*/
#ifndef INCLUDE_CONTAINERS_MIXED_UNION_H_
#define INCLUDE_CONTAINERS_MIXED_UNION_H_
/* These functions appear to exclude cases where the
* inputs have the same type and the output is guaranteed
* to have the same type as the inputs. Eg, bitset unions
*/
#include <roaring/containers/array.h>
#include <roaring/containers/bitset.h>
#include <roaring/containers/run.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/* Compute the union of src_1 and src_2 and write the result to
* dst. It is allowed for src_2 to be dst. */
void array_bitset_container_union(const array_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* Compute the union of src_1 and src_2 and write the result to
* dst. It is allowed for src_2 to be dst. This version does not
* update the cardinality of dst (it is set to BITSET_UNKNOWN_CARDINALITY). */
void array_bitset_container_lazy_union(const array_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/*
* Compute the union between src_1 and src_2 and write the result
* to *dst. If the return function is true, the result is a bitset_container_t
* otherwise is a array_container_t. We assume that dst is not pre-allocated. In
* case of failure, *dst will be NULL.
*/
bool array_array_container_union(const array_container_t *src_1,
const array_container_t *src_2,
container_t **dst);
/*
* Compute the union between src_1 and src_2 and write the result
* to *dst if it cannot be written to src_1. If the return function is true,
* the result is a bitset_container_t
* otherwise is a array_container_t. When the result is an array_container_t, it
* it either written to src_1 (if *dst is null) or to *dst.
* If the result is a bitset_container_t and *dst is null, then there was a
* failure.
*/
bool array_array_container_inplace_union(array_container_t *src_1,
const array_container_t *src_2,
container_t **dst);
/*
* Same as array_array_container_union except that it will more eagerly produce
* a bitset.
*/
bool array_array_container_lazy_union(const array_container_t *src_1,
const array_container_t *src_2,
container_t **dst);
/*
* Same as array_array_container_inplace_union except that it will more eagerly
* produce a bitset.
*/
bool array_array_container_lazy_inplace_union(array_container_t *src_1,
const array_container_t *src_2,
container_t **dst);
/* Compute the union of src_1 and src_2 and write the result to
* dst. We assume that dst is a
* valid container. The result might need to be further converted to array or
* bitset container,
* the caller is responsible for the eventual conversion. */
void array_run_container_union(const array_container_t *src_1,
const run_container_t *src_2,
run_container_t *dst);
/* Compute the union of src_1 and src_2 and write the result to
* src2. The result might need to be further converted to array or
* bitset container,
* the caller is responsible for the eventual conversion. */
void array_run_container_inplace_union(const array_container_t *src_1,
run_container_t *src_2);
/* Compute the union of src_1 and src_2 and write the result to
* dst. It is allowed for dst to be src_2.
* If run_container_is_full(src_1) is true, you must not be calling this
*function.
**/
void run_bitset_container_union(const run_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* Compute the union of src_1 and src_2 and write the result to
* dst. It is allowed for dst to be src_2. This version does not
* update the cardinality of dst (it is set to BITSET_UNKNOWN_CARDINALITY).
* If run_container_is_full(src_1) is true, you must not be calling this
* function.
* */
void run_bitset_container_lazy_union(const run_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
#endif /* INCLUDE_CONTAINERS_MIXED_UNION_H_ */
+185
View File
@@ -0,0 +1,185 @@
/*
* mixed_xor.h
*
* This header declares XOR operations between different Roaring container
* types, such as array, bitset, and run containers. These "mixed" routines
* handle cases where the two inputs do not share the same representation and
* where the most appropriate output representation may depend on the data.
*
* It includes regular, lazy, and inplace variants so higher-level bitmap code
* can choose between fully normalized results and faster deferred-maintenance
* paths.
*/
#ifndef INCLUDE_CONTAINERS_MIXED_XOR_H_
#define INCLUDE_CONTAINERS_MIXED_XOR_H_
/* These functions appear to exclude cases where the
* inputs have the same type and the output is guaranteed
* to have the same type as the inputs. Eg, bitset unions
*/
/*
* Java implementation (as of May 2016) for array_run, run_run
* and bitset_run don't do anything different for inplace.
* (They are not truly in place.)
*/
#include <roaring/containers/array.h>
#include <roaring/containers/bitset.h>
#include <roaring/containers/run.h>
// #include "containers.h"
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/* Compute the xor of src_1 and src_2 and write the result to
* dst (which has no container initially).
* Result is true iff dst is a bitset */
bool array_bitset_container_xor(const array_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst);
/* Compute the xor of src_1 and src_2 and write the result to
* dst. It is allowed for src_2 to be dst. This version does not
* update the cardinality of dst (it is set to BITSET_UNKNOWN_CARDINALITY).
*/
void array_bitset_container_lazy_xor(const array_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* Compute the xor of src_1 and src_2 and write the result to
* dst (which has no container initially). Return value is
* "dst is a bitset"
*/
bool bitset_bitset_container_xor(const bitset_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst);
/* Compute the xor of src_1 and src_2 and write the result to
* dst. Result may be either a bitset or an array container
* (returns "result is bitset"). dst does not initially have
* any container, but becomes either a bitset container (return
* result true) or an array container.
*/
bool run_bitset_container_xor(const run_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst);
/* lazy xor. Dst is initialized and may be equal to src_2.
* Result is left as a bitset container, even if actual
* cardinality would dictate an array container.
*/
void run_bitset_container_lazy_xor(const run_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst);
/* dst does not indicate a valid container initially. Eventually it
* can become any kind of container.
*/
int array_run_container_xor(const array_container_t *src_1,
const run_container_t *src_2, container_t **dst);
/* dst does not initially have a valid container. Creates either
* an array or a bitset container, indicated by return code
*/
bool array_array_container_xor(const array_container_t *src_1,
const array_container_t *src_2,
container_t **dst);
/* dst does not initially have a valid container. Creates either
* an array or a bitset container, indicated by return code.
* A bitset container will not have a valid cardinality and the
* container type might not be correct for the actual cardinality
*/
bool array_array_container_lazy_xor(const array_container_t *src_1,
const array_container_t *src_2,
container_t **dst);
/* Dst is a valid run container. (Can it be src_2? Let's say not.)
* Leaves result as run container, even if other options are
* smaller.
*/
void array_run_container_lazy_xor(const array_container_t *src_1,
const run_container_t *src_2,
run_container_t *dst);
/* dst does not indicate a valid container initially. Eventually it
* can become any kind of container.
*/
int run_run_container_xor(const run_container_t *src_1,
const run_container_t *src_2, container_t **dst);
/* INPLACE versions (initial implementation may not exploit all inplace
* opportunities (if any...)
*/
/* Compute the xor of src_1 and src_2 and write the result to
* dst (which has no container initially). It will modify src_1
* to be dst if the result is a bitset. Otherwise, it will
* free src_1 and dst will be a new array container. In both
* cases, the caller is responsible for deallocating dst.
* Returns true iff dst is a bitset */
bool bitset_array_container_ixor(bitset_container_t *src_1,
const array_container_t *src_2,
container_t **dst);
bool bitset_bitset_container_ixor(bitset_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst);
bool array_bitset_container_ixor(array_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst);
/* Compute the xor of src_1 and src_2 and write the result to
* dst. Result may be either a bitset or an array container
* (returns "result is bitset"). dst does not initially have
* any container, but becomes either a bitset container (return
* result true) or an array container.
*/
bool run_bitset_container_ixor(run_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst);
bool bitset_run_container_ixor(bitset_container_t *src_1,
const run_container_t *src_2, container_t **dst);
/* dst does not indicate a valid container initially. Eventually it
* can become any kind of container.
*/
int array_run_container_ixor(array_container_t *src_1,
const run_container_t *src_2, container_t **dst);
int run_array_container_ixor(run_container_t *src_1,
const array_container_t *src_2, container_t **dst);
bool array_array_container_ixor(array_container_t *src_1,
const array_container_t *src_2,
container_t **dst);
int run_run_container_ixor(run_container_t *src_1, const run_container_t *src_2,
container_t **dst);
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
#endif
@@ -0,0 +1,60 @@
/*
* perfparameters.h
*
* This header centralizes a small set of performance-tuning constants and
* heuristic defaults used by CRoaring container code. These parameters control
* decisions such as initial container sizing and when lazy or eager operations
* may convert between container representations.
*
* In practice, these values encode trade-offs between memory use, allocation
* overhead, and execution speed for common workloads.
*/
#ifndef PERFPARAMETERS_H_
#define PERFPARAMETERS_H_
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/**
During lazy computations, we can transform array containers into bitset
containers as
long as we can expect them to have ARRAY_LAZY_LOWERBOUND values.
*/
enum { ARRAY_LAZY_LOWERBOUND = 1024 };
/* default initial size of a run container
setting it to zero delays the malloc.*/
enum { RUN_DEFAULT_INIT_SIZE = 0 };
/* default initial size of an array container
setting it to zero delays the malloc */
enum { ARRAY_DEFAULT_INIT_SIZE = 0 };
/* automatic bitset conversion during lazy or */
#ifndef LAZY_OR_BITSET_CONVERSION
#define LAZY_OR_BITSET_CONVERSION true
#endif
/* automatically attempt to convert a bitset to a full run during lazy
* evaluation */
#ifndef LAZY_OR_BITSET_CONVERSION_TO_FULL
#define LAZY_OR_BITSET_CONVERSION_TO_FULL true
#endif
/* automatically attempt to convert a bitset to a full run */
#ifndef OR_BITSET_CONVERSION_TO_FULL
#define OR_BITSET_CONVERSION_TO_FULL true
#endif
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
#endif
+726
View File
@@ -0,0 +1,726 @@
/*
* run.h
*
* Run containers store a set of 16-bit integers as a sorted array of
* non-overlapping runs. Each run is represented by a starting value and a
* length, encoding one contiguous interval of present integers.
*
* This representation is effective when the data contains long consecutive
* ranges because it compresses many adjacent values into a small number of
* run records while still supporting search and set operations over the
* interval list.
*/
#ifndef INCLUDE_CONTAINERS_RUN_H_
#define INCLUDE_CONTAINERS_RUN_H_
#include <roaring/roaring_types.h> // roaring_iterator
// Include other headers after roaring_types.h
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <roaring/array_util.h> // binarySearch()/memequals() for inlining
#include <roaring/containers/container_defs.h> // container_t, perfparameters
#include <roaring/portability.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
// Note: in pure C++ code, you should avoid putting `using` in header files
using api::roaring_iterator;
using api::roaring_iterator64;
namespace internal {
#endif
/* struct rle16_s - run length pair
*
* @value: start position of the run
* @length: length of the run is `length + 1`
*
* An RLE pair {v, l} would represent the integers between the interval
* [v, v+l+1], e.g. {3, 2} = [3, 4, 5].
*/
struct rle16_s {
uint16_t value;
uint16_t length;
};
typedef struct rle16_s rle16_t;
#ifdef __cplusplus
#define CROARING_MAKE_RLE16(val, len) \
{ (uint16_t)(val), (uint16_t)(len) } // no tagged structs until c++20
#else
#define CROARING_MAKE_RLE16(val, len) \
(rle16_t) { .value = (uint16_t)(val), .length = (uint16_t)(len) }
#endif
/* struct run_container_s - run container bitmap
*
* @n_runs: number of rle_t pairs in `runs`.
* @capacity: capacity in rle_t pairs `runs` can hold.
* @runs: pairs of rle_t.
*/
STRUCT_CONTAINER(run_container_s) {
int32_t n_runs;
int32_t capacity;
rle16_t *runs;
};
typedef struct run_container_s run_container_t;
#define CAST_run(c) CAST(run_container_t *, c) // safer downcast
#define const_CAST_run(c) CAST(const run_container_t *, c)
#define movable_CAST_run(c) movable_CAST(run_container_t **, c)
/* Create a new run container. Return NULL in case of failure. */
run_container_t *run_container_create(void);
/* Create a new run container with given capacity. Return NULL in case of
* failure. */
run_container_t *run_container_create_given_capacity(int32_t size);
/*
* Shrink the capacity to the actual size, return the number of bytes saved.
*/
int run_container_shrink_to_fit(run_container_t *src);
/* Free memory owned by `run'. */
void run_container_free(run_container_t *run);
/* Duplicate container */
run_container_t *run_container_clone(const run_container_t *src);
/*
* Effectively deletes the value at index index, repacking data.
*/
static inline void recoverRoomAtIndex(run_container_t *run, uint16_t index) {
memmove(run->runs + index, run->runs + (1 + index),
(run->n_runs - index - 1) * sizeof(rle16_t));
run->n_runs--;
}
/**
* Good old binary search through rle data
*/
inline int32_t interleavedBinarySearch(const rle16_t *array, int32_t lenarray,
uint16_t ikey) {
int32_t low = 0;
int32_t high = lenarray - 1;
while (low <= high) {
int32_t middleIndex = (low + high) >> 1;
uint16_t middleValue = array[middleIndex].value;
if (middleValue < ikey) {
low = middleIndex + 1;
} else if (middleValue > ikey) {
high = middleIndex - 1;
} else {
return middleIndex;
}
}
return -(low + 1);
}
/*
* Returns index of the run which contains $ikey
*/
static inline int32_t rle16_find_run(const rle16_t *array, int32_t lenarray,
uint16_t ikey) {
int32_t low = 0;
int32_t high = lenarray - 1;
while (low <= high) {
int32_t middleIndex = (low + high) >> 1;
uint16_t min = array[middleIndex].value;
uint16_t max = array[middleIndex].value + array[middleIndex].length;
if (ikey > max) {
low = middleIndex + 1;
} else if (ikey < min) {
high = middleIndex - 1;
} else {
return middleIndex;
}
}
return -(low + 1);
}
/**
* Returns number of runs which can'be be merged with the key because they
* are less than the key.
* Note that [5,6,7,8] can be merged with the key 9 and won't be counted.
*/
static inline int32_t rle16_count_less(const rle16_t *array, int32_t lenarray,
uint16_t key) {
if (lenarray == 0) return 0;
int32_t low = 0;
int32_t high = lenarray - 1;
while (low <= high) {
int32_t middleIndex = (low + high) >> 1;
uint16_t min_value = array[middleIndex].value;
uint16_t max_value =
array[middleIndex].value + array[middleIndex].length;
if (max_value + UINT32_C(1) < key) { // uint32 arithmetic
low = middleIndex + 1;
} else if (key < min_value) {
high = middleIndex - 1;
} else {
return middleIndex;
}
}
return low;
}
static inline int32_t rle16_count_greater(const rle16_t *array,
int32_t lenarray, uint16_t key) {
if (lenarray == 0) return 0;
int32_t low = 0;
int32_t high = lenarray - 1;
while (low <= high) {
int32_t middleIndex = (low + high) >> 1;
uint16_t min_value = array[middleIndex].value;
uint16_t max_value =
array[middleIndex].value + array[middleIndex].length;
if (max_value < key) {
low = middleIndex + 1;
} else if (key + UINT32_C(1) < min_value) { // uint32 arithmetic
high = middleIndex - 1;
} else {
return lenarray - (middleIndex + 1);
}
}
return lenarray - low;
}
/**
* increase capacity to at least min. Whether the
* existing data needs to be copied over depends on copy. If "copy" is false,
* then the new content will be uninitialized, otherwise a copy is made.
*/
void run_container_grow(run_container_t *run, int32_t min, bool copy);
/**
* Moves the data so that we can write data at index
*/
static inline void makeRoomAtIndex(run_container_t *run, uint16_t index) {
/* This function calls realloc + memmove sequentially to move by one index.
* Potentially copying twice the array.
*/
if (run->n_runs + 1 > run->capacity)
run_container_grow(run, run->n_runs + 1, true);
memmove(run->runs + 1 + index, run->runs + index,
(run->n_runs - index) * sizeof(rle16_t));
run->n_runs++;
}
/* Add `pos' to `run'. Returns true if `pos' was not present. */
bool run_container_add(run_container_t *run, uint16_t pos);
/* Remove `pos' from `run'. Returns true if `pos' was present. */
static inline bool run_container_remove(run_container_t *run, uint16_t pos) {
int32_t index = interleavedBinarySearch(run->runs, run->n_runs, pos);
if (index >= 0) {
int32_t le = run->runs[index].length;
if (le == 0) {
recoverRoomAtIndex(run, (uint16_t)index);
} else {
run->runs[index].value++;
run->runs[index].length--;
}
return true;
}
index = -index - 2; // points to preceding value, possibly -1
if (index >= 0) { // possible match
int32_t offset = pos - run->runs[index].value;
int32_t le = run->runs[index].length;
if (offset < le) {
// need to break in two
run->runs[index].length = (uint16_t)(offset - 1);
// need to insert
uint16_t newvalue = pos + 1;
int32_t newlength = le - offset - 1;
makeRoomAtIndex(run, (uint16_t)(index + 1));
run->runs[index + 1].value = newvalue;
run->runs[index + 1].length = (uint16_t)newlength;
return true;
} else if (offset == le) {
run->runs[index].length--;
return true;
}
}
// no match
return false;
}
/* Check whether `pos' is present in `run'. */
inline bool run_container_contains(const run_container_t *run, uint16_t pos) {
int32_t index = interleavedBinarySearch(run->runs, run->n_runs, pos);
if (index >= 0) return true;
index = -index - 2; // points to preceding value, possibly -1
if (index != -1) { // possible match
int32_t offset = pos - run->runs[index].value;
int32_t le = run->runs[index].length;
if (offset <= le) return true;
}
return false;
}
/*
* Check whether all positions in a range of positions from pos_start (included)
* to pos_end (excluded) is present in `run'.
*/
static inline bool run_container_contains_range(const run_container_t *run,
uint32_t pos_start,
uint32_t pos_end) {
uint32_t count = 0;
int32_t index =
interleavedBinarySearch(run->runs, run->n_runs, (uint16_t)pos_start);
if (index < 0) {
index = -index - 2;
if ((index == -1) ||
((pos_start - run->runs[index].value) > run->runs[index].length)) {
return false;
}
}
for (int32_t i = index; i < run->n_runs; ++i) {
const uint32_t stop = run->runs[i].value + run->runs[i].length;
if (run->runs[i].value >= pos_end) break;
if (stop >= pos_end) {
count += (((pos_end - run->runs[i].value) > 0)
? (pos_end - run->runs[i].value)
: 0);
break;
}
const uint32_t min = (stop - pos_start) > 0 ? (stop - pos_start) : 0;
count += (min < run->runs[i].length) ? min : run->runs[i].length;
}
return count >= (pos_end - pos_start - 1);
}
/* Get the cardinality of `run'. Requires an actual computation. */
int run_container_cardinality(const run_container_t *run);
/* Card > 0?, see run_container_empty for the reverse */
static inline bool run_container_nonzero_cardinality(
const run_container_t *run) {
return run->n_runs > 0; // runs never empty
}
/* Card == 0?, see run_container_nonzero_cardinality for the reverse */
static inline bool run_container_empty(const run_container_t *run) {
return run->n_runs == 0; // runs never empty
}
/* Copy one container into another. We assume that they are distinct. */
void run_container_copy(const run_container_t *src, run_container_t *dst);
/**
* Append run described by vl to the run container, possibly merging.
* It is assumed that the run would be inserted at the end of the container, no
* check is made.
* It is assumed that the run container has the necessary capacity: caller is
* responsible for checking memory capacity.
*
*
* This is not a safe function, it is meant for performance: use with care.
*/
static inline void run_container_append(run_container_t *run, rle16_t vl,
rle16_t *previousrl) {
const uint32_t previousend = previousrl->value + previousrl->length;
if (vl.value > previousend + 1) { // we add a new one
run->runs[run->n_runs] = vl;
run->n_runs++;
*previousrl = vl;
} else {
uint32_t newend = vl.value + vl.length + UINT32_C(1);
if (newend > previousend) { // we merge
previousrl->length = (uint16_t)(newend - 1 - previousrl->value);
run->runs[run->n_runs - 1] = *previousrl;
}
}
}
/**
* Like run_container_append but it is assumed that the content of run is empty.
*/
static inline rle16_t run_container_append_first(run_container_t *run,
rle16_t vl) {
run->runs[run->n_runs] = vl;
run->n_runs++;
return vl;
}
/**
* append a single value given by val to the run container, possibly merging.
* It is assumed that the value would be inserted at the end of the container,
* no check is made.
* It is assumed that the run container has the necessary capacity: caller is
* responsible for checking memory capacity.
*
* This is not a safe function, it is meant for performance: use with care.
*/
static inline void run_container_append_value(run_container_t *run,
uint16_t val,
rle16_t *previousrl) {
const uint32_t previousend = previousrl->value + previousrl->length;
if (val > previousend + 1) { // we add a new one
*previousrl = CROARING_MAKE_RLE16(val, 0);
run->runs[run->n_runs] = *previousrl;
run->n_runs++;
} else if (val == previousend + 1) { // we merge
previousrl->length++;
run->runs[run->n_runs - 1] = *previousrl;
}
}
/**
* Like run_container_append_value but it is assumed that the content of run is
* empty.
*/
static inline rle16_t run_container_append_value_first(run_container_t *run,
uint16_t val) {
rle16_t newrle = CROARING_MAKE_RLE16(val, 0);
run->runs[run->n_runs] = newrle;
run->n_runs++;
return newrle;
}
/* Check whether the container spans the whole chunk (cardinality = 1<<16).
* This check can be done in constant time (inexpensive). */
static inline bool run_container_is_full(const run_container_t *run) {
rle16_t vl = run->runs[0];
return (run->n_runs == 1) && (vl.value == 0) && (vl.length == 0xFFFF);
}
/* Compute the union of `src_1' and `src_2' and write the result to `dst'
* It is assumed that `dst' is distinct from both `src_1' and `src_2'. */
void run_container_union(const run_container_t *src_1,
const run_container_t *src_2, run_container_t *dst);
/* Compute the union of `src_1' and `src_2' and write the result to `src_1' */
void run_container_union_inplace(run_container_t *src_1,
const run_container_t *src_2);
/* Compute the intersection of src_1 and src_2 and write the result to
* dst. It is assumed that dst is distinct from both src_1 and src_2. */
void run_container_intersection(const run_container_t *src_1,
const run_container_t *src_2,
run_container_t *dst);
/* Compute the size of the intersection of src_1 and src_2 . */
int run_container_intersection_cardinality(const run_container_t *src_1,
const run_container_t *src_2);
/* Check whether src_1 and src_2 intersect. */
bool run_container_intersect(const run_container_t *src_1,
const run_container_t *src_2);
/* Compute the symmetric difference of `src_1' and `src_2' and write the result
* to `dst'
* It is assumed that `dst' is distinct from both `src_1' and `src_2'. */
void run_container_xor(const run_container_t *src_1,
const run_container_t *src_2, run_container_t *dst);
/*
* Write out the 16-bit integers contained in this container as a list of 32-bit
* integers using base
* as the starting value (it might be expected that base has zeros in its 16
* least significant bits).
* The function returns the number of values written.
* The caller is responsible for allocating enough memory in out.
*/
int run_container_to_uint32_array(void *vout, const run_container_t *cont,
uint32_t base);
/*
* Print this container using printf (useful for debugging).
*/
void run_container_printf(const run_container_t *v);
/*
* Print this container using printf as a comma-separated list of 32-bit
* integers starting at base.
*/
void run_container_printf_as_uint32_array(const run_container_t *v,
uint32_t base);
bool run_container_validate(const run_container_t *run, const char **reason);
/**
* Return the serialized size in bytes of a container having "num_runs" runs.
*/
static inline int32_t run_container_serialized_size_in_bytes(int32_t num_runs) {
return sizeof(uint16_t) +
sizeof(rle16_t) * num_runs; // each run requires 2 2-byte entries.
}
bool run_container_iterate(const run_container_t *cont, uint32_t base,
roaring_iterator iterator, void *ptr);
bool run_container_iterate64(const run_container_t *cont, uint32_t base,
roaring_iterator64 iterator, uint64_t high_bits,
void *ptr);
/**
* Writes the underlying array to buf, outputs how many bytes were written.
* This is meant to be byte-by-byte compatible with the Java and Go versions of
* Roaring.
* The number of bytes written should be run_container_size_in_bytes(container).
*/
int32_t run_container_write(const run_container_t *container, char *buf);
/**
* 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.
* The number of bytes read should be bitset_container_size_in_bytes(container).
* The cardinality parameter is provided for consistency with other containers,
* but
* it might be effectively ignored..
*/
int32_t run_container_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.
*/
CROARING_ALLOW_UNALIGNED
static inline int32_t run_container_size_in_bytes(
const run_container_t *container) {
return run_container_serialized_size_in_bytes(container->n_runs);
}
/**
* Return true if the two containers have the same content.
*/
CROARING_ALLOW_UNALIGNED
static inline bool run_container_equals(const run_container_t *container1,
const run_container_t *container2) {
if (container1->n_runs != container2->n_runs) {
return false;
}
return memequals(container1->runs, container2->runs,
container1->n_runs * sizeof(rle16_t));
}
/**
* Return true if container1 is a subset of container2.
*/
bool run_container_is_subset(const run_container_t *container1,
const run_container_t *container2);
/**
* Used in a start-finish scan that appends segments, for XOR and NOT
*/
void run_container_smart_append_exclusive(run_container_t *src,
const uint16_t start,
const uint16_t length);
/**
* The new container consists of a single run [start,stop).
* It is required that stop>start, the caller is responsability for this check.
* It is required that stop <= (1<<16), the caller is responsability for this
* check. The cardinality of the created container is stop - start. Returns NULL
* on failure
*/
static inline run_container_t *run_container_create_range(uint32_t start,
uint32_t stop) {
run_container_t *rc = run_container_create_given_capacity(1);
if (rc) {
rle16_t r;
r.value = (uint16_t)start;
r.length = (uint16_t)(stop - start - 1);
run_container_append_first(rc, r);
}
return rc;
}
/**
* If the element of given rank is in this container, supposing that the first
* element has rank start_rank, then the function returns true and sets element
* accordingly.
* Otherwise, it returns false and update start_rank.
*/
bool run_container_select(const run_container_t *container,
uint32_t *start_rank, uint32_t rank,
uint32_t *element);
/* Compute the difference of src_1 and src_2 and write the result to
* dst. It is assumed that dst is distinct from both src_1 and src_2. */
void run_container_andnot(const run_container_t *src_1,
const run_container_t *src_2, run_container_t *dst);
void run_container_offset(const run_container_t *c, container_t **loc,
container_t **hic, uint16_t offset);
/* Returns the smallest value (assumes not empty) */
inline uint16_t run_container_minimum(const run_container_t *run) {
if (run->n_runs == 0) return 0;
return run->runs[0].value;
}
/* Returns the largest value (assumes not empty) */
inline uint16_t run_container_maximum(const run_container_t *run) {
if (run->n_runs == 0) return 0;
return run->runs[run->n_runs - 1].value + run->runs[run->n_runs - 1].length;
}
/* Returns the number of values equal or smaller than x */
int run_container_rank(const run_container_t *arr, uint16_t x);
/* bulk version of run_container_rank(); return number of consumed elements */
uint32_t run_container_rank_many(const run_container_t *arr,
uint64_t start_rank, const uint32_t *begin,
const uint32_t *end, uint64_t *ans);
/* Returns the index of x, if not exsist return -1 */
int run_container_get_index(const run_container_t *arr, uint16_t x);
/* Returns the index of the first run containing a value at least as large as x,
* or -1 */
inline int run_container_index_equalorlarger(const run_container_t *arr,
uint16_t x) {
int32_t index = interleavedBinarySearch(arr->runs, arr->n_runs, x);
if (index >= 0) return index;
index = -index - 2; // points to preceding run, possibly -1
if (index != -1) { // possible match
int32_t offset = x - arr->runs[index].value;
int32_t le = arr->runs[index].length;
if (offset <= le) return index;
}
index += 1;
if (index < arr->n_runs) {
return index;
}
return -1;
}
/*
* Add all values in range [min, max] using hint.
*/
static inline void run_container_add_range_nruns(run_container_t *run,
uint32_t min, uint32_t max,
int32_t nruns_less,
int32_t nruns_greater) {
int32_t nruns_common = run->n_runs - nruns_less - nruns_greater;
if (nruns_common == 0) {
makeRoomAtIndex(run, (uint16_t)nruns_less);
run->runs[nruns_less].value = (uint16_t)min;
run->runs[nruns_less].length = (uint16_t)(max - min);
} else {
uint32_t common_min = run->runs[nruns_less].value;
uint32_t common_max = run->runs[nruns_less + nruns_common - 1].value +
run->runs[nruns_less + nruns_common - 1].length;
uint32_t result_min = (common_min < min) ? common_min : min;
uint32_t result_max = (common_max > max) ? common_max : max;
run->runs[nruns_less].value = (uint16_t)result_min;
run->runs[nruns_less].length = (uint16_t)(result_max - result_min);
memmove(&(run->runs[nruns_less + 1]),
&(run->runs[run->n_runs - nruns_greater]),
nruns_greater * sizeof(rle16_t));
run->n_runs = nruns_less + 1 + nruns_greater;
}
}
/**
* Add all values in range [min, max]. This function is currently unused
* and left as documentation.
*/
/*static inline void run_container_add_range(run_container_t* run,
uint32_t min, uint32_t max) {
int32_t nruns_greater = rle16_count_greater(run->runs, run->n_runs, max);
int32_t nruns_less = rle16_count_less(run->runs, run->n_runs -
nruns_greater, min); run_container_add_range_nruns(run, min, max, nruns_less,
nruns_greater);
}*/
/**
* Shifts last $count elements either left (distance < 0) or right (distance >
* 0)
*/
static inline void run_container_shift_tail(run_container_t *run, int32_t count,
int32_t distance) {
if (distance > 0) {
if (run->capacity < count + distance) {
run_container_grow(run, count + distance, true);
}
}
int32_t srcpos = run->n_runs - count;
int32_t dstpos = srcpos + distance;
memmove(&(run->runs[dstpos]), &(run->runs[srcpos]),
sizeof(rle16_t) * count);
run->n_runs += distance;
}
/**
* Remove all elements in range [min, max]
*/
static inline void run_container_remove_range(run_container_t *run,
uint32_t min, uint32_t max) {
int32_t first = rle16_find_run(run->runs, run->n_runs, (uint16_t)min);
int32_t last = rle16_find_run(run->runs, run->n_runs, (uint16_t)max);
if (first >= 0 && min > run->runs[first].value &&
max < ((uint32_t)run->runs[first].value +
(uint32_t)run->runs[first].length)) {
// split this run into two adjacent runs
// right subinterval
makeRoomAtIndex(run, (uint16_t)(first + 1));
run->runs[first + 1].value = (uint16_t)(max + 1);
run->runs[first + 1].length =
(uint16_t)((run->runs[first].value + run->runs[first].length) -
(max + 1));
// left subinterval
run->runs[first].length =
(uint16_t)((min - 1) - run->runs[first].value);
return;
}
// update left-most partial run
if (first >= 0) {
if (min > run->runs[first].value) {
run->runs[first].length =
(uint16_t)((min - 1) - run->runs[first].value);
first++;
}
} else {
first = -first - 1;
}
// update right-most run
if (last >= 0) {
uint16_t run_max = run->runs[last].value + run->runs[last].length;
if (run_max > max) {
run->runs[last].value = (uint16_t)(max + 1);
run->runs[last].length = (uint16_t)(run_max - (max + 1));
last--;
}
} else {
last = (-last - 1) - 1;
}
// remove intermediate runs
if (first <= last) {
run_container_shift_tail(run, run->n_runs - (last + 1),
-(last - first + 1));
}
}
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
#endif /* INCLUDE_CONTAINERS_RUN_H_ */
+53
View File
@@ -0,0 +1,53 @@
/*
* isadetection.h
*
* This header declares the small interface used to detect instruction-set
* capabilities relevant to CRoaring's optimized kernels on x64 platforms. It
* also defines compile-time feature macros that indicate whether the compiler
* toolchain is capable of building AVX-512 code paths.
*
* The resulting flags are used to decide whether accelerated implementations,
* such as AVX2 or AVX-512 variants, can be selected safely at runtime.
*/
#ifndef ROARING_ISADETECTION_H
#define ROARING_ISADETECTION_H
#if defined(__x86_64__) || defined(_M_AMD64) // x64
#ifndef CROARING_COMPILER_SUPPORTS_AVX512
#ifdef __has_include
// We want to make sure that the AVX-512 functions are only built on compilers
// fully supporting AVX-512.
#if __has_include(<avx512vbmi2intrin.h>)
#define CROARING_COMPILER_SUPPORTS_AVX512 1
#endif // #if __has_include(<avx512vbmi2intrin.h>)
#endif // #ifdef __has_include
// Visual Studio 2019 and up support AVX-512
#ifdef _MSC_VER
#if _MSC_VER >= 1920
#define CROARING_COMPILER_SUPPORTS_AVX512 1
#endif // #if _MSC_VER >= 1920
#endif // #ifdef _MSC_VER
#ifndef CROARING_COMPILER_SUPPORTS_AVX512
#define CROARING_COMPILER_SUPPORTS_AVX512 0
#endif // #ifndef CROARING_COMPILER_SUPPORTS_AVX512
#endif // #ifndef CROARING_COMPILER_SUPPORTS_AVX512
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
enum {
ROARING_SUPPORTS_AVX2 = 1,
ROARING_SUPPORTS_AVX512 = 2,
};
int croaring_hardware_support(void);
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
#endif // x64
#endif // ROARING_ISADETECTION_H
+51
View File
@@ -0,0 +1,51 @@
/*
* memory.h
*
* This header defines CRoaring's memory-allocation abstraction layer. It
* declares the function pointer types and hook structure used to override the
* library's malloc/realloc/calloc/free and aligned allocation routines, along
* with the wrapper functions used throughout the codebase.
*
* This allows applications to integrate CRoaring with custom allocators,
* memory trackers, arenas, or platform-specific aligned allocation policies
* without changing the rest of the library code.
*/
#ifndef INCLUDE_ROARING_MEMORY_H_
#define INCLUDE_ROARING_MEMORY_H_
#include <stddef.h> // for size_t
#ifdef __cplusplus
extern "C" {
#endif
typedef void* (*roaring_malloc_p)(size_t);
typedef void* (*roaring_realloc_p)(void*, size_t);
typedef void* (*roaring_calloc_p)(size_t, size_t);
typedef void (*roaring_free_p)(void*);
typedef void* (*roaring_aligned_malloc_p)(size_t, size_t);
typedef void (*roaring_aligned_free_p)(void*);
typedef struct roaring_memory_s {
roaring_malloc_p malloc;
roaring_realloc_p realloc;
roaring_calloc_p calloc;
roaring_free_p free;
roaring_aligned_malloc_p aligned_malloc;
roaring_aligned_free_p aligned_free;
} roaring_memory_t;
void roaring_init_memory_hook(roaring_memory_t memory_hook);
void* roaring_malloc(size_t);
void* roaring_realloc(void*, size_t);
void* roaring_calloc(size_t, size_t);
void roaring_free(void*);
void* roaring_aligned_malloc(size_t, size_t);
void roaring_aligned_free(void*);
#ifdef __cplusplus
}
#endif
#endif // INCLUDE_ROARING_MEMORY_H_
+293
View File
@@ -0,0 +1,293 @@
/*
* configreport.h
*
* This header provides lightweight helpers to report basic build and runtime
* configuration information, such as processor family, compiler identity,
* optimization status, endianness, and selected hardware characteristics.
*
* It is mainly intended for diagnostics, benchmarking output, and quick
* inspection of the environment in which CRoaring or code using CRoaring was
* compiled and is being executed.
*
* If this gets compiled into a different execution unit than the CRoaring
* library, the functions croaring_hardware_support() & ROARING_SUPPORTS_AVX512
* and croaring_hardware_support() & ROARING_SUPPORTS_AVX2 *may* trigger an
* additional call to dynamic_croaring_detect_supported_architectures().
*/
#ifndef CROARING_INCLUDE_MISC_CONFIGREPORT_H_
#define CROARING_INCLUDE_MISC_CONFIGREPORT_H_
#include <stddef.h> // for size_t
#include <stdint.h>
#include <stdio.h>
#include <roaring/portability.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace misc {
#endif /* __cplusplus */
#if CROARING_IS_X64
// useful for basic info (0)
static inline void native_cpuid(unsigned int *eax, unsigned int *ebx,
unsigned int *ecx, unsigned int *edx) {
#ifdef CROARING_INLINE_ASM
__asm volatile("cpuid"
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
: "0"(*eax), "2"(*ecx));
#else
(void)eax;
(void)ebx;
(void)ecx;
(void)edx;
#endif /* CROARING_INLINE_ASM */
}
// CPUID instruction takes no parameters as CPUID implicitly uses the EAX
// register.
// The EAX register should be loaded with a value specifying what information to
// return
static inline void cpuinfo(int code, int *eax, int *ebx, int *ecx, int *edx) {
#ifdef CROARING_INLINE_ASM
__asm__ volatile("cpuid;" // call cpuid instruction
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx),
"=d"(*edx) // output equal to "movl %%eax %1"
: "a"(code) // input equal to "movl %1, %%eax"
//:"%eax","%ebx","%ecx","%edx"// clobbered register
);
#else
(void)code;
(void)eax;
(void)ebx;
(void)ecx;
(void)edx;
#endif /* CROARING_INLINE_ASM */
}
static inline int computecacheline() {
int eax = 0, ebx = 0, ecx = 0, edx = 0;
cpuinfo((int)0x80000006, &eax, &ebx, &ecx, &edx);
return ecx & 0xFF;
}
// this is quite imperfect, but can be handy
static inline const char *guessprocessor() {
unsigned eax = 1, ebx = 0, ecx = 0, edx = 0;
native_cpuid(&eax, &ebx, &ecx, &edx);
const char *codename;
switch (eax >> 4) {
case 0x506E:
codename = "Skylake";
break;
case 0x406C:
codename = "CherryTrail";
break;
case 0x306D:
codename = "Broadwell";
break;
case 0x306C:
codename = "Haswell";
break;
case 0x306A:
codename = "IvyBridge";
break;
case 0x206A:
case 0x206D:
codename = "SandyBridge";
break;
case 0x2065:
case 0x206C:
case 0x206F:
codename = "Westmere";
break;
case 0x106E:
case 0x106A:
case 0x206E:
codename = "Nehalem";
break;
case 0x1067:
case 0x106D:
codename = "Penryn";
break;
case 0x006F:
case 0x1066:
codename = "Merom";
break;
case 0x0066:
codename = "Presler";
break;
case 0x0063:
case 0x0064:
codename = "Prescott";
break;
case 0x006D:
codename = "Dothan";
break;
case 0x0366:
codename = "Cedarview";
break;
case 0x0266:
codename = "Lincroft";
break;
case 0x016C:
codename = "Pineview";
break;
case 0x706e:
case 0x606a:
codename = "Icelake";
break;
case 0x706a:
case 0x506c:
codename = "Goldmont";
break;
case 0x806c:
case 0x806d:
codename = "TigerLake";
break;
case 0x806e:
case 0x906e:
codename = "Kabylake";
break;
case 0xa065:
case 0xa066:
codename = "Cometlake";
break;
case 0xa067:
codename = "Rocketlake";
break;
case 0x9067:
case 0x906a:
codename = "Alderlake";
break;
case 0xb067:
codename = "Raptorlake";
break;
case 0x30f1:
case 0x60f0:
case 0x70f1:
case 0x60f8:
case 0x90f0:
codename = "Zen2";
break;
case 0x20f10:
case 0x50f00:
codename = "Zen3";
break;
case 0x40f40:
codename = "Zen3+";
break;
case 0x60f10:
codename = "Zen4";
break;
default:
codename = "unknown";
break;
}
return codename;
}
static inline void tellmeall() {
#if CROARING_IS_BIG_ENDIAN
printf("# big-endian system detected\n");
#endif /* CROARING_IS_BIG_ENDIAN */
printf("# x64 processor: %s\n", guessprocessor());
#if defined(__clang__)
printf("# compiler: clang %d.%d.%d\n", __clang_major__, __clang_minor__,
__clang_patchlevel__);
#elif defined(__GNUC__)
printf(" compiler: gcc %d.%d.%d\n", __GNUC__, __GNUC_MINOR__,
__GNUC_PATCHLEVEL__);
#elif defined(_MSC_VER)
printf("# compiler: MSVC %d\n", _MSC_VER);
#elif defined(__VERSION__)
printf("# compiler: %s\n", __VERSION__);
#else
printf("# compiler: unknown\n");
#endif /* compiler detection */
#if defined(__OPTIMIZE__)
printf("# optimization: on\n");
#elif defined(__OPTIMIZE_SIZE__)
printf("# size optimization: on\n");
#elif defined(_MSC_VER) && !defined(_DEBUG)
printf("# optimization: on\n");
#else
printf("# optimization: off\n");
#endif /* optimization detection */
if ((sizeof(int) != 4) || (sizeof(long) != 8)) {
printf("number of bytes: int = %lu long = %lu \n",
(long unsigned int)sizeof(size_t),
(long unsigned int)sizeof(int));
}
#if __BIG_ENDIAN__
printf("# you have a big endian machine\n");
#endif /* __BIG_ENDIAN__ */
#if __CHAR_BIT__
if (__CHAR_BIT__ != 8)
printf("# on your machine, chars don't have 8bits???\n");
#endif /* __CHAR_BIT__ */
if (computecacheline() != 64)
printf("# cache line: %d bytes\n", computecacheline());
}
#else
static inline void tellmeall() {
#if CROARING_IS_BIG_ENDIAN
printf("# big-endian system detected\n");
#endif /* CROARING_IS_BIG_ENDIAN */
#if defined(__x86_64__) || defined(_M_X64) || defined(__amd64__)
printf("# x64 processor detected\n");
#elif defined(__arm__) || defined(__aarch64__) || defined(__arm64__) || \
defined(_M_ARM) || defined(_M_ARM64)
printf("# ARM processor detected\n");
#else
printf("# Non-X64, non-ARM processor\n");
#endif /* architecture detection */
#if defined(__clang__)
printf("# compiler: clang %d.%d.%d\n", __clang_major__, __clang_minor__,
__clang_patchlevel__);
#elif defined(__GNUC__)
printf(" compiler: gcc %d.%d.%d\n", __GNUC__, __GNUC_MINOR__,
__GNUC_PATCHLEVEL__);
#elif defined(_MSC_VER)
printf("# compiler: MSVC %d\n", _MSC_VER);
#elif defined(__VERSION__)
printf("# compiler: %s\n", __VERSION__);
#else
printf("# compiler: unknown\n");
#endif /* compiler detection */
#if defined(__OPTIMIZE__)
printf("# optimization: on\n");
#elif defined(__OPTIMIZE_SIZE__)
printf("# size optimization: on\n");
#elif defined(_MSC_VER) && !defined(_DEBUG)
printf("# optimization: on\n");
#else
printf("# optimization: off\n");
#endif /* optimization detection */
if ((sizeof(int) != 4) || (sizeof(long) != 8)) {
printf("number of bytes: int = %lu long = %lu \n",
(long unsigned int)sizeof(size_t),
(long unsigned int)sizeof(int));
}
#if __BIG_ENDIAN__
printf("# you have a big endian machine\n");
#endif /* __BIG_ENDIAN__ */
#if __CHAR_BIT__
if (__CHAR_BIT__ != 8)
printf("# on your machine, chars don't have 8bits???\n");
#endif /* __CHAR_BIT__ */
}
#endif /* CROARING_IS_X64 */
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace misc {
#endif /* __cplusplus */
#endif /* CROARING_INCLUDE_MISC_CONFIGREPORT_H_ */
+756
View File
@@ -0,0 +1,756 @@
/*
* portability.h
*
* This header centralizes compiler-, platform-, and architecture-specific
* portability definitions used throughout CRoaring. It provides feature
* detection, calling-convention and attribute macros, intrinsic and inline
* assembly enablement, endianness helpers, alignment annotations, atomic
* reference-count support, and other low-level compatibility glue.
*
* The goal is to keep these conditional definitions in one place so the rest
* of the codebase can rely on a more uniform interface across GCC, Clang,
* MSVC, x86/x64, ARM/NEON, and other supported environments.
*/
/**
* All macros should be prefixed with either CROARING or ROARING.
* The library uses both ROARING_...
* as well as CROARING_ as prefixes. The ROARING_ prefix is for
* macros that are provided by the build system or that are closely
* related to the format. The header macros may also use ROARING_.
* The CROARING_ prefix is for internal macros that a user is unlikely
* to ever interact with.
*/
#ifndef CROARING_INCLUDE_PORTABILITY_H_
#define CROARING_INCLUDE_PORTABILITY_H_
// Local Redis patch: __has_include polyfill for compilers without the
// builtin, used by the endian and atomics detection below.
#ifndef __has_include
#define __has_include(x) 0
#endif
// Users who need _GNU_SOURCE should define it?
// #ifndef _GNU_SOURCE
// #define _GNU_SOURCE 1
// #endif // _GNU_SOURCE
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS 1
#endif // __STDC_FORMAT_MACROS
#ifdef _MSC_VER
#define CROARING_VISUAL_STUDIO 1
/**
* We want to differentiate carefully between
* clang under visual studio and regular visual
* studio.
*/
#ifdef __clang__
// clang under visual studio
#define CROARING_CLANG_VISUAL_STUDIO 1
#else
// just regular visual studio (best guess)
#define CROARING_REGULAR_VISUAL_STUDIO 1
#endif // __clang__
#endif // _MSC_VER
#ifndef CROARING_VISUAL_STUDIO
#define CROARING_VISUAL_STUDIO 0
#endif
#ifndef CROARING_CLANG_VISUAL_STUDIO
#define CROARING_CLANG_VISUAL_STUDIO 0
#endif
#ifndef CROARING_REGULAR_VISUAL_STUDIO
#define CROARING_REGULAR_VISUAL_STUDIO 0
#endif
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h> // will provide posix_memalign with _POSIX_C_SOURCE as defined above
#ifdef __GLIBC__
#include <malloc.h> // this should never be needed but there are some reports that it is needed.
#endif
// alignas/alignof are keywords in C++ and in C23+, where <stdalign.h> is
// deprecated. Only include it for C11..C17.
#if !defined(__cplusplus) && \
(!defined(__STDC_VERSION__) || __STDC_VERSION__ < 202311L)
#include <stdalign.h>
#endif
#ifdef __cplusplus
extern "C" { // portability definitions are in global scope, not a namespace
#endif
#if defined(__SIZEOF_LONG_LONG__) && __SIZEOF_LONG_LONG__ != 8
#error This code assumes 64-bit long longs (by use of the GCC intrinsics). Your system is not currently supported.
#endif
#if CROARING_REGULAR_VISUAL_STUDIO
#ifndef __restrict__
#define __restrict__ __restrict
#endif // __restrict__
#endif // CROARING_REGULAR_VISUAL_STUDIO
#if defined(__x86_64__) || defined(_M_X64)
// we have an x64 processor
#define CROARING_IS_X64 1
#if defined(_MSC_VER) && (_MSC_VER < 1910)
// Old visual studio systems won't support AVX2 well.
#undef CROARING_IS_X64
#endif
#if defined(__clang_major__) && (__clang_major__ <= 8) && !defined(__AVX2__)
// Older versions of clang have a bug affecting us
// https://stackoverflow.com/questions/57228537/how-does-one-use-pragma-clang-attribute-push-with-c-namespaces
#undef CROARING_IS_X64
#endif
#ifdef ROARING_DISABLE_X64
#undef CROARING_IS_X64
#endif
// we include the intrinsic header
#if !CROARING_REGULAR_VISUAL_STUDIO
/* Non-Microsoft C/C++-compatible compiler */
#include <x86intrin.h> // on some recent GCC, this will declare posix_memalign
#if CROARING_CLANG_VISUAL_STUDIO
/**
* You are not supposed, normally, to include these
* headers directly. Instead you should either include intrin.h
* or x86intrin.h. However, when compiling with clang
* under Windows (i.e., when _MSC_VER is set), these headers
* only get included *if* the corresponding features are detected
* from macros:
* e.g., if __AVX2__ is set... in turn, we normally set these
* macros by compiling against the corresponding architecture
* (e.g., arch:AVX2, -mavx2, etc.) which compiles the whole
* software with these advanced instructions. These headers would
* normally guard against such usage, but we carefully included
* <x86intrin.h> (or <intrin.h>) before, so the headers
* are fooled.
*/
// To avoid reordering imports:
// clang-format off
#include <bmiintrin.h> // for _blsr_u64
#include <lzcntintrin.h> // for __lzcnt64
#include <immintrin.h> // for most things (AVX2, AVX512, _popcnt64)
#include <smmintrin.h>
#include <tmmintrin.h>
#include <avxintrin.h>
#include <avx2intrin.h>
#include <wmmintrin.h>
#if _MSC_VER >= 1920
// Important: we need the AVX-512 headers:
#include <avx512fintrin.h>
#include <avx512dqintrin.h>
#include <avx512cdintrin.h>
#include <avx512bwintrin.h>
#include <avx512vlintrin.h>
#include <avx512vbmiintrin.h>
#include <avx512vbmi2intrin.h>
#include <avx512vpopcntdqintrin.h>
// clang-format on
#endif // _MSC_VER >= 1920
// unfortunately, we may not get _blsr_u64, but, thankfully, clang
// has it as a macro.
#ifndef _blsr_u64
// we roll our own
#define _blsr_u64(n) ((n - 1) & n)
#endif // _blsr_u64
#endif // SIMDJSON_CLANG_VISUAL_STUDIO
#endif // CROARING_REGULAR_VISUAL_STUDIO
#endif // defined(__x86_64__) || defined(_M_X64)
#if !defined(CROARING_USENEON) && !defined(DISABLENEON) && defined(__ARM_NEON)
#define CROARING_USENEON
#endif
#if defined(CROARING_USENEON)
#include <arm_neon.h>
#endif
#if defined(__e2k__)
// we have an e2k (Elbrus-2000) processor
#define CROARING_IS_E2K 1
#endif
#if !CROARING_REGULAR_VISUAL_STUDIO && !defined(CROARING_IS_E2K) && !__FILC__
/* Non-Microsoft C/C++-compatible compiler, assumes that it supports inline
* assembly */
#define CROARING_INLINE_ASM 1
#endif // _MSC_VER
#if CROARING_REGULAR_VISUAL_STUDIO
/* Microsoft C/C++-compatible compiler */
#include <intrin.h>
#ifndef __clang__ // if one compiles with MSVC *with* clang, then these
// intrinsics are defined!!!
#define CROARING_INTRINSICS 1
// sadly there is no way to check whether we are missing these intrinsics
// specifically.
/* wrappers for Visual Studio built-ins that look like gcc built-ins
* __builtin_ctzll */
/** result might be undefined when input_num is zero */
inline int roaring_trailing_zeroes(unsigned long long input_num) {
unsigned long index;
#ifdef _WIN64 // highly recommended!!!
_BitScanForward64(&index, input_num);
#else // if we must support 32-bit Windows
if ((uint32_t)input_num != 0) {
_BitScanForward(&index, (uint32_t)input_num);
} else {
_BitScanForward(&index, (uint32_t)(input_num >> 32));
index += 32;
}
#endif // _WIN64
return index;
}
/* wrappers for Visual Studio built-ins that look like gcc built-ins
* __builtin_clzll */
/** result might be undefined when input_num is zero */
inline int roaring_leading_zeroes(unsigned long long input_num) {
unsigned long index;
#ifdef _WIN64 // highly recommended!!!
_BitScanReverse64(&index, input_num);
#else // if we must support 32-bit Windows
if (input_num > 0xFFFFFFFF) {
_BitScanReverse(&index, (uint32_t)(input_num >> 32));
index += 32;
} else {
_BitScanReverse(&index, (uint32_t)(input_num));
}
#endif // _WIN64
return 63 - index;
}
/* Use #define so this is effective even under /Ob0 (no inline) */
#define roaring_unreachable __assume(0)
#endif // __clang__
#endif // CROARING_REGULAR_VISUAL_STUDIO
#ifndef CROARING_INTRINSICS
#define CROARING_INTRINSICS 1
#define roaring_unreachable __builtin_unreachable()
/** result might be undefined when input_num is zero */
inline int roaring_trailing_zeroes(unsigned long long input_num) {
return __builtin_ctzll(input_num);
}
/** result might be undefined when input_num is zero */
inline int roaring_leading_zeroes(unsigned long long input_num) {
return __builtin_clzll(input_num);
}
#endif
#if CROARING_REGULAR_VISUAL_STUDIO
#define ALIGNED(x) __declspec(align(x))
#elif defined(__GNUC__) || defined(__clang__)
#define ALIGNED(x) __attribute__((aligned(x)))
#else
#warning "Warning. Unrecognized compiler."
#define ALIGNED(x)
#endif
#if defined(__GNUC__) || defined(__clang__)
#define CROARING_WARN_UNUSED __attribute__((warn_unused_result))
#else
#define CROARING_WARN_UNUSED
#endif
#define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100)
#ifdef CROARING_USENEON
// we can always compute the popcount fast.
#elif (defined(_M_ARM) || defined(_M_ARM64)) && \
((defined(_WIN64) || defined(_WIN32)) && \
defined(CROARING_REGULAR_VISUAL_STUDIO) && \
CROARING_REGULAR_VISUAL_STUDIO)
// we will need this function:
static inline int roaring_hamming_backup(uint64_t x) {
uint64_t c1 = UINT64_C(0x5555555555555555);
uint64_t c2 = UINT64_C(0x3333333333333333);
uint64_t c4 = UINT64_C(0x0F0F0F0F0F0F0F0F);
x -= (x >> 1) & c1;
x = ((x >> 2) & c2) + (x & c2);
x = (x + (x >> 4)) & c4;
x *= UINT64_C(0x0101010101010101);
return x >> 56;
}
#endif
static inline int roaring_hamming(uint64_t x) {
#if defined(_WIN64) && defined(CROARING_REGULAR_VISUAL_STUDIO) && \
CROARING_REGULAR_VISUAL_STUDIO
#ifdef CROARING_USENEON
return vaddv_u8(vcnt_u8(vcreate_u8(input_num)));
#elif defined(_M_ARM64)
return roaring_hamming_backup(x);
// (int) _CountOneBits64(x); is unavailable
#else // _M_ARM64
return (int)__popcnt64(x);
#endif // _M_ARM64
#elif defined(_WIN32) && defined(CROARING_REGULAR_VISUAL_STUDIO) && \
CROARING_REGULAR_VISUAL_STUDIO
#ifdef _M_ARM
return roaring_hamming_backup(x);
// _CountOneBits is unavailable
#else // _M_ARM
return (int)__popcnt((unsigned int)x) +
(int)__popcnt((unsigned int)(x >> 32));
#endif // _M_ARM
#else
return __builtin_popcountll(x);
#endif
}
#ifndef UINT64_C
#define UINT64_C(c) (c##ULL)
#endif // UINT64_C
#ifndef UINT32_C
#define UINT32_C(c) (c##UL)
#endif // UINT32_C
#ifdef __cplusplus
} // extern "C" {
#endif // __cplusplus
// this is almost standard?
#undef STRINGIFY_IMPLEMENTATION_
#undef STRINGIFY
#define STRINGIFY_IMPLEMENTATION_(a) #a
#define STRINGIFY(a) STRINGIFY_IMPLEMENTATION_(a)
// Our fast kernels require 64-bit systems.
//
// On 32-bit x86, we lack 64-bit popcnt, lzcnt, blsr instructions.
// Furthermore, the number of SIMD registers is reduced.
//
// On 32-bit ARM, we would have smaller registers.
//
// The library should still have the fallback kernel. It is
// slower, but it should run everywhere.
//
// Enable valid runtime implementations, and select
// CROARING_BUILTIN_IMPLEMENTATION
//
// We are going to use runtime dispatch.
#if CROARING_IS_X64
#ifdef __clang__
// clang does not have GCC push pop
// warning: clang attribute push can't be used within a namespace in clang up
// til 8.0 so CROARING_TARGET_REGION and CROARING_UNTARGET_REGION must be
// *outside* of a namespace.
#define CROARING_TARGET_REGION(T) \
_Pragma(STRINGIFY(clang attribute push(__attribute__((target(T))), \
apply_to = function)))
#define CROARING_UNTARGET_REGION _Pragma("clang attribute pop")
#elif defined(__GNUC__)
// GCC is easier
#define CROARING_TARGET_REGION(T) \
_Pragma("GCC push_options") _Pragma(STRINGIFY(GCC target(T)))
#define CROARING_UNTARGET_REGION _Pragma("GCC pop_options")
#endif // clang then gcc
#endif // CROARING_IS_X64
// Default target region macros don't do anything.
#ifndef CROARING_TARGET_REGION
#define CROARING_TARGET_REGION(T)
#define CROARING_UNTARGET_REGION
#endif
#define CROARING_TARGET_AVX2 \
CROARING_TARGET_REGION("avx2,bmi,pclmul,lzcnt,popcnt")
#define CROARING_TARGET_AVX512 \
CROARING_TARGET_REGION( \
"avx2,bmi,bmi2,pclmul,lzcnt,popcnt,avx512f,avx512dq,avx512bw," \
"avx512vbmi2,avx512bitalg,avx512vpopcntdq")
#define CROARING_UNTARGET_AVX2 CROARING_UNTARGET_REGION
#define CROARING_UNTARGET_AVX512 CROARING_UNTARGET_REGION
#ifdef __AVX2__
// No need for runtime dispatching.
// It is unnecessary and harmful to old clang to tag regions.
#undef CROARING_TARGET_AVX2
#define CROARING_TARGET_AVX2
#undef CROARING_UNTARGET_AVX2
#define CROARING_UNTARGET_AVX2
#endif
#if defined(__AVX512F__) && defined(__AVX512DQ__) && defined(__AVX512BW__) && \
defined(__AVX512VBMI2__) && defined(__AVX512BITALG__) && \
defined(__AVX512VPOPCNTDQ__)
// No need for runtime dispatching.
// It is unnecessary and harmful to old clang to tag regions.
#undef CROARING_TARGET_AVX512
#define CROARING_TARGET_AVX512
#undef CROARING_UNTARGET_AVX512
#define CROARING_UNTARGET_AVX512
#endif
// Allow unaligned memory access
// Local Redis patch: gated on __has_attribute(no_sanitize) so the attribute
// is only used by compilers that support it (clang, GCC >= 8). GCC 5-7 would
// otherwise warn that it is ignored, breaking -Werror builds.
#if defined(__has_attribute)
#if __has_attribute(no_sanitize)
#define CROARING_ALLOW_UNALIGNED __attribute__((no_sanitize("alignment")))
#endif
#endif
#if !defined(CROARING_ALLOW_UNALIGNED)
#define CROARING_ALLOW_UNALIGNED
#endif
// Local Redis patch: reworked endian detection. Replaces upstream's
// malformed "#ifndef !defined(__BYTE_ORDER__) || ..." guard, additionally
// recognizes the non-GNU __BYTE_ORDER, _BYTE_ORDER and BYTE_ORDER macro
// families, and errors at build time when the target byte order cannot be
// determined instead of silently assuming little endian.
#ifndef CROARING_IS_BIG_ENDIAN
#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \
defined(__ORDER_LITTLE_ENDIAN__)
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define CROARING_IS_BIG_ENDIAN 1
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define CROARING_IS_BIG_ENDIAN 0
#else
#error "Unsupported __BYTE_ORDER__ value"
#endif
#elif defined(_WIN32)
#define CROARING_IS_BIG_ENDIAN 0
#else
#if defined(__APPLE__) || \
defined(__FreeBSD__) // defined __BYTE_ORDER__ && defined
// __ORDER_BIG_ENDIAN__
#include <machine/endian.h>
#elif defined(sun) || \
defined(__sun) // defined(__APPLE__) || defined(__FreeBSD__)
#include <sys/byteorder.h>
#else // defined(__APPLE__) || defined(__FreeBSD__)
#if __has_include(<endian.h>) || defined(__linux__)
#include <endian.h>
#endif // __has_include(<endian.h>) || defined(__linux__)
#endif // defined(__APPLE__) || defined(__FreeBSD__)
#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && \
defined(__ORDER_LITTLE_ENDIAN__)
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define CROARING_IS_BIG_ENDIAN 1
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define CROARING_IS_BIG_ENDIAN 0
#else
#error "Unsupported __BYTE_ORDER__ value"
#endif
#elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \
defined(__LITTLE_ENDIAN)
#if __BYTE_ORDER == __BIG_ENDIAN
#define CROARING_IS_BIG_ENDIAN 1
#elif __BYTE_ORDER == __LITTLE_ENDIAN
#define CROARING_IS_BIG_ENDIAN 0
#else
#error "Unsupported __BYTE_ORDER value"
#endif
#elif defined(_BYTE_ORDER) && defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN)
#if _BYTE_ORDER == _BIG_ENDIAN
#define CROARING_IS_BIG_ENDIAN 1
#elif _BYTE_ORDER == _LITTLE_ENDIAN
#define CROARING_IS_BIG_ENDIAN 0
#else
#error "Unsupported _BYTE_ORDER value"
#endif
#elif defined(BYTE_ORDER) && defined(BIG_ENDIAN) && defined(LITTLE_ENDIAN)
#if BYTE_ORDER == BIG_ENDIAN
#define CROARING_IS_BIG_ENDIAN 1
#elif BYTE_ORDER == LITTLE_ENDIAN
#define CROARING_IS_BIG_ENDIAN 0
#else
#error "Unsupported BYTE_ORDER value"
#endif
#elif defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
#define CROARING_IS_BIG_ENDIAN 1
#elif defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
#define CROARING_IS_BIG_ENDIAN 0
#elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN)
#define CROARING_IS_BIG_ENDIAN 1
#elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN)
#define CROARING_IS_BIG_ENDIAN 0
#else
#error "Unable to determine target byte order for CRoaring; define CROARING_IS_BIG_ENDIAN to 0 or 1."
#endif
#endif
#endif
#if CROARING_IS_BIG_ENDIAN != 0 && CROARING_IS_BIG_ENDIAN != 1
#error "CROARING_IS_BIG_ENDIAN must be 0 or 1."
#endif
// Host <-> big endian conversion.
#if CROARING_IS_BIG_ENDIAN
#define croaring_htobe64(x) (x)
#elif defined(_WIN32) || defined(_WIN64) // CROARING_IS_BIG_ENDIAN
#include <stdlib.h>
#define croaring_htobe64(x) _byteswap_uint64(x)
#elif defined(__APPLE__) // CROARING_IS_BIG_ENDIAN
#include <libkern/OSByteOrder.h>
#define croaring_htobe64(x) OSSwapInt64(x)
#elif defined(__linux__) // CROARING_IS_BIG_ENDIAN
#include <byteswap.h>
#define croaring_htobe64(x) bswap_64(x)
#elif defined(__FreeBSD__) // CROARING_IS_BIG_ENDIAN
#include <sys/endian.h>
#define croaring_htobe64(x) bswap64(x)
#else // CROARING_IS_BIG_ENDIAN
// Gets compiled to bswap or equivalent on most compilers.
#define croaring_htobe64(x) \
(((x & 0x00000000000000FFULL) << 56) | \
((x & 0x000000000000FF00ULL) << 40) | \
((x & 0x0000000000FF0000ULL) << 24) | \
((x & 0x00000000FF000000ULL) << 8) | ((x & 0x000000FF00000000ULL) >> 8) | \
((x & 0x0000FF0000000000ULL) >> 24) | \
((x & 0x00FF000000000000ULL) >> 40) | \
((x & 0xFF00000000000000ULL) >> 56))
#endif // CROARING_IS_BIG_ENDIAN
#define croaring_be64toh(x) croaring_htobe64(x)
// End of host <-> big endian conversion.
// Host <-> little-endian conversion helpers.
//
// The CRoaring "portable" serialization format (and the regular
// roaring_bitmap_serialize / Roaring64Map::write formats which build on it)
// is defined to be little-endian on the wire. Code that reads or writes
// multi-byte integers to such buffers must convert between host and
// little-endian byte order. On little-endian hosts these are no-ops; on
// big-endian hosts they swap bytes.
//
// The "frozen" format is intentionally non-portable and uses native byte
// order; it must not use these helpers.
#if CROARING_IS_BIG_ENDIAN
static inline uint16_t croaring_bswap16(uint16_t x) {
return (uint16_t)((x << 8) | (x >> 8));
}
static inline uint32_t croaring_bswap32(uint32_t x) {
return ((x & 0x000000FFU) << 24) | ((x & 0x0000FF00U) << 8) |
((x & 0x00FF0000U) >> 8) | ((x & 0xFF000000U) >> 24);
}
static inline uint64_t croaring_bswap64(uint64_t x) {
return ((x & 0x00000000000000FFULL) << 56) |
((x & 0x000000000000FF00ULL) << 40) |
((x & 0x0000000000FF0000ULL) << 24) |
((x & 0x00000000FF000000ULL) << 8) |
((x & 0x000000FF00000000ULL) >> 8) |
((x & 0x0000FF0000000000ULL) >> 24) |
((x & 0x00FF000000000000ULL) >> 40) |
((x & 0xFF00000000000000ULL) >> 56);
}
#define croaring_htole16(x) croaring_bswap16(x)
#define croaring_htole32(x) croaring_bswap32(x)
#define croaring_htole64(x) croaring_bswap64(x)
#else // CROARING_IS_BIG_ENDIAN
#define croaring_htole16(x) (x)
#define croaring_htole32(x) (x)
#define croaring_htole64(x) (x)
#endif // CROARING_IS_BIG_ENDIAN
#define croaring_letoh16(x) croaring_htole16(x)
#define croaring_letoh32(x) croaring_htole32(x)
#define croaring_letoh64(x) croaring_htole64(x)
// Defines for the possible CROARING atomic implementations
#define CROARING_ATOMIC_IMPL_NONE 1
#define CROARING_ATOMIC_IMPL_CPP 2
#define CROARING_ATOMIC_IMPL_C 3
#define CROARING_ATOMIC_IMPL_C_WINDOWS 4
// Local Redis patch: CROARING_ATOMIC_IMPL_GCC is a __sync-builtin fallback
// for toolchains without C11/C++11 atomics (selected and implemented below).
#define CROARING_ATOMIC_IMPL_GCC 5
// If the use has forced a specific implementation, use that, otherwise,
// figure out the best implementation we can use.
#if !defined(CROARING_ATOMIC_IMPL)
#if defined(__cplusplus) && __cplusplus >= 201103L
#if __has_include(<atomic>)
#define CROARING_ATOMIC_IMPL CROARING_ATOMIC_IMPL_CPP
#endif //__has_include(<atomic>)
#elif __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)
#if __has_include(<stdatomic.h>)
#define CROARING_ATOMIC_IMPL CROARING_ATOMIC_IMPL_C
#endif // __has_include(<stdatomic.h>)
#endif
#endif // !defined(CROARING_ATOMIC_IMPL)
#if !defined(CROARING_ATOMIC_IMPL)
#if CROARING_REGULAR_VISUAL_STUDIO
// https://www.technetworkhub.com/c11-atomics-in-visual-studio-2022-version-17/
#define CROARING_ATOMIC_IMPL CROARING_ATOMIC_IMPL_C_WINDOWS
#elif defined(__GNUC__) || defined(__clang__)
// Local Redis patch: fall back to the __sync builtins.
#define CROARING_ATOMIC_IMPL CROARING_ATOMIC_IMPL_GCC
#endif
#endif // !defined(CROARING_ATOMIC_IMPL)
#if CROARING_ATOMIC_IMPL == CROARING_ATOMIC_IMPL_C
#include <stdatomic.h>
typedef _Atomic(uint32_t) croaring_refcount_t;
static inline void croaring_refcount_inc(croaring_refcount_t *val) {
// Increasing the reference counter can always be done with
// memory_order_relaxed: New references to an object can only be formed from
// an existing reference, and passing an existing reference from one thread
// to another must already provide any required synchronization.
atomic_fetch_add_explicit(val, 1, memory_order_relaxed);
}
static inline bool croaring_refcount_dec(croaring_refcount_t *val) {
// It is important to enforce any possible access to the object in one
// thread (through an existing reference) to happen before deleting the
// object in a different thread. This is achieved by a "release" operation
// after dropping a reference (any access to the object through this
// reference must obviously happened before), and an "acquire" operation
// before deleting the object.
bool is_zero = atomic_fetch_sub_explicit(val, 1, memory_order_release) == 1;
if (is_zero) {
atomic_thread_fence(memory_order_acquire);
}
return is_zero;
}
static inline uint32_t croaring_refcount_get(const croaring_refcount_t *val) {
return atomic_load_explicit(val, memory_order_relaxed);
}
#elif CROARING_ATOMIC_IMPL == CROARING_ATOMIC_IMPL_CPP
#include <atomic>
typedef std::atomic<uint32_t> croaring_refcount_t;
static inline void croaring_refcount_inc(croaring_refcount_t *val) {
val->fetch_add(1, std::memory_order_relaxed);
}
static inline bool croaring_refcount_dec(croaring_refcount_t *val) {
// See above comments on the c11 atomic implementation for memory ordering
bool is_zero = val->fetch_sub(1, std::memory_order_release) == 1;
if (is_zero) {
std::atomic_thread_fence(std::memory_order_acquire);
}
return is_zero;
}
static inline uint32_t croaring_refcount_get(const croaring_refcount_t *val) {
return val->load(std::memory_order_relaxed);
}
#elif CROARING_ATOMIC_IMPL == CROARING_ATOMIC_IMPL_C_WINDOWS
#include <intrin.h>
#pragma intrinsic(_InterlockedIncrement)
#pragma intrinsic(_InterlockedDecrement)
// _InterlockedIncrement and _InterlockedDecrement take a (signed) long, and
// overflow is defined to wrap, so we can pretend it is a uint32_t for our case
typedef volatile long croaring_refcount_t;
static inline void croaring_refcount_inc(croaring_refcount_t *val) {
_InterlockedIncrement(val);
}
static inline bool croaring_refcount_dec(croaring_refcount_t *val) {
return _InterlockedDecrement(val) == 0;
}
static inline uint32_t croaring_refcount_get(const croaring_refcount_t *val) {
// Per
// https://learn.microsoft.com/en-us/windows/win32/sync/interlocked-variable-access
// > Simple reads and writes to properly-aligned 32-bit variables are atomic
// > operations. In other words, you will not end up with only one portion
// > of the variable updated; all bits are updated in an atomic fashion.
return *val;
}
#elif CROARING_ATOMIC_IMPL == CROARING_ATOMIC_IMPL_GCC
// Local Redis patch: __sync-builtin refcount implementation.
typedef uint32_t croaring_refcount_t;
static inline void croaring_refcount_inc(croaring_refcount_t *val) {
__sync_add_and_fetch(val, 1);
}
static inline bool croaring_refcount_dec(croaring_refcount_t *val) {
return __sync_sub_and_fetch(val, 1) == 0;
}
static inline uint32_t croaring_refcount_get(const croaring_refcount_t *val) {
return __sync_fetch_and_add((croaring_refcount_t *)val, 0);
}
#elif CROARING_ATOMIC_IMPL == CROARING_ATOMIC_IMPL_NONE
#include <assert.h>
typedef uint32_t croaring_refcount_t;
static inline void croaring_refcount_inc(croaring_refcount_t *val) {
*val += 1;
}
// Local Redis patch: return whether the dereferenced counter reached zero.
// Drop once an upstream release contains the same fix.
static inline bool croaring_refcount_dec(croaring_refcount_t *val) {
assert(*val > 0);
*val -= 1;
return *val == 0;
}
static inline uint32_t croaring_refcount_get(const croaring_refcount_t *val) {
return *val;
}
#else
#error "Unknown atomic implementation"
#endif
#if defined(__GNUC__) || defined(__clang__)
#define CROARING_DEPRECATED __attribute__((deprecated))
#elif defined(_MSC_VER)
#define CROARING_DEPRECATED __declspec(deprecated)
#else
#define CROARING_DEPRECATED
#endif // defined(__GNUC__) || defined(__clang__)
// We want to initialize structs to zero portably (C and C++), without
// warnings. We can do mystruct s = CROARING_ZERO_INITIALIZER;
#if __cplusplus
#define CROARING_ZERO_INITIALIZER \
{}
#else
#define CROARING_ZERO_INITIALIZER \
{ 0 }
#endif
#if defined(__cplusplus)
#define CROARING_STATIC_ASSERT(x, y) static_assert(x, y)
#else
#define CROARING_STATIC_ASSERT(x, y) _Static_assert(x, y)
#endif
// We need portability.h to be included first,
// but we also always want isadetection.h to be
// included (right after).
// See https://github.com/RoaringBitmap/CRoaring/issues/394
// There is no scenario where we want portability.h to
// be included, but not isadetection.h: the latter is a
// strict requirement.
#include <roaring/isadetection.h> // include it last!
#endif /* INCLUDE_PORTABILITY_H_ */
File diff suppressed because it is too large. Load diff
+897
View File
@@ -0,0 +1,897 @@
/*
* roaring64.h
*
* This file declares the 64-bit Roaring bitmap API. A roaring64 bitmap stores
* sets of 64-bit unsigned integers by partitioning the value space by high
* bits and using Roaring containers for the lower bits inside each partition.
* This keeps the structure compact while preserving fast membership tests,
* insertions, iteration, and set operations over large sparse integer sets.
*/
#ifndef ROARING64_H
#define ROARING64_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <roaring/memory.h>
#include <roaring/portability.h>
#include <roaring/roaring.h>
#include <roaring/roaring_types.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace api {
#endif
/** An opaque 64-bit Roaring bitmap. Create one with `roaring64_bitmap_create()`
* and release it with `roaring64_bitmap_free()`. */
typedef struct roaring64_bitmap_s roaring64_bitmap_t;
/** Internal leaf type, exposed only for use inside `roaring64_bulk_context_t`.
* Callers should treat it as opaque. */
typedef uint64_t roaring64_leaf_t;
/** An opaque iterator over a 64-bit bitmap. See `roaring64_iterator_create()`.
*/
typedef struct roaring64_iterator_s roaring64_iterator_t;
/**
* A bit of context usable with `roaring64_bitmap_*_bulk()` functions.
*
* Should be initialized with `{0}` (or `memset()` to all zeros).
* Callers should treat it as an opaque type.
*
* A context may only be used with a single bitmap (unless re-initialized to
* zero), and any modification to a bitmap (other than modifications performed
* with `_bulk()` functions with the context passed) will invalidate any
* contexts associated with that bitmap.
*/
typedef struct roaring64_bulk_context_s {
uint8_t high_bytes[6];
roaring64_leaf_t *leaf;
} roaring64_bulk_context_t;
/**
* Dynamically allocates a new bitmap (initially empty).
* Client is responsible for calling `roaring64_bitmap_free()`.
* The returned pointer may be NULL in case of errors.
*/
roaring64_bitmap_t *roaring64_bitmap_create(void);
void roaring64_bitmap_free(roaring64_bitmap_t *r);
/**
* Returns a copy of a bitmap.
* The returned pointer may be NULL in case of errors.
*/
roaring64_bitmap_t *roaring64_bitmap_copy(const roaring64_bitmap_t *r);
/**
* Copies a bitmap from src to dest. It is assumed that the pointer dest
* is to an already allocated bitmap. The content of the dest bitmap is
* freed/deleted.
*
* It might be preferable and simpler to call roaring64_bitmap_copy except
* that roaring64_bitmap_overwrite can save on memory allocations.
*
*/
void roaring64_bitmap_overwrite(roaring64_bitmap_t *dest,
const roaring64_bitmap_t *src);
/**
* Creates a new bitmap of a pointer to N 64-bit integers.
*/
roaring64_bitmap_t *roaring64_bitmap_of_ptr(size_t n_args,
const uint64_t *vals);
#ifdef __cplusplus
/**
* Creates a new bitmap which contains all values passed in as arguments.
*
* To create a bitmap from a variable number of arguments, use the
* `roaring64_bitmap_of_ptr` function instead.
*/
// Use an immediately invoked closure, capturing by reference
// (in case __VA_ARGS__ refers to context outside the closure)
// Include a 0 at the beginning of the array to make the array length > 0
// (zero sized arrays are not valid in standard c/c++)
#define roaring64_bitmap_from(...) \
[&]() { \
const uint64_t roaring64_bitmap_from_array[] = {0, __VA_ARGS__}; \
return roaring64_bitmap_of_ptr( \
(sizeof(roaring64_bitmap_from_array) / \
sizeof(roaring64_bitmap_from_array[0])) - \
1, \
&roaring64_bitmap_from_array[1]); \
}()
#else
/**
* Creates a new bitmap which contains all values passed in as arguments.
*
* To create a bitmap from a variable number of arguments, use the
* `roaring64_bitmap_of_ptr` function instead.
*/
// While __VA_ARGS__ occurs twice in expansion, one of the times is in a sizeof
// expression, which is an unevaluated context, so it's even safe in the case
// where expressions passed have side effects (roaring64_bitmap_from(my_func(),
// ++i))
// Include a 0 at the beginning of the array to make the array length > 0
// (zero sized arrays are not valid in standard c/c++)
#define roaring64_bitmap_from(...) \
roaring64_bitmap_of_ptr( \
(sizeof((const uint64_t[]){0, __VA_ARGS__}) / sizeof(uint64_t)) - 1, \
&((const uint64_t[]){0, __VA_ARGS__})[1])
#endif
/**
* Create a new bitmap by moving containers from a 32 bit roaring bitmap.
*
* After calling this function, the original bitmap will be empty, and the
* returned bitmap will contain all the values from the original bitmap.
*/
roaring64_bitmap_t *roaring64_bitmap_move_from_roaring32(roaring_bitmap_t *r);
/**
* Create a new bitmap containing all the values in [min, max) that are at a
* distance k*step from min.
* The returned pointer may be NULL in case of errors.
*/
roaring64_bitmap_t *roaring64_bitmap_from_range(uint64_t min, uint64_t max,
uint64_t step);
/**
* Adds the provided value to the bitmap.
*/
void roaring64_bitmap_add(roaring64_bitmap_t *r, uint64_t val);
/**
* Adds the provided value to the bitmap.
* Returns true if a new value was added, false if the value already existed.
*/
bool roaring64_bitmap_add_checked(roaring64_bitmap_t *r, uint64_t val);
/**
* Add an item, using context from a previous insert for faster insertion.
*
* `context` will be used to store information between calls to make bulk
* operations faster. `*context` should be zero-initialized before the first
* call to this function.
*
* Modifying the bitmap in any way (other than `-bulk` suffixed functions)
* will invalidate the stored context, calling this function with a non-zero
* context after doing any modification invokes undefined behavior.
*
* In order to exploit this optimization, the caller should call this function
* with values with the same high 48 bits of the value consecutively.
*/
void roaring64_bitmap_add_bulk(roaring64_bitmap_t *r,
roaring64_bulk_context_t *context, uint64_t val);
/**
* Add `n_args` values from `vals`, faster than repeatedly calling
* `roaring64_bitmap_add()`
*
* In order to exploit this optimization, the caller should attempt to keep
* values with the same high 48 bits of the value as consecutive elements in
* `vals`.
*/
void roaring64_bitmap_add_many(roaring64_bitmap_t *r, size_t n_args,
const uint64_t *vals);
/**
* Add all values in range [min, max).
*/
void roaring64_bitmap_add_range(roaring64_bitmap_t *r, uint64_t min,
uint64_t max);
/**
* Add all values in range [min, max].
*/
void roaring64_bitmap_add_range_closed(roaring64_bitmap_t *r, uint64_t min,
uint64_t max);
/**
* Removes a value from the bitmap if present.
*/
void roaring64_bitmap_remove(roaring64_bitmap_t *r, uint64_t val);
/**
* Removes a value from the bitmap if present, returns true if the value was
* removed and false if the value was not present.
*/
bool roaring64_bitmap_remove_checked(roaring64_bitmap_t *r, uint64_t val);
/**
* Remove an item, using context from a previous insert for faster removal.
*
* `context` will be used to store information between calls to make bulk
* operations faster. `*context` should be zero-initialized before the first
* call to this function.
*
* Modifying the bitmap in any way (other than `-bulk` suffixed functions)
* will invalidate the stored context, calling this function with a non-zero
* context after doing any modification invokes undefined behavior.
*
* In order to exploit this optimization, the caller should call this function
* with values with the same high 48 bits of the value consecutively.
*/
void roaring64_bitmap_remove_bulk(roaring64_bitmap_t *r,
roaring64_bulk_context_t *context,
uint64_t val);
/**
* Remove `n_args` values from `vals`, faster than repeatedly calling
* `roaring64_bitmap_remove()`
*
* In order to exploit this optimization, the caller should attempt to keep
* values with the same high 48 bits of the value as consecutive elements in
* `vals`.
*/
void roaring64_bitmap_remove_many(roaring64_bitmap_t *r, size_t n_args,
const uint64_t *vals);
/**
* Remove all values in range [min, max).
*/
void roaring64_bitmap_remove_range(roaring64_bitmap_t *r, uint64_t min,
uint64_t max);
/**
* Remove all values in range [min, max].
*/
void roaring64_bitmap_remove_range_closed(roaring64_bitmap_t *r, uint64_t min,
uint64_t max);
/**
* Empties the bitmap.
*/
void roaring64_bitmap_clear(roaring64_bitmap_t *r);
/**
* Returns true if the provided value is present.
*/
bool roaring64_bitmap_contains(const roaring64_bitmap_t *r, uint64_t val);
/**
* Returns true if all values in the range [min, max) are present.
*/
bool roaring64_bitmap_contains_range(const roaring64_bitmap_t *r, uint64_t min,
uint64_t max);
/**
* Returns true if all values in the range [min, max] are present.
*/
bool roaring64_bitmap_contains_range_closed(const roaring64_bitmap_t *r,
uint64_t min, uint64_t max);
/**
* Check if an item is present using context from a previous insert or search
* for faster search.
*
* `context` will be used to store information between calls to make bulk
* operations faster. `*context` should be zero-initialized before the first
* call to this function.
*
* Modifying the bitmap in any way (other than `-bulk` suffixed functions)
* will invalidate the stored context, calling this function with a non-zero
* context after doing any modification invokes undefined behavior.
*
* In order to exploit this optimization, the caller should call this function
* with values with the same high 48 bits of the value consecutively.
*/
bool roaring64_bitmap_contains_bulk(const roaring64_bitmap_t *r,
roaring64_bulk_context_t *context,
uint64_t val);
/**
* Selects the element at index 'rank' where the smallest element is at index 0.
* If the size of the bitmap is strictly greater than rank, then this function
* returns true and sets element to the element of given rank. Otherwise, it
* returns false.
*/
bool roaring64_bitmap_select(const roaring64_bitmap_t *r, uint64_t rank,
uint64_t *element);
/**
* Returns the number of integers that are smaller or equal to x. Thus if x is
* the first element, this function will return 1. If x is smaller than the
* smallest element, this function will return 0.
*
* The indexing convention differs between roaring64_bitmap_select and
* roaring64_bitmap_rank: roaring_bitmap64_select refers to the smallest value
* as having index 0, whereas roaring64_bitmap_rank returns 1 when ranking
* the smallest value.
*/
uint64_t roaring64_bitmap_rank(const roaring64_bitmap_t *r, uint64_t val);
/**
* Returns true if the given value is in the bitmap, and sets `out_index` to the
* (0-based) index of the value in the bitmap. Returns false if the value is not
* in the bitmap.
*/
bool roaring64_bitmap_get_index(const roaring64_bitmap_t *r, uint64_t val,
uint64_t *out_index);
/**
* Returns the number of values in the bitmap.
*/
uint64_t roaring64_bitmap_get_cardinality(const roaring64_bitmap_t *r);
/**
* Returns the number of elements in the range [min, max).
*/
uint64_t roaring64_bitmap_range_cardinality(const roaring64_bitmap_t *r,
uint64_t min, uint64_t max);
/**
* Returns the number of elements in the range [min, max]
*/
uint64_t roaring64_bitmap_range_closed_cardinality(const roaring64_bitmap_t *r,
uint64_t min, uint64_t max);
/**
* Returns true if the bitmap is empty (cardinality is zero).
*/
bool roaring64_bitmap_is_empty(const roaring64_bitmap_t *r);
/**
* Returns the smallest value in the set, or UINT64_MAX if the set is empty.
*/
uint64_t roaring64_bitmap_minimum(const roaring64_bitmap_t *r);
/**
* Returns the largest value in the set, or 0 if empty.
*/
uint64_t roaring64_bitmap_maximum(const roaring64_bitmap_t *r);
/**
* Remove run-length encoding even when it is more space efficient.
* Return whether a change was applied.
*/
bool roaring64_bitmap_remove_run_compression(roaring64_bitmap_t *r);
/**
* Returns true if the result has at least one run container.
*/
bool roaring64_bitmap_run_optimize(roaring64_bitmap_t *r);
/**
* Shrinks internal arrays to eliminate any unused capacity. Returns the number
* of bytes freed.
*/
size_t roaring64_bitmap_shrink_to_fit(roaring64_bitmap_t *r);
/**
* (For advanced users.)
* Collect statistics about the bitmap
*/
void roaring64_bitmap_statistics(const roaring64_bitmap_t *r,
roaring64_statistics_t *stat);
/**
* Perform internal consistency checks.
*
* Returns true if the bitmap is consistent. It may be useful to call this
* after deserializing bitmaps from untrusted sources. If
* roaring64_bitmap_internal_validate returns true, then the bitmap is
* consistent and can be trusted not to cause crashes or memory corruption.
*
* If reason is non-null, it will be set to a string describing the first
* inconsistency found if any.
*/
bool roaring64_bitmap_internal_validate(const roaring64_bitmap_t *r,
const char **reason);
/**
* Return true if the two bitmaps contain the same elements.
*/
bool roaring64_bitmap_equals(const roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* Return true if all the elements of r1 are also in r2.
*/
bool roaring64_bitmap_is_subset(const roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* Return true if all the elements of r1 are also in r2, and r2 is strictly
* greater than r1.
*/
bool roaring64_bitmap_is_strict_subset(const roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* Computes the intersection between two bitmaps and returns new bitmap. The
* caller is responsible for free-ing the result.
*
* Performance hint: if you are computing the intersection between several
* bitmaps, two-by-two, it is best to start with the smallest bitmaps. You may
* also rely on roaring64_bitmap_and_inplace to avoid creating many temporary
* bitmaps.
*
* The returned pointer may be NULL in case of errors.
*/
roaring64_bitmap_t *roaring64_bitmap_and(const roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* Computes the size of the intersection between two bitmaps.
*/
uint64_t roaring64_bitmap_and_cardinality(const roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* In-place version of `roaring64_bitmap_and()`, modifies `r1`. `r1` and `r2`
* are allowed to be equal.
*
* Performance hint: if you are computing the intersection between several
* bitmaps, two-by-two, it is best to start with the smallest bitmaps.
*/
void roaring64_bitmap_and_inplace(roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* Check whether two bitmaps intersect.
*/
bool roaring64_bitmap_intersect(const roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* Check whether a bitmap intersects the range [min, max).
*/
bool roaring64_bitmap_intersect_with_range(const roaring64_bitmap_t *r,
uint64_t min, uint64_t max);
/**
* Computes the Jaccard index between two bitmaps. (Also known as the Tanimoto
* distance, or the Jaccard similarity coefficient)
*
* The Jaccard index is undefined if both bitmaps are empty.
*/
double roaring64_bitmap_jaccard_index(const roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* Computes the union between two bitmaps and returns new bitmap. The caller is
* responsible for free-ing the result.
* The returned pointer may be NULL in case of errors.
*/
roaring64_bitmap_t *roaring64_bitmap_or(const roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* Computes the size of the union between two bitmaps.
*/
uint64_t roaring64_bitmap_or_cardinality(const roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* In-place version of `roaring64_bitmap_or(), modifies `r1`.
*/
void roaring64_bitmap_or_inplace(roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* Computes the symmetric difference (xor) between two bitmaps and returns a new
* bitmap. The caller is responsible for free-ing the result.
* The returned pointer may be NULL in case of errors.
*/
roaring64_bitmap_t *roaring64_bitmap_xor(const roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* Computes the size of the symmetric difference (xor) between two bitmaps.
*/
uint64_t roaring64_bitmap_xor_cardinality(const roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* In-place version of `roaring64_bitmap_xor()`, modifies `r1`. `r1` and `r2`
* are not allowed to be equal (that would result in an empty bitmap).
*/
void roaring64_bitmap_xor_inplace(roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* Computes the difference (andnot) between two bitmaps and returns a new
* bitmap. The caller is responsible for free-ing the result.
* The returned pointer may be NULL in case of errors.
*/
roaring64_bitmap_t *roaring64_bitmap_andnot(const roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* Computes the size of the difference (andnot) between two bitmaps.
*/
uint64_t roaring64_bitmap_andnot_cardinality(const roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* In-place version of `roaring64_bitmap_andnot()`, modifies `r1`. `r1` and `r2`
* are not allowed to be equal (that would result in an empty bitmap).
*/
void roaring64_bitmap_andnot_inplace(roaring64_bitmap_t *r1,
const roaring64_bitmap_t *r2);
/**
* Compute the negation of the bitmap in the interval [min, max).
* The number of negated values is `max - min`. Areas outside the range are
* passed through unchanged.
* The returned pointer may be NULL in case of errors.
*/
roaring64_bitmap_t *roaring64_bitmap_flip(const roaring64_bitmap_t *r,
uint64_t min, uint64_t max);
/**
* Compute the negation of the bitmap in the interval [min, max].
* The number of negated values is `max - min + 1`. Areas outside the range are
* passed through unchanged.
* The returned pointer may be NULL in case of errors.
*/
roaring64_bitmap_t *roaring64_bitmap_flip_closed(const roaring64_bitmap_t *r,
uint64_t min, uint64_t max);
/**
* In-place version of `roaring64_bitmap_flip`. Compute the negation of the
* bitmap in the interval [min, max). The number of negated values is `max -
* min`. Areas outside the range are passed through unchanged.
*/
void roaring64_bitmap_flip_inplace(roaring64_bitmap_t *r, uint64_t min,
uint64_t max);
/**
* In-place version of `roaring64_bitmap_flip_closed`. Compute the negation of
* the bitmap in the interval [min, max]. The number of negated values is `max -
* min + 1`. Areas outside the range are passed through unchanged.
*/
void roaring64_bitmap_flip_closed_inplace(roaring64_bitmap_t *r, uint64_t min,
uint64_t max);
/**
* Return a copy of the bitmap with all values shifted by offset.
*
* If `positive` is true, the shift is added, otherwise subtracted. Values that
* overflow or underflow uint64_t are dropped. The caller is responsible for
* freeing the returned bitmap.
*/
roaring64_bitmap_t *roaring64_bitmap_add_offset_signed(
const roaring64_bitmap_t *r, bool positive, uint64_t offset);
/**
* Return a copy of the bitmap with all values shifted up by offset.
*
* Values that overflow or underflow uint64_t are dropped. The caller is
* responsible for freeing the returned bitmap.
*/
static inline roaring64_bitmap_t *roaring64_bitmap_add_offset(
const roaring64_bitmap_t *r, uint64_t offset) {
return roaring64_bitmap_add_offset_signed(r, true, offset);
}
/**
* Return a copy of the bitmap with all values shifted down by offset.
*
* Values that overflow or underflow uint64_t are dropped. The caller is
* responsible for freeing the returned bitmap.
*/
static inline roaring64_bitmap_t *roaring64_bitmap_sub_offset(
const roaring64_bitmap_t *r, uint64_t offset) {
return roaring64_bitmap_add_offset_signed(r, false, offset);
}
/**
* How many bytes are required to serialize this bitmap.
*
* This is meant to be compatible with other languages:
* https://github.com/RoaringBitmap/RoaringFormatSpec#extension-for-64-bit-implementations
*/
size_t roaring64_bitmap_portable_size_in_bytes(const roaring64_bitmap_t *r);
/**
* Write a bitmap to a buffer. The output buffer should refer to at least
* `roaring64_bitmap_portable_size_in_bytes(r)` bytes of allocated memory.
*
* Returns how many bytes were written, which should match
* `roaring64_bitmap_portable_size_in_bytes(r)`.
*
* This is meant to be compatible with other languages:
* https://github.com/RoaringBitmap/RoaringFormatSpec#extension-for-64-bit-implementations
*
* When serializing data to a file, we recommend that you also use
* checksums so that, at deserialization, you can be confident
* that you are recovering the correct data.
*/
size_t roaring64_bitmap_portable_serialize(const roaring64_bitmap_t *r,
char *buf);
/**
* Check how many bytes would be read (up to maxbytes) at this pointer if there
* is a valid bitmap, returns zero if there is no valid bitmap.
*
* This is meant to be compatible with other languages
* https://github.com/RoaringBitmap/RoaringFormatSpec#extension-for-64-bit-implementations
*/
size_t roaring64_bitmap_portable_deserialize_size(const char *buf,
size_t maxbytes);
/**
* Read a bitmap from a serialized buffer (reading up to maxbytes).
* In case of failure, NULL is returned.
*
* This is meant to be compatible with other languages
* https://github.com/RoaringBitmap/RoaringFormatSpec#extension-for-64-bit-implementations
*
* The function itself is safe in the sense that it will not cause buffer
* overflows: it will not read beyond the scope of the provided buffer
* (buf,maxbytes).
*
* However, for correct operations, it is assumed that the bitmap
* read was once serialized from a valid bitmap (i.e., it follows the format
* specification). If you provided an incorrect input (garbage), then the bitmap
* read may not be in a valid state and following operations may not lead to
* sensible results. In particular, the serialized array containers need to be
* in sorted order, and the run containers should be in sorted non-overlapping
* order. This is is guaranteed to happen when serializing an existing bitmap,
* but not for random inputs.
*
* If the source is untrusted, you should call
* roaring64_bitmap_internal_validate to check the validity of the
* bitmap prior to using it. Only after calling
* roaring64_bitmap_internal_validate is the bitmap considered safe for use.
*
* We also recommend that you use checksums to check that serialized data
* corresponds to the serialized bitmap. The CRoaring library does not provide
* checksumming.
*/
roaring64_bitmap_t *roaring64_bitmap_portable_deserialize_safe(const char *buf,
size_t maxbytes);
/**
* Returns the number of bytes required to serialize this bitmap in a "frozen"
* format. This is not compatible with any other serialization formats.
*
* `roaring64_bitmap_shrink_to_fit()` must be called before this method.
*/
size_t roaring64_bitmap_frozen_size_in_bytes(const roaring64_bitmap_t *r);
/**
* Serializes the bitmap in a "frozen" format. The given buffer must be at least
* `roaring64_bitmap_frozen_size_in_bytes()` in size. Returns the number of
* bytes used for serialization.
*
* `roaring64_bitmap_shrink_to_fit()` must be called before this method.
*
* The frozen format is optimized for speed of (de)serialization, as well as
* allowing the user to create a bitmap based on a memory mapped file, which is
* possible because the format mimics the memory layout of the bitmap.
*
* Because the format mimics the memory layout of the bitmap, the format is not
* fixed across releases of Roaring Bitmaps, and may change in future releases.
*
* This function is endian-sensitive. If you have a big-endian system (e.g., a
* mainframe IBM s390x), the data format is going to be big-endian and not
* compatible with little-endian systems. This is not a bug, it is by design,
* since the format imitates C memory layout of roaring64_bitmap_t.
*/
size_t roaring64_bitmap_frozen_serialize(const roaring64_bitmap_t *r,
char *buf);
/**
* Creates a readonly bitmap that is a view of the given buffer. The buffer
* must be created with `roaring64_bitmap_frozen_serialize()`, and must be
* aligned by 64 bytes.
*
* Returns NULL if deserialization fails.
*
* The returned bitmap must only be used in a readonly manner. The bitmap must
* be freed using `roaring64_bitmap_free()` as normal. The backing buffer must
* only be freed after the bitmap.
*
* This function is endian-sensitive. If you have a big-endian system (e.g., a
* mainframe IBM s390x), the data format is going to be big-endian and not
* compatible with little-endian systems. This is not a bug, it is by design,
* since the format imitates C memory layout of roaring64_bitmap_t.
*/
roaring64_bitmap_t *roaring64_bitmap_frozen_view(const char *buf,
size_t maxbytes);
/**
* Iterate over the bitmap elements. The function `iterator` is called once for
* all the values with `ptr` (can be NULL) as the second parameter of each call.
*
* `roaring_iterator64` is simply a pointer to a function that returns a bool
* and takes `(uint64_t, void*)` as inputs. True means that the iteration should
* continue, while false means that it should stop.
*
* Returns true if the `roaring64_iterator` returned true throughout (so that
* all data points were necessarily visited).
*
* Iteration is ordered from the smallest to the largest elements.
*/
bool roaring64_bitmap_iterate(const roaring64_bitmap_t *r,
roaring_iterator64 iterator, void *ptr);
/**
* Convert the bitmap to a sorted array `out`.
*
* Caller is responsible to ensure that there is enough memory allocated, e.g.
* ```
* out = malloc(roaring64_bitmap_get_cardinality(bitmap) * sizeof(uint64_t));
* ```
*/
void roaring64_bitmap_to_uint64_array(const roaring64_bitmap_t *r,
uint64_t *out);
/**
* Create an iterator object that can be used to iterate through the values.
* Caller is responsible for calling `roaring64_iterator_free()`.
*
* The iterator is initialized. If there is a value, then this iterator points
* to the first value and `roaring64_iterator_has_value()` returns true. The
* value can be retrieved with `roaring64_iterator_value()`.
*/
roaring64_iterator_t *roaring64_iterator_create(const roaring64_bitmap_t *r);
/**
* Create an iterator object that can be used to iterate through the values.
* Caller is responsible for calling `roaring64_iterator_free()`.
*
* The iterator is initialized. If there is a value, then this iterator points
* to the last value and `roaring64_iterator_has_value()` returns true. The
* value can be retrieved with `roaring64_iterator_value()`.
*/
roaring64_iterator_t *roaring64_iterator_create_last(
const roaring64_bitmap_t *r);
/**
* Re-initializes an existing iterator. Functionally the same as
* `roaring64_iterator_create` without a allocation.
*/
void roaring64_iterator_reinit(const roaring64_bitmap_t *r,
roaring64_iterator_t *it);
/**
* Re-initializes an existing iterator. Functionally the same as
* `roaring64_iterator_create_last` without a allocation.
*/
void roaring64_iterator_reinit_last(const roaring64_bitmap_t *r,
roaring64_iterator_t *it);
/**
* Creates a copy of the iterator. Caller is responsible for calling
* `roaring64_iterator_free()` on the resulting iterator.
*/
roaring64_iterator_t *roaring64_iterator_copy(const roaring64_iterator_t *it);
/**
* Free the iterator.
*/
void roaring64_iterator_free(roaring64_iterator_t *it);
/**
* Returns true if the iterator currently points to a value. If so, calling
* `roaring64_iterator_value()` returns the value.
*/
bool roaring64_iterator_has_value(const roaring64_iterator_t *it);
/**
* Returns the value the iterator currently points to. Should only be called if
* `roaring64_iterator_has_value()` returns true.
*/
uint64_t roaring64_iterator_value(const roaring64_iterator_t *it);
/**
* Advance the iterator. If there is a new value, then
* `roaring64_iterator_has_value()` returns true. Values are traversed in
* increasing order. For convenience, returns the result of
* `roaring64_iterator_has_value()`.
*
* Once this returns false, `roaring64_iterator_advance` should not be called on
* the iterator again. Calling `roaring64_iterator_previous` is allowed.
*/
bool roaring64_iterator_advance(roaring64_iterator_t *it);
/**
* Decrement the iterator. If there is a new value, then
* `roaring64_iterator_has_value()` returns true. Values are traversed in
* decreasing order. For convenience, returns the result of
* `roaring64_iterator_has_value()`.
*
* Once this returns false, `roaring64_iterator_previous` should not be called
* on the iterator again. Calling `roaring64_iterator_advance` is allowed.
*/
bool roaring64_iterator_previous(roaring64_iterator_t *it);
/**
* Move the iterator to the first value greater than or equal to `val`, if it
* exists at or after the current position of the iterator. If there is a new
* value, then `roaring64_iterator_has_value()` returns true. Values are
* traversed in increasing order. For convenience, returns the result of
* `roaring64_iterator_has_value()`.
*/
bool roaring64_iterator_move_equalorlarger(roaring64_iterator_t *it,
uint64_t val);
/**
* Reads up to `count` values from the iterator into the given `buf`. Returns
* the number of elements read. The number of elements read can be smaller than
* `count`, which means that there are no more elements in the bitmap.
*
* This function can be used together with other iterator functions.
*/
uint64_t roaring64_iterator_read(roaring64_iterator_t *it, uint64_t *buf,
uint64_t count);
/**
* Reads previous ${count} values from iterator into user-supplied ${buf}.
* Returns the number of read elements.
* This number can be smaller than ${count}, which means that iterator is
* drained.
*
* Values are written in descending order: buf[0] is the highest (current)
* value, buf[ret-1] is the lowest value read.
*
* This function satisfies semantics of reverse iteration and can be used
* together with other iterator functions.
* - first value is copied from the current iterator value
* - after function returns, iterator is positioned at the previous element
*/
uint64_t roaring64_iterator_read_backward(roaring64_iterator_t *it,
uint64_t *buf, uint64_t count);
typedef struct roaring64_range_closed_s {
uint64_t min;
uint64_t max;
} roaring64_range_closed_t;
/**
* Reads next ${count} ranges from iterator into user-supplied ${buf}.
* A range is defined as a maximal interval of consecutive values.
* For example, the set {1,2,3,5,6} contains two ranges: [1..3] and [5..6].
* Each range is represented as a struct {min,max}, both endpoints included.
* Consecutive values that span internal container boundaries are merged into
* a single range.
*
* Returns the number of read ranges.
* This number can be smaller than ${count}, which means that the iterator is
* drained.
*
* This function can be used together with other iterator functions.
* - first range will start with the current iterator value
* - after the function returns, the iterator is positioned at the next element
* after the end of the last returned range, or has_value is false if
* the bitmap is exhausted.
*/
size_t roaring64_iterator_read_ranges(roaring64_iterator_t *it,
roaring64_range_closed_t *buf,
size_t count);
/**
* Reads previous ${count} ranges from iterator into user-supplied ${buf}.
* A range is defined as a maximal interval of consecutive values.
* For example, the set {1,2,3,5,6} contains two ranges: [1..3] and [5..6].
* Each range is represented as a struct {min,max}, both endpoints included.
* Consecutive values that span internal container boundaries are merged into
* a single range.
*
* Returns the number of read ranges.
* This number can be smaller than ${count}, which means that the iterator is
* drained.
*
* Ranges are returned in reverse order, e.g. the first range returned is the
* highest range (ending at the current value).
*
* This function can be used together with other iterator functions.
* - first range will end with the current iterator value
* - after the function returns, the iterator is positioned at the element
* before the beginning of the last returned range, or has_value is false if
* the bitmap is exhausted.
*/
size_t roaring64_iterator_read_prev_ranges(roaring64_iterator_t *it,
roaring64_range_closed_t *buf,
size_t count);
#ifdef __cplusplus
} // extern "C"
} // namespace roaring
} // namespace api
#endif
#endif /* ROARING64_H */
+53
View File
@@ -0,0 +1,53 @@
#ifndef ROARING64_INTERNAL_H
#define ROARING64_INTERNAL_H
/* Local Redis patch (not part of upstream CRoaring).
*
* Upstream defines `struct roaring64_bitmap_s` privately inside
* src/roaring64.c. Redis needs the layout to walk every allocation behind a
* 64-bit bitmap for MEMORY USAGE accounting, fork-child page dismissal and
* active defragmentation (see src/bitroar.c), exactly like it already
* does for the 32-bit `roaring_bitmap_t` whose layout upstream does expose.
* This header moves the struct definition verbatim out of roaring64.c so both
* files share one definition.
*
* This header must be included after a header that defines `container_t`
* (e.g. <roaring/containers/containers.h>); roaring64.c includes containers.h
* last on purpose, so this header cannot pull it in itself.
*/
#include <roaring/art/art.h>
#include <roaring/roaring64.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace api {
#endif
struct roaring64_bitmap_s {
art_t art;
uint8_t flags;
uint64_t first_free;
uint64_t capacity;
container_t **containers;
};
/* Leaf type of the ART used to keep the high 48 bits of each entry.
* Low 8 bits: typecode
* High 56 bits: container index */
static inline uint8_t roaring64_leaf_typecode(roaring64_leaf_t leaf) {
return (uint8_t)leaf;
}
static inline uint64_t roaring64_leaf_index(roaring64_leaf_t leaf) {
return leaf >> 8;
}
#ifdef __cplusplus
} // namespace api
} // namespace roaring
} // extern "C"
#endif
#endif /* ROARING64_INTERNAL_H */
+316
View File
@@ -0,0 +1,316 @@
/*
* roaring_array.h
*
* This file declares the roaring_array helper structure and the operations
* used to manage it. A roaring array is the top-level index used by a 32-bit
* Roaring bitmap: it stores sorted 16-bit high keys alongside the container
* pointers and type codes associated with each key.
*
* In effect, it is the directory that maps each populated 16-bit chunk of the
* 32-bit value space to the container holding that chunk's low 16-bit values.
* The functions in this header handle allocation, lookup, insertion,
* replacement, copying, serialization support, and structural updates on that
* directory.
*/
#ifndef INCLUDE_ROARING_ARRAY_H
#define INCLUDE_ROARING_ARRAY_H
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <roaring/array_util.h>
#include <roaring/containers/containers.h> // get_writable_copy_if_shared()
#ifdef __cplusplus
extern "C" {
namespace roaring {
// Note: in pure C++ code, you should avoid putting `using` in header files
using api::roaring_array_t;
namespace internal {
#endif
enum {
SERIAL_COOKIE_NO_RUNCONTAINER = 12346,
SERIAL_COOKIE = 12347,
FROZEN_COOKIE = 13766,
NO_OFFSET_THRESHOLD = 4
};
/**
* Create a new roaring array
*/
roaring_array_t *ra_create(void);
/**
* Initialize an existing roaring array with the specified capacity (in number
* of containers)
*/
bool ra_init_with_capacity(roaring_array_t *new_ra, uint32_t cap);
/**
* Initialize with zero capacity
*/
void ra_init(roaring_array_t *t);
/**
* Copies this roaring array, we assume that dest is not initialized
*/
bool ra_copy(const roaring_array_t *source, roaring_array_t *dest,
bool copy_on_write);
/*
* Shrinks the capacity, returns the number of bytes saved.
*/
int ra_shrink_to_fit(roaring_array_t *ra);
/**
* Copies this roaring array, we assume that dest is initialized
*/
bool ra_overwrite(const roaring_array_t *source, roaring_array_t *dest,
bool copy_on_write);
/**
* Frees the memory used by a roaring array
*/
void ra_clear(roaring_array_t *r);
/**
* Frees the memory used by a roaring array, but does not free the containers
*/
void ra_clear_without_containers(roaring_array_t *r);
/**
* Frees just the containers
*/
void ra_clear_containers(roaring_array_t *ra);
/**
* Get the index corresponding to a 16-bit key
*/
inline int32_t ra_get_index(const roaring_array_t *ra, uint16_t x) {
if ((ra->size == 0) || ra->keys[ra->size - 1] == x) return ra->size - 1;
return binarySearch(ra->keys, (int32_t)ra->size, x);
}
/**
* Retrieves the container at index i, filling in the typecode
*/
inline container_t *ra_get_container_at_index(const roaring_array_t *ra,
uint16_t i, uint8_t *typecode) {
*typecode = ra->typecodes[i];
return ra->containers[i];
}
/**
* Retrieves the key at index i
*/
inline uint16_t ra_get_key_at_index(const roaring_array_t *ra, uint16_t i) {
return ra->keys[i];
}
/**
* Add a new key-value pair at index i
*/
void ra_insert_new_key_value_at(roaring_array_t *ra, int32_t i, uint16_t key,
container_t *c, uint8_t typecode);
/**
* Append a new key-value pair
*/
void ra_append(roaring_array_t *ra, uint16_t key, container_t *c,
uint8_t typecode);
/**
* Append a new key-value pair to ra, cloning (in COW sense) a value from sa
* at index index
*/
void ra_append_copy(roaring_array_t *ra, const roaring_array_t *sa,
uint16_t index, bool copy_on_write);
/**
* Append new key-value pairs to ra, cloning (in COW sense) values from sa
* at indexes
* [start_index, end_index)
*/
void ra_append_copy_range(roaring_array_t *ra, const roaring_array_t *sa,
int32_t start_index, int32_t end_index,
bool copy_on_write);
/** appends from sa to ra, ending with the greatest key that is
* is less or equal stopping_key
*/
void ra_append_copies_until(roaring_array_t *ra, const roaring_array_t *sa,
uint16_t stopping_key, bool copy_on_write);
/** appends from sa to ra, starting with the smallest key that is
* is strictly greater than before_start
*/
void ra_append_copies_after(roaring_array_t *ra, const roaring_array_t *sa,
uint16_t before_start, bool copy_on_write);
/**
* Move the key-value pairs to ra from sa at indexes
* [start_index, end_index), old array should not be freed
* (use ra_clear_without_containers)
**/
void ra_append_move_range(roaring_array_t *ra, roaring_array_t *sa,
int32_t start_index, int32_t end_index);
/**
* Append new key-value pairs to ra, from sa at indexes
* [start_index, end_index)
*/
void ra_append_range(roaring_array_t *ra, roaring_array_t *sa,
int32_t start_index, int32_t end_index,
bool copy_on_write);
/**
* Set the container at the corresponding index using the specified
* typecode.
*/
inline void ra_set_container_at_index(const roaring_array_t *ra, int32_t i,
container_t *c, uint8_t typecode) {
assert(i < ra->size);
ra->containers[i] = c;
ra->typecodes[i] = typecode;
}
container_t *ra_get_container(roaring_array_t *ra, uint16_t x,
uint8_t *typecode);
/**
* If needed, increase the capacity of the array so that it can fit k values
* (at
* least);
*/
bool extend_array(roaring_array_t *ra, int32_t k);
inline int32_t ra_get_size(const roaring_array_t *ra) { return ra->size; }
static inline int32_t ra_advance_until(const roaring_array_t *ra, uint16_t x,
int32_t pos) {
return advanceUntil(ra->keys, pos, ra->size, x);
}
int32_t ra_advance_until_freeing(roaring_array_t *ra, uint16_t x, int32_t pos);
void ra_downsize(roaring_array_t *ra, int32_t new_length);
inline void ra_replace_key_and_container_at_index(roaring_array_t *ra,
int32_t i, uint16_t key,
container_t *c,
uint8_t typecode) {
assert(i < ra->size);
ra->keys[i] = key;
ra->containers[i] = c;
ra->typecodes[i] = typecode;
}
// write set bits to an array
void ra_to_uint32_array(const roaring_array_t *ra, uint32_t *ans);
/**
* write a bitmap to a buffer. This is meant to be compatible with
* the
* Java and Go versions. Return the size in bytes of the serialized
* output (which should be ra_portable_size_in_bytes(ra)).
*/
size_t ra_portable_serialize(const roaring_array_t *ra, char *buf);
/**
* read a bitmap from a serialized version. This is meant to be compatible
* with the Java and Go versions.
* maxbytes indicates how many bytes available from buf.
* When the function returns true, roaring_array_t is populated with the data
* and *readbytes indicates how many bytes were read. In all cases, if the
* function returns true, then maxbytes >= *readbytes.
*/
bool ra_portable_deserialize(roaring_array_t *ra, const char *buf,
const size_t maxbytes, size_t *readbytes);
/**
* Quickly checks whether there is a serialized bitmap at the pointer,
* not exceeding size "maxbytes" in bytes. This function does not allocate
* memory dynamically.
*
* This function returns 0 if and only if no valid bitmap is found.
* Otherwise, it returns how many bytes are occupied by the bitmap data.
*/
size_t ra_portable_deserialize_size(const char *buf, const size_t maxbytes);
/**
* How many bytes are required to serialize this bitmap (meant to be
* compatible
* with Java and Go versions)
*/
size_t ra_portable_size_in_bytes(const roaring_array_t *ra);
/**
* return true if it contains at least one run container.
*/
bool ra_has_run_container(const roaring_array_t *ra);
/**
* Size of the header when serializing (meant to be compatible
* with Java and Go versions)
*/
uint32_t ra_portable_header_size(const roaring_array_t *ra);
/**
* If the container at the index i is share, unshare it (creating a local
* copy if needed).
*/
static inline void ra_unshare_container_at_index(roaring_array_t *ra,
uint16_t i) {
assert(i < ra->size);
ra->containers[i] =
get_writable_copy_if_shared(ra->containers[i], &ra->typecodes[i]);
}
/**
* remove at index i, sliding over all entries after i
*/
void ra_remove_at_index(roaring_array_t *ra, int32_t i);
/**
* clears all containers, sets the size at 0 and shrinks the memory usage.
*/
void ra_reset(roaring_array_t *ra);
/**
* remove at index i, sliding over all entries after i. Free removed container.
*/
void ra_remove_at_index_and_free(roaring_array_t *ra, int32_t i);
/**
* remove a chunk of indices, sliding over entries after it
*/
// void ra_remove_index_range(roaring_array_t *ra, int32_t begin, int32_t end);
// used in inplace andNot only, to slide left the containers from
// the mutated RoaringBitmap that are after the largest container of
// the argument RoaringBitmap. It is followed by a call to resize.
//
void ra_copy_range(roaring_array_t *ra, uint32_t begin, uint32_t end,
uint32_t new_begin);
/**
* Shifts rightmost $count containers to the left (distance < 0) or
* to the right (distance > 0).
* Allocates memory if necessary.
* This function doesn't free or create new containers.
* Caller is responsible for that.
*/
void ra_shift_tail(roaring_array_t *ra, int32_t count, int32_t distance);
#ifdef __cplusplus
} // namespace internal
}
} // extern "C" { namespace roaring {
#endif
#endif
+159
View File
@@ -0,0 +1,159 @@
/*
Shared type definitions used across the CRoaring public API and internal
components.
This file centralizes common typedefs and small structs that are referenced
by multiple headers, including iterator callback signatures, roaring array
metadata, statistics structures, and compatibility types used to bridge the
C and C++ builds.
*/
#ifndef ROARING_TYPES_H
#define ROARING_TYPES_H
#include <stdbool.h>
#include <stdint.h>
#include <roaring/portability.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace api {
#endif
/**
* When building .c files as C++, there's added compile-time checking if the
* container types are derived from a `container_t` base class. So long as
* such a base class is empty, the struct will behave compatibly with C structs
* despite the derivation. This is due to the Empty Base Class Optimization:
*
* https://en.cppreference.com/w/cpp/language/ebo
*
* But since C isn't namespaced, taking `container_t` globally might collide
* with other projects. So roaring.h uses ROARING_CONTAINER_T, while internal
* code #undefs that after declaring `typedef ROARING_CONTAINER_T container_t;`
*/
#if defined(__cplusplus)
extern "C++" {
struct container_s {};
}
#define ROARING_CONTAINER_T ::roaring::api::container_s
#else
#define ROARING_CONTAINER_T void // no compile-time checking
#endif
#define ROARING_FLAG_COW UINT8_C(0x1)
#define ROARING_FLAG_FROZEN UINT8_C(0x2)
/**
* Roaring arrays are array-based key-value pairs having containers as values
* and 16-bit integer keys. A roaring bitmap might be implemented as such.
*/
// parallel arrays. Element sizes quite different.
// Alternative is array
// of structs. Which would have better
// cache performance through binary searches?
typedef struct roaring_array_s {
int32_t size;
int32_t allocation_size;
ROARING_CONTAINER_T **containers; // Use container_t in non-API files!
uint16_t *keys;
uint8_t *typecodes;
uint8_t flags;
} roaring_array_t;
typedef bool (*roaring_iterator)(uint32_t value, void *param);
typedef bool (*roaring_iterator64)(uint64_t value, void *param);
/**
* (For advanced users.)
* The roaring_statistics_t can be used to collect detailed statistics about
* the composition of a roaring bitmap.
*/
typedef struct roaring_statistics_s {
uint32_t n_containers; /* number of containers */
uint32_t n_array_containers; /* number of array containers */
uint32_t n_run_containers; /* number of run containers */
uint32_t n_bitset_containers; /* number of bitmap containers */
uint32_t
n_values_array_containers; /* number of values in array containers */
uint32_t n_values_run_containers; /* number of values in run containers */
uint32_t
n_values_bitset_containers; /* number of values in bitmap containers */
uint32_t n_bytes_array_containers; /* number of allocated bytes in array
containers */
uint32_t n_bytes_run_containers; /* number of allocated bytes in run
containers */
uint32_t n_bytes_bitset_containers; /* number of allocated bytes in bitmap
containers */
uint32_t
max_value; /* the maximal value, undefined if cardinality is zero */
uint32_t
min_value; /* the minimal value, undefined if cardinality is zero */
CROARING_DEPRECATED
uint64_t sum_value; /* deprecated always zero */
uint64_t cardinality; /* total number of values stored in the bitmap */
// and n_values_arrays, n_values_rle, n_values_bitmap
} roaring_statistics_t;
/**
* (For advanced users.)
* The roaring64_statistics_t can be used to collect detailed statistics about
* the composition of a roaring64 bitmap.
*/
typedef struct roaring64_statistics_s {
uint64_t n_containers; /* number of containers */
uint64_t n_array_containers; /* number of array containers */
uint64_t n_run_containers; /* number of run containers */
uint64_t n_bitset_containers; /* number of bitmap containers */
uint64_t
n_values_array_containers; /* number of values in array containers */
uint64_t n_values_run_containers; /* number of values in run containers */
uint64_t
n_values_bitset_containers; /* number of values in bitmap containers */
uint64_t n_bytes_array_containers; /* number of allocated bytes in array
containers */
uint64_t n_bytes_run_containers; /* number of allocated bytes in run
containers */
uint64_t n_bytes_bitset_containers; /* number of allocated bytes in bitmap
containers */
uint64_t
max_value; /* the maximal value, undefined if cardinality is zero */
uint64_t
min_value; /* the minimal value, undefined if cardinality is zero */
uint64_t cardinality; /* total number of values stored in the bitmap */
// and n_values_arrays, n_values_rle, n_values_bitmap
} roaring64_statistics_t;
/**
* Roaring-internal type used to iterate within a roaring container.
*/
typedef struct roaring_container_iterator_s {
// For bitset and array containers this is the index of the bit / entry.
// For run containers this points at the run.
int32_t index;
} roaring_container_iterator_t;
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace api {
#endif
#endif /* ROARING_TYPES_H */
+12
View File
@@ -0,0 +1,12 @@
// clang-format off
// /include/roaring/roaring_version.h automatically generated by release.py, do not change by hand
#ifndef ROARING_INCLUDE_ROARING_VERSION
#define ROARING_INCLUDE_ROARING_VERSION
#define ROARING_VERSION "4.7.2"
enum {
ROARING_VERSION_MAJOR = 4,
ROARING_VERSION_MINOR = 7,
ROARING_VERSION_REVISION = 2
};
#endif // ROARING_INCLUDE_ROARING_VERSION
// clang-format on
+89
View File
@@ -0,0 +1,89 @@
/*
* utilasm.h
*
* This file provides optional inline-assembly helpers for low-level bit
* manipulation on supported x86/x64 targets. These macros are used to map a
* few performance-sensitive operations, such as shifting, testing, setting,
* and clearing bits, to specific machine instructions when inline assembly is
* enabled.
*
* The intent is to centralize these architecture-specific primitives behind a
* small interface so the rest of the codebase can use them conditionally while
* keeping the generic implementation paths separate.
*/
#ifndef INCLUDE_UTILASM_H_
#define INCLUDE_UTILASM_H_
#include <roaring/portability.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
#endif
#if defined(CROARING_INLINE_ASM)
#define CROARING_ASMBITMANIPOPTIMIZATION // optimization flag
#define ASM_SHIFT_RIGHT(srcReg, bitsReg, destReg) \
__asm volatile("shrx %1, %2, %0" \
: "=r"(destReg) \
: /* write */ \
"r"(bitsReg), /* read only */ \
"r"(srcReg) /* read only */ \
)
#define ASM_INPLACESHIFT_RIGHT(srcReg, bitsReg) \
__asm volatile("shrx %1, %0, %0" \
: "+r"(srcReg) \
: /* read/write */ \
"r"(bitsReg) /* read only */ \
)
#define ASM_SHIFT_LEFT(srcReg, bitsReg, destReg) \
__asm volatile("shlx %1, %2, %0" \
: "=r"(destReg) \
: /* write */ \
"r"(bitsReg), /* read only */ \
"r"(srcReg) /* read only */ \
)
// set bit at position testBit within testByte to 1 and
// copy cmovDst to cmovSrc if that bit was previously clear
#define ASM_SET_BIT_INC_WAS_CLEAR(testByte, testBit, count) \
__asm volatile( \
"bts %2, %0\n" \
"sbb $-1, %1\n" \
: "+r"(testByte), /* read/write */ \
"+r"(count) \
: /* read/write */ \
"r"(testBit) /* read only */ \
)
#define ASM_CLEAR_BIT_DEC_WAS_SET(testByte, testBit, count) \
__asm volatile( \
"btr %2, %0\n" \
"sbb $0, %1\n" \
: "+r"(testByte), /* read/write */ \
"+r"(count) \
: /* read/write */ \
"r"(testBit) /* read only */ \
)
#define ASM_BT64(testByte, testBit, count) \
__asm volatile( \
"bt %2,%1\n" \
"sbb %0,%0" /*could use setb */ \
: "=r"(count) \
: /* write */ \
"r"(testByte), /* read only */ \
"r"(testBit) /* read only */ \
)
#endif
#ifdef __cplusplus
}
} // extern "C" { namespace roaring {
#endif
#endif /* INCLUDE_UTILASM_H_ */
+2168
View File
File diff suppressed because it is too large. Load diff
+2399
View File
File diff suppressed because it is too large. Load diff
+491
View File
@@ -0,0 +1,491 @@
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <roaring/bitset/bitset.h>
#include <roaring/memory.h>
#include <roaring/portability.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
extern inline void bitset_print(const bitset_t *b);
extern inline bool bitset_for_each(const bitset_t *b, bitset_iterator iterator,
void *ptr);
extern inline size_t bitset_next_set_bits(const bitset_t *bitset,
size_t *buffer, size_t capacity,
size_t *startfrom);
extern inline void bitset_set_to_value(bitset_t *bitset, size_t i, bool flag);
extern inline bool bitset_next_set_bit(const bitset_t *bitset, size_t *i);
extern inline void bitset_set(bitset_t *bitset, size_t i);
extern inline bool bitset_get(const bitset_t *bitset, size_t i);
extern inline size_t bitset_size_in_words(const bitset_t *bitset);
extern inline size_t bitset_size_in_bits(const bitset_t *bitset);
extern inline size_t bitset_size_in_bytes(const bitset_t *bitset);
/* Create a new bitset. Return NULL in case of failure. */
bitset_t *bitset_create(void) {
bitset_t *bitset = NULL;
/* Allocate the bitset itself. */
if ((bitset = (bitset_t *)roaring_malloc(sizeof(bitset_t))) == NULL) {
return NULL;
}
bitset->array = NULL;
bitset->arraysize = 0;
bitset->capacity = 0;
return bitset;
}
/* Create a new bitset able to contain size bits. Return NULL in case of
* failure. */
bitset_t *bitset_create_with_capacity(size_t size) {
bitset_t *bitset = NULL;
/* Allocate the bitset itself. */
if ((bitset = (bitset_t *)roaring_malloc(sizeof(bitset_t))) == NULL) {
return NULL;
}
bitset->arraysize =
(size + sizeof(uint64_t) * 8 - 1) / (sizeof(uint64_t) * 8);
bitset->capacity = bitset->arraysize;
if ((bitset->array = (uint64_t *)roaring_calloc(
bitset->arraysize, sizeof(uint64_t))) == NULL) {
roaring_free(bitset);
return NULL;
}
return bitset;
}
/* Create a copy */
bitset_t *bitset_copy(const bitset_t *bitset) {
bitset_t *copy = NULL;
/* Allocate the bitset itself. */
if ((copy = (bitset_t *)roaring_malloc(sizeof(bitset_t))) == NULL) {
return NULL;
}
memcpy(copy, bitset, sizeof(bitset_t));
copy->capacity = copy->arraysize;
if ((copy->array = (uint64_t *)roaring_malloc(sizeof(uint64_t) *
bitset->arraysize)) == NULL) {
roaring_free(copy);
return NULL;
}
memcpy(copy->array, bitset->array, sizeof(uint64_t) * bitset->arraysize);
return copy;
}
void bitset_clear(bitset_t *bitset) {
memset(bitset->array, 0, sizeof(uint64_t) * bitset->arraysize);
}
void bitset_fill(bitset_t *bitset) {
memset(bitset->array, 0xff, sizeof(uint64_t) * bitset->arraysize);
}
void bitset_shift_left(bitset_t *bitset, size_t s) {
size_t extra_words = s / 64;
int inword_shift = s % 64;
size_t as = bitset->arraysize;
if (inword_shift == 0) {
bitset_resize(bitset, as + extra_words, false);
// could be done with a memmove
for (size_t i = as + extra_words; i > extra_words; i--) {
bitset->array[i - 1] = bitset->array[i - 1 - extra_words];
}
} else {
bitset_resize(bitset, as + extra_words + 1, true);
bitset->array[as + extra_words] =
bitset->array[as - 1] >> (64 - inword_shift);
for (size_t i = as + extra_words; i >= extra_words + 2; i--) {
bitset->array[i - 1] =
(bitset->array[i - 1 - extra_words] << inword_shift) |
(bitset->array[i - 2 - extra_words] >> (64 - inword_shift));
}
bitset->array[extra_words] = bitset->array[0] << inword_shift;
}
for (size_t i = 0; i < extra_words; i++) {
bitset->array[i] = 0;
}
}
void bitset_shift_right(bitset_t *bitset, size_t s) {
size_t extra_words = s / 64;
int inword_shift = s % 64;
size_t as = bitset->arraysize;
if (inword_shift == 0) {
// could be done with a memmove
for (size_t i = 0; i < as - extra_words; i++) {
bitset->array[i] = bitset->array[i + extra_words];
}
bitset_resize(bitset, as - extra_words, false);
} else {
for (size_t i = 0; i + extra_words + 1 < as; i++) {
bitset->array[i] =
(bitset->array[i + extra_words] >> inword_shift) |
(bitset->array[i + extra_words + 1] << (64 - inword_shift));
}
bitset->array[as - extra_words - 1] =
(bitset->array[as - 1] >> inword_shift);
bitset_resize(bitset, as - extra_words, false);
}
}
/* Free memory. */
void bitset_free(bitset_t *bitset) {
if (bitset == NULL) {
return;
}
roaring_free(bitset->array);
roaring_free(bitset);
}
/* Resize the bitset so that it can support newarraysize * 64 bits. Return true
* in case of success, false for failure. */
bool bitset_resize(bitset_t *bitset, size_t newarraysize, bool padwithzeroes) {
if (newarraysize > SIZE_MAX / 64) {
return false;
}
size_t smallest =
newarraysize < bitset->arraysize ? newarraysize : bitset->arraysize;
if (bitset->capacity < newarraysize) {
uint64_t *newarray;
size_t newcapacity = bitset->capacity;
if (newcapacity == 0) {
newcapacity = 1;
}
while (newcapacity < newarraysize) {
newcapacity *= 2;
}
if ((newarray = (uint64_t *)roaring_realloc(
bitset->array, sizeof(uint64_t) * newcapacity)) == NULL) {
return false;
}
bitset->capacity = newcapacity;
bitset->array = newarray;
}
if (padwithzeroes && (newarraysize > smallest))
memset(bitset->array + smallest, 0,
sizeof(uint64_t) * (newarraysize - smallest));
bitset->arraysize = newarraysize;
return true; // success!
}
size_t bitset_count(const bitset_t *bitset) {
size_t card = 0;
size_t k = 0;
for (; k + 7 < bitset->arraysize; k += 8) {
card += roaring_hamming(bitset->array[k]);
card += roaring_hamming(bitset->array[k + 1]);
card += roaring_hamming(bitset->array[k + 2]);
card += roaring_hamming(bitset->array[k + 3]);
card += roaring_hamming(bitset->array[k + 4]);
card += roaring_hamming(bitset->array[k + 5]);
card += roaring_hamming(bitset->array[k + 6]);
card += roaring_hamming(bitset->array[k + 7]);
}
for (; k + 3 < bitset->arraysize; k += 4) {
card += roaring_hamming(bitset->array[k]);
card += roaring_hamming(bitset->array[k + 1]);
card += roaring_hamming(bitset->array[k + 2]);
card += roaring_hamming(bitset->array[k + 3]);
}
for (; k < bitset->arraysize; k++) {
card += roaring_hamming(bitset->array[k]);
}
return card;
}
bool bitset_inplace_union(bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2) {
size_t minlength =
b1->arraysize < b2->arraysize ? b1->arraysize : b2->arraysize;
for (size_t k = 0; k < minlength; ++k) {
b1->array[k] |= b2->array[k];
}
if (b2->arraysize > b1->arraysize) {
size_t oldsize = b1->arraysize;
if (!bitset_resize(b1, b2->arraysize, false)) return false;
memcpy(b1->array + oldsize, b2->array + oldsize,
(b2->arraysize - oldsize) * sizeof(uint64_t));
}
return true;
}
bool bitset_empty(const bitset_t *bitset) {
for (size_t k = 0; k < bitset->arraysize; k++) {
if (bitset->array[k] != 0) {
return false;
}
}
return true;
}
size_t bitset_minimum(const bitset_t *bitset) {
for (size_t k = 0; k < bitset->arraysize; k++) {
uint64_t w = bitset->array[k];
if (w != 0) {
return roaring_trailing_zeroes(w) + k * 64;
}
}
return SIZE_MAX;
}
bool bitset_grow(bitset_t *bitset, size_t newarraysize) {
if (newarraysize < bitset->arraysize) {
return false;
}
if (newarraysize > SIZE_MAX / 64) {
return false;
}
if (bitset->capacity < newarraysize) {
uint64_t *newarray;
size_t newcapacity = (UINT64_C(0xFFFFFFFFFFFFFFFF) >>
roaring_leading_zeroes(newarraysize)) +
1;
while (newcapacity < newarraysize) {
newcapacity *= 2;
}
if ((newarray = (uint64_t *)roaring_realloc(
bitset->array, sizeof(uint64_t) * newcapacity)) == NULL) {
return false;
}
bitset->capacity = newcapacity;
bitset->array = newarray;
}
memset(bitset->array + bitset->arraysize, 0,
sizeof(uint64_t) * (newarraysize - bitset->arraysize));
bitset->arraysize = newarraysize;
return true; // success!
}
size_t bitset_maximum(const bitset_t *bitset) {
for (size_t k = bitset->arraysize; k > 0; k--) {
uint64_t w = bitset->array[k - 1];
if (w != 0) {
return 63 - roaring_leading_zeroes(w) + (k - 1) * 64;
}
}
return 0;
}
/* Returns true if bitsets share no common elements, false otherwise.
*
* Performs early-out if common element found. */
bool bitsets_disjoint(const bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2) {
size_t minlength =
b1->arraysize < b2->arraysize ? b1->arraysize : b2->arraysize;
for (size_t k = 0; k < minlength; k++) {
if ((b1->array[k] & b2->array[k]) != 0) return false;
}
return true;
}
/* Returns true if bitsets contain at least 1 common element, false if they are
* disjoint.
*
* Performs early-out if common element found. */
bool bitsets_intersect(const bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2) {
size_t minlength =
b1->arraysize < b2->arraysize ? b1->arraysize : b2->arraysize;
for (size_t k = 0; k < minlength; k++) {
if ((b1->array[k] & b2->array[k]) != 0) return true;
}
return false;
}
/* Returns true if b has any bits set in or after b->array[starting_loc]. */
static bool any_bits_set(const bitset_t *b, size_t starting_loc) {
if (starting_loc >= b->arraysize) {
return false;
}
for (size_t k = starting_loc; k < b->arraysize; k++) {
if (b->array[k] != 0) return true;
}
return false;
}
/* Returns true if b1 has all of b2's bits set.
*
* Performs early out if a bit is found in b2 that is not found in b1. */
bool bitset_contains_all(const bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2) {
size_t min_size = b1->arraysize;
if (b1->arraysize > b2->arraysize) {
min_size = b2->arraysize;
}
for (size_t k = 0; k < min_size; k++) {
if ((b1->array[k] & b2->array[k]) != b2->array[k]) {
return false;
}
}
if (b2->arraysize > b1->arraysize) {
/* Need to check if b2 has any bits set beyond b1's array */
return !any_bits_set(b2, b1->arraysize);
}
return true;
}
size_t bitset_union_count(const bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2) {
size_t answer = 0;
size_t minlength =
b1->arraysize < b2->arraysize ? b1->arraysize : b2->arraysize;
size_t k = 0;
for (; k + 3 < minlength; k += 4) {
answer += roaring_hamming(b1->array[k] | b2->array[k]);
answer += roaring_hamming(b1->array[k + 1] | b2->array[k + 1]);
answer += roaring_hamming(b1->array[k + 2] | b2->array[k + 2]);
answer += roaring_hamming(b1->array[k + 3] | b2->array[k + 3]);
}
for (; k < minlength; ++k) {
answer += roaring_hamming(b1->array[k] | b2->array[k]);
}
if (b2->arraysize > b1->arraysize) {
// k is equal to b1->arraysize
for (; k + 3 < b2->arraysize; k += 4) {
answer += roaring_hamming(b2->array[k]);
answer += roaring_hamming(b2->array[k + 1]);
answer += roaring_hamming(b2->array[k + 2]);
answer += roaring_hamming(b2->array[k + 3]);
}
for (; k < b2->arraysize; ++k) {
answer += roaring_hamming(b2->array[k]);
}
} else {
// k is equal to b2->arraysize
for (; k + 3 < b1->arraysize; k += 4) {
answer += roaring_hamming(b1->array[k]);
answer += roaring_hamming(b1->array[k + 1]);
answer += roaring_hamming(b1->array[k + 2]);
answer += roaring_hamming(b1->array[k + 3]);
}
for (; k < b1->arraysize; ++k) {
answer += roaring_hamming(b1->array[k]);
}
}
return answer;
}
void bitset_inplace_intersection(bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2) {
size_t minlength =
b1->arraysize < b2->arraysize ? b1->arraysize : b2->arraysize;
size_t k = 0;
for (; k < minlength; ++k) {
b1->array[k] &= b2->array[k];
}
for (; k < b1->arraysize; ++k) {
b1->array[k] = 0; // memset could, maybe, be a tiny bit faster
}
}
size_t bitset_intersection_count(const bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2) {
size_t answer = 0;
size_t minlength =
b1->arraysize < b2->arraysize ? b1->arraysize : b2->arraysize;
for (size_t k = 0; k < minlength; ++k) {
answer += roaring_hamming(b1->array[k] & b2->array[k]);
}
return answer;
}
void bitset_inplace_difference(bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2) {
size_t minlength =
b1->arraysize < b2->arraysize ? b1->arraysize : b2->arraysize;
size_t k = 0;
for (; k < minlength; ++k) {
b1->array[k] &= ~(b2->array[k]);
}
}
size_t bitset_difference_count(const bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2) {
size_t minlength =
b1->arraysize < b2->arraysize ? b1->arraysize : b2->arraysize;
size_t k = 0;
size_t answer = 0;
for (; k < minlength; ++k) {
answer += roaring_hamming(b1->array[k] & ~(b2->array[k]));
}
for (; k < b1->arraysize; ++k) {
answer += roaring_hamming(b1->array[k]);
}
return answer;
}
bool bitset_inplace_symmetric_difference(
bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2) {
size_t minlength =
b1->arraysize < b2->arraysize ? b1->arraysize : b2->arraysize;
size_t k = 0;
for (; k < minlength; ++k) {
b1->array[k] ^= b2->array[k];
}
if (b2->arraysize > b1->arraysize) {
size_t oldsize = b1->arraysize;
if (!bitset_resize(b1, b2->arraysize, false)) return false;
memcpy(b1->array + oldsize, b2->array + oldsize,
(b2->arraysize - oldsize) * sizeof(uint64_t));
}
return true;
}
size_t bitset_symmetric_difference_count(
const bitset_t *CROARING_CBITSET_RESTRICT b1,
const bitset_t *CROARING_CBITSET_RESTRICT b2) {
size_t minlength =
b1->arraysize < b2->arraysize ? b1->arraysize : b2->arraysize;
size_t k = 0;
size_t answer = 0;
for (; k < minlength; ++k) {
answer += roaring_hamming(b1->array[k] ^ b2->array[k]);
}
if (b2->arraysize > b1->arraysize) {
for (; k < b2->arraysize; ++k) {
answer += roaring_hamming(b2->array[k]);
}
} else {
for (; k < b1->arraysize; ++k) {
answer += roaring_hamming(b1->array[k]);
}
}
return answer;
}
bool bitset_trim(bitset_t *bitset) {
size_t newsize = bitset->arraysize;
while (newsize > 0) {
if (bitset->array[newsize - 1] == 0)
newsize -= 1;
else
break;
}
if (bitset->capacity == newsize) return true; // nothing to do
uint64_t *newarray;
if ((newarray = (uint64_t *)roaring_realloc(
bitset->array, sizeof(uint64_t) * newsize)) == NULL) {
return false;
}
bitset->array = newarray;
bitset->capacity = newsize;
bitset->arraysize = newsize;
return true;
}
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
+1150
View File
File diff suppressed because it is too large. Load diff
+586
View File
@@ -0,0 +1,586 @@
/*
* array.c
*
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <roaring/containers/array.h>
#include <roaring/memory.h>
#if CROARING_IS_X64
#ifndef CROARING_COMPILER_SUPPORTS_AVX512
#error "CROARING_COMPILER_SUPPORTS_AVX512 needs to be defined."
#endif // CROARING_COMPILER_SUPPORTS_AVX512
#endif
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
extern inline uint16_t array_container_minimum(const array_container_t *arr);
extern inline uint16_t array_container_maximum(const array_container_t *arr);
extern inline int array_container_index_equalorlarger(
const array_container_t *arr, uint16_t x);
extern inline int array_container_rank(const array_container_t *arr,
uint16_t x);
extern inline uint32_t array_container_rank_many(const array_container_t *arr,
uint64_t start_rank,
const uint32_t *begin,
const uint32_t *end,
uint64_t *ans);
extern inline int array_container_get_index(const array_container_t *arr,
uint16_t x);
extern inline bool array_container_contains(const array_container_t *arr,
uint16_t pos);
extern inline int array_container_cardinality(const array_container_t *array);
extern inline bool array_container_nonzero_cardinality(
const array_container_t *array);
extern inline int32_t array_container_serialized_size_in_bytes(int32_t card);
extern inline bool array_container_empty(const array_container_t *array);
extern inline bool array_container_full(const array_container_t *array);
/* Create a new array with capacity size. Return NULL in case of failure. */
array_container_t *array_container_create_given_capacity(int32_t size) {
array_container_t *container;
if ((container = (array_container_t *)roaring_malloc(
sizeof(array_container_t))) == NULL) {
return NULL;
}
if (size <= 0) { // we don't want to rely on malloc(0)
container->array = NULL;
} else if ((container->array = (uint16_t *)roaring_malloc(sizeof(uint16_t) *
size)) == NULL) {
roaring_free(container);
return NULL;
}
container->capacity = size;
container->cardinality = 0;
return container;
}
/* Create a new array. Return NULL in case of failure. */
array_container_t *array_container_create(void) {
return array_container_create_given_capacity(ARRAY_DEFAULT_INIT_SIZE);
}
/* Create a new array containing all values in [min,max). */
array_container_t *array_container_create_range(uint32_t min, uint32_t max) {
array_container_t *answer =
array_container_create_given_capacity(max - min + 1);
if (answer == NULL) return answer;
answer->cardinality = 0;
for (uint32_t k = min; k < max; k++) {
answer->array[answer->cardinality++] = k;
}
return answer;
}
/* Duplicate container */
CROARING_ALLOW_UNALIGNED
array_container_t *array_container_clone(const array_container_t *src) {
array_container_t *newcontainer =
array_container_create_given_capacity(src->capacity);
if (newcontainer == NULL) return NULL;
newcontainer->cardinality = src->cardinality;
memcpy(newcontainer->array, src->array,
src->cardinality * sizeof(uint16_t));
return newcontainer;
}
void array_container_offset(const array_container_t *c, container_t **loc,
container_t **hic, uint16_t offset) {
array_container_t *lo = NULL, *hi = NULL;
int top, lo_cap, hi_cap;
top = (1 << 16) - offset;
lo_cap = count_less(c->array, c->cardinality, top);
if (loc && lo_cap) {
lo = array_container_create_given_capacity(lo_cap);
for (int i = 0; i < lo_cap; ++i) {
lo->array[i] = c->array[i] + offset;
}
lo->cardinality = lo_cap;
*loc = (container_t *)lo;
}
hi_cap = c->cardinality - lo_cap;
if (hic && hi_cap) {
hi = array_container_create_given_capacity(hi_cap);
for (int i = 0; i < hi_cap; ++i) {
hi->array[i] = c->array[lo_cap + i] + offset;
}
hi->cardinality = hi_cap;
*hic = (container_t *)hi;
}
}
int array_container_shrink_to_fit(array_container_t *src) {
if (src->cardinality == src->capacity) return 0; // nothing to do
int savings = src->capacity - src->cardinality;
src->capacity = src->cardinality;
if (src->capacity ==
0) { // we do not want to rely on realloc for zero allocs
roaring_free(src->array);
src->array = NULL;
} else {
uint16_t *oldarray = src->array;
src->array = (uint16_t *)roaring_realloc(
oldarray, src->capacity * sizeof(uint16_t));
if (src->array == NULL) roaring_free(oldarray); // should never happen?
}
return savings;
}
/* Free memory. */
void array_container_free(array_container_t *arr) {
if (arr == NULL) return;
roaring_free(arr->array);
roaring_free(arr);
}
static inline int32_t grow_capacity(int32_t capacity) {
return (capacity <= 0) ? ARRAY_DEFAULT_INIT_SIZE
: capacity < 64 ? capacity * 2
: capacity < 1024 ? capacity * 3 / 2
: capacity * 5 / 4;
}
static inline int32_t clamp(int32_t val, int32_t min, int32_t max) {
return ((val < min) ? min : (val > max) ? max : val);
}
void array_container_grow(array_container_t *container, int32_t min,
bool preserve) {
int32_t max = (min <= DEFAULT_MAX_SIZE ? DEFAULT_MAX_SIZE : 65536);
int32_t new_capacity = clamp(grow_capacity(container->capacity), min, max);
container->capacity = new_capacity;
uint16_t *array = container->array;
if (preserve) {
container->array =
(uint16_t *)roaring_realloc(array, new_capacity * sizeof(uint16_t));
if (container->array == NULL) roaring_free(array);
} else {
roaring_free(array);
container->array =
(uint16_t *)roaring_malloc(new_capacity * sizeof(uint16_t));
}
// if realloc fails, we have container->array == NULL.
}
/* Copy one container into another. We assume that they are distinct. */
void array_container_copy(const array_container_t *src,
array_container_t *dst) {
const int32_t cardinality = src->cardinality;
if (cardinality > dst->capacity) {
array_container_grow(dst, cardinality, false);
}
dst->cardinality = cardinality;
memcpy(dst->array, src->array, cardinality * sizeof(uint16_t));
}
void array_container_add_from_range(array_container_t *arr, uint32_t min,
uint32_t max, uint16_t step) {
for (uint32_t value = min; value < max; value += step) {
array_container_append(arr, value);
}
}
/* Computes the union of array1 and array2 and write the result to arrayout.
* It is assumed that arrayout is distinct from both array1 and array2.
*/
void array_container_union(const array_container_t *array_1,
const array_container_t *array_2,
array_container_t *out) {
const int32_t card_1 = array_1->cardinality, card_2 = array_2->cardinality;
const int32_t max_cardinality = card_1 + card_2;
if (out->capacity < max_cardinality) {
array_container_grow(out, max_cardinality, false);
}
out->cardinality = (int32_t)fast_union_uint16(
array_1->array, card_1, array_2->array, card_2, out->array);
}
/* Computes the difference of array1 and array2 and write the result
* to array out.
* Array out does not need to be distinct from array_1
*/
void array_container_andnot(const array_container_t *array_1,
const array_container_t *array_2,
array_container_t *out) {
if (out->capacity < array_1->cardinality)
array_container_grow(out, array_1->cardinality, false);
#if CROARING_IS_X64
if ((croaring_hardware_support() & ROARING_SUPPORTS_AVX2) &&
(out != array_1) && (out != array_2)) {
out->cardinality = difference_vector16(
array_1->array, array_1->cardinality, array_2->array,
array_2->cardinality, out->array);
} else {
out->cardinality =
difference_uint16(array_1->array, array_1->cardinality,
array_2->array, array_2->cardinality, out->array);
}
#else
out->cardinality =
difference_uint16(array_1->array, array_1->cardinality, array_2->array,
array_2->cardinality, out->array);
#endif
}
/* Computes the symmetric difference of array1 and array2 and write the
* result
* to arrayout.
* It is assumed that arrayout is distinct from both array1 and array2.
*/
void array_container_xor(const array_container_t *array_1,
const array_container_t *array_2,
array_container_t *out) {
const int32_t card_1 = array_1->cardinality, card_2 = array_2->cardinality;
const int32_t max_cardinality = card_1 + card_2;
if (out->capacity < max_cardinality) {
array_container_grow(out, max_cardinality, false);
}
#if CROARING_IS_X64
if (croaring_hardware_support() & ROARING_SUPPORTS_AVX2) {
out->cardinality =
xor_vector16(array_1->array, array_1->cardinality, array_2->array,
array_2->cardinality, out->array);
} else {
out->cardinality =
xor_uint16(array_1->array, array_1->cardinality, array_2->array,
array_2->cardinality, out->array);
}
#else
out->cardinality =
xor_uint16(array_1->array, array_1->cardinality, array_2->array,
array_2->cardinality, out->array);
#endif
}
static inline int32_t minimum_int32(int32_t a, int32_t b) {
return (a < b) ? a : b;
}
/* computes the intersection of array1 and array2 and write the result to
* arrayout.
* It is assumed that arrayout is distinct from both array1 and array2.
* */
void array_container_intersection(const array_container_t *array1,
const array_container_t *array2,
array_container_t *out) {
int32_t card_1 = array1->cardinality, card_2 = array2->cardinality,
min_card = minimum_int32(card_1, card_2);
const int threshold = 64; // subject to tuning
#if CROARING_IS_X64
if (out->capacity < min_card) {
array_container_grow(out, min_card + sizeof(__m128i) / sizeof(uint16_t),
false);
}
#else
if (out->capacity < min_card) {
array_container_grow(out, min_card, false);
}
#endif
if (card_1 * threshold < card_2) {
out->cardinality = intersect_skewed_uint16(
array1->array, card_1, array2->array, card_2, out->array);
} else if (card_2 * threshold < card_1) {
out->cardinality = intersect_skewed_uint16(
array2->array, card_2, array1->array, card_1, out->array);
} else {
#if CROARING_IS_X64
if (croaring_hardware_support() & ROARING_SUPPORTS_AVX2) {
out->cardinality = intersect_vector16(
array1->array, card_1, array2->array, card_2, out->array);
} else {
out->cardinality = intersect_uint16(
array1->array, card_1, array2->array, card_2, out->array);
}
#else
out->cardinality = intersect_uint16(array1->array, card_1,
array2->array, card_2, out->array);
#endif
}
}
/* computes the size of the intersection of array1 and array2
* */
int array_container_intersection_cardinality(const array_container_t *array1,
const array_container_t *array2) {
int32_t card_1 = array1->cardinality, card_2 = array2->cardinality;
const int threshold = 64; // subject to tuning
if (card_1 * threshold < card_2) {
return intersect_skewed_uint16_cardinality(array1->array, card_1,
array2->array, card_2);
} else if (card_2 * threshold < card_1) {
return intersect_skewed_uint16_cardinality(array2->array, card_2,
array1->array, card_1);
} else {
#if CROARING_IS_X64
if (croaring_hardware_support() & ROARING_SUPPORTS_AVX2) {
return intersect_vector16_cardinality(array1->array, card_1,
array2->array, card_2);
} else {
return intersect_uint16_cardinality(array1->array, card_1,
array2->array, card_2);
}
#else
return intersect_uint16_cardinality(array1->array, card_1,
array2->array, card_2);
#endif
}
}
bool array_container_intersect(const array_container_t *array1,
const array_container_t *array2) {
int32_t card_1 = array1->cardinality, card_2 = array2->cardinality;
const int threshold = 64; // subject to tuning
if (card_1 * threshold < card_2) {
return intersect_skewed_uint16_nonempty(array1->array, card_1,
array2->array, card_2);
} else if (card_2 * threshold < card_1) {
return intersect_skewed_uint16_nonempty(array2->array, card_2,
array1->array, card_1);
} else {
// we do not bother vectorizing
return intersect_uint16_nonempty(array1->array, card_1, array2->array,
card_2);
}
}
/* computes the intersection of array1 and array2 and write the result to
* array1.
* */
void array_container_intersection_inplace(array_container_t *src_1,
const array_container_t *src_2) {
int32_t card_1 = src_1->cardinality, card_2 = src_2->cardinality;
const int threshold = 64; // subject to tuning
if (card_1 * threshold < card_2) {
src_1->cardinality = intersect_skewed_uint16(
src_1->array, card_1, src_2->array, card_2, src_1->array);
} else if (card_2 * threshold < card_1) {
src_1->cardinality = intersect_skewed_uint16(
src_2->array, card_2, src_1->array, card_1, src_1->array);
} else {
#if CROARING_IS_X64
if (croaring_hardware_support() & ROARING_SUPPORTS_AVX2) {
src_1->cardinality = intersect_vector16_inplace(
src_1->array, card_1, src_2->array, card_2);
} else {
src_1->cardinality = intersect_uint16(
src_1->array, card_1, src_2->array, card_2, src_1->array);
}
#else
src_1->cardinality = intersect_uint16(
src_1->array, card_1, src_2->array, card_2, src_1->array);
#endif
}
}
CROARING_ALLOW_UNALIGNED
int array_container_to_uint32_array(void *vout, const array_container_t *cont,
uint32_t base) {
#if CROARING_IS_X64
int support = croaring_hardware_support();
#if CROARING_COMPILER_SUPPORTS_AVX512
if (support & ROARING_SUPPORTS_AVX512) {
return avx512_array_container_to_uint32_array(vout, cont->array,
cont->cardinality, base);
}
#endif
if (support & ROARING_SUPPORTS_AVX2) {
return array_container_to_uint32_array_vector16(
vout, cont->array, cont->cardinality, base);
}
#endif // CROARING_IS_X64
int outpos = 0;
uint32_t *out = (uint32_t *)vout;
size_t i = 0;
for (; i < (size_t)cont->cardinality; ++i) {
const uint32_t val = base + cont->array[i];
memcpy(out + outpos, &val,
sizeof(uint32_t)); // should be compiled as a MOV on x64
outpos++;
}
return outpos;
}
void array_container_printf(const array_container_t *v) {
if (v->cardinality == 0) {
printf("{}");
return;
}
printf("{");
printf("%d", v->array[0]);
for (int i = 1; i < v->cardinality; ++i) {
printf(",%d", v->array[i]);
}
printf("}");
}
void array_container_printf_as_uint32_array(const array_container_t *v,
uint32_t base) {
if (v->cardinality == 0) {
return;
}
printf("%u", v->array[0] + base);
for (int i = 1; i < v->cardinality; ++i) {
printf(",%u", v->array[i] + base);
}
}
/*
* Validate the container. Returns true if valid.
*/
bool array_container_validate(const array_container_t *v, const char **reason) {
if (v->capacity < 0) {
*reason = "negative capacity";
return false;
}
if (v->cardinality < 0) {
*reason = "negative cardinality";
return false;
}
if (v->cardinality > v->capacity) {
*reason = "cardinality exceeds capacity";
return false;
}
if (v->cardinality > DEFAULT_MAX_SIZE) {
*reason = "cardinality exceeds DEFAULT_MAX_SIZE";
return false;
}
if (v->cardinality == 0) {
*reason = "zero cardinality";
return false;
}
if (v->array == NULL) {
*reason = "NULL array pointer";
return false;
}
uint16_t prev = v->array[0];
for (int i = 1; i < v->cardinality; ++i) {
if (v->array[i] <= prev) {
*reason = "array elements not strictly increasing";
return false;
}
prev = v->array[i];
}
return true;
}
/* Compute the number of runs */
int32_t array_container_number_of_runs(const array_container_t *ac) {
// Can SIMD work here?
int32_t nr_runs = 0;
int32_t prev = -2;
for (const uint16_t *p = ac->array; p != ac->array + ac->cardinality; ++p) {
if (*p != prev + 1) nr_runs++;
prev = *p;
}
return nr_runs;
}
/**
* Writes the underlying array to buf, outputs how many bytes were written.
* The number of bytes written should be
* array_container_size_in_bytes(container).
*
*/
int32_t array_container_write(const array_container_t *container, char *buf) {
#if CROARING_IS_BIG_ENDIAN
for (int32_t i = 0; i < container->cardinality; ++i) {
uint16_t v_le = croaring_htole16(container->array[i]);
memcpy(buf + i * sizeof(uint16_t), &v_le, sizeof(uint16_t));
}
#else
memcpy(buf, container->array, container->cardinality * sizeof(uint16_t));
#endif
return array_container_size_in_bytes(container);
}
bool array_container_is_subset(const array_container_t *container1,
const array_container_t *container2) {
if (container1->cardinality > container2->cardinality) {
return false;
}
int i1 = 0, i2 = 0;
while (i1 < container1->cardinality && i2 < container2->cardinality) {
if (container1->array[i1] == container2->array[i2]) {
i1++;
i2++;
} else if (container1->array[i1] > container2->array[i2]) {
i2++;
} else { // container1->array[i1] < container2->array[i2]
return false;
}
}
if (i1 == container1->cardinality) {
return true;
} else {
return false;
}
}
int32_t array_container_read(int32_t cardinality, array_container_t *container,
const char *buf) {
if (container->capacity < cardinality) {
array_container_grow(container, cardinality, false);
}
container->cardinality = cardinality;
#if CROARING_IS_BIG_ENDIAN
for (int32_t i = 0; i < cardinality; ++i) {
uint16_t v_le;
memcpy(&v_le, buf + i * sizeof(uint16_t), sizeof(uint16_t));
container->array[i] = croaring_letoh16(v_le);
}
#else
memcpy(container->array, buf, container->cardinality * sizeof(uint16_t));
#endif
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++)
if (!iterator(cont->array[i] + base, ptr)) return false;
return true;
}
bool array_container_iterate64(const array_container_t *cont, uint32_t base,
roaring_iterator64 iterator, uint64_t high_bits,
void *ptr) {
for (int i = 0; i < cont->cardinality; i++)
if (!iterator(high_bits | (uint64_t)(cont->array[i] + base), ptr))
return false;
return true;
}
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+354
View File
@@ -0,0 +1,354 @@
#include <stdio.h>
#include <roaring/bitset_util.h>
#include <roaring/containers/containers.h>
#include <roaring/containers/convert.h>
#include <roaring/containers/perfparameters.h>
#if CROARING_IS_X64
#ifndef CROARING_COMPILER_SUPPORTS_AVX512
#error "CROARING_COMPILER_SUPPORTS_AVX512 needs to be defined."
#endif // CROARING_COMPILER_SUPPORTS_AVX512
#endif
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
// file contains grubby stuff that must know impl. details of all container
// types.
bitset_container_t *bitset_container_from_array(const array_container_t *ac) {
bitset_container_t *ans = bitset_container_create();
int limit = array_container_cardinality(ac);
for (int i = 0; i < limit; ++i) bitset_container_set(ans, ac->array[i]);
return ans;
}
bitset_container_t *bitset_container_from_run(const run_container_t *arr) {
int card = run_container_cardinality(arr);
bitset_container_t *answer = bitset_container_create();
for (int rlepos = 0; rlepos < arr->n_runs; ++rlepos) {
rle16_t vl = arr->runs[rlepos];
bitset_set_lenrange(answer->words, vl.value, vl.length);
}
answer->cardinality = card;
return answer;
}
array_container_t *array_container_from_run(const run_container_t *arr) {
array_container_t *answer =
array_container_create_given_capacity(run_container_cardinality(arr));
answer->cardinality = 0;
for (int rlepos = 0; rlepos < arr->n_runs; ++rlepos) {
int run_start = arr->runs[rlepos].value;
int run_end = run_start + arr->runs[rlepos].length;
for (int run_value = run_start; run_value <= run_end; ++run_value) {
answer->array[answer->cardinality++] = (uint16_t)run_value;
}
}
return answer;
}
array_container_t *array_container_from_bitset(const bitset_container_t *bits) {
array_container_t *result =
array_container_create_given_capacity(bits->cardinality);
result->cardinality = bits->cardinality;
#if CROARING_IS_X64
#if CROARING_COMPILER_SUPPORTS_AVX512
if (croaring_hardware_support() & ROARING_SUPPORTS_AVX512) {
bitset_extract_setbits_avx512_uint16(
bits->words, BITSET_CONTAINER_SIZE_IN_WORDS, result->array,
bits->cardinality, 0);
} else
#endif
{
// sse version ends up being slower here
// (bitset_extract_setbits_sse_uint16)
// because of the sparsity of the data
bitset_extract_setbits_uint16(
bits->words, BITSET_CONTAINER_SIZE_IN_WORDS, result->array, 0);
}
#else
// If the system is not x64, then we have no accelerated function.
bitset_extract_setbits_uint16(bits->words, BITSET_CONTAINER_SIZE_IN_WORDS,
result->array, 0);
#endif
return result;
}
/* assumes that container has adequate space. Run from [s,e] (inclusive) */
static void add_run(run_container_t *rc, int s, int e) {
rc->runs[rc->n_runs].value = s;
rc->runs[rc->n_runs].length = e - s;
rc->n_runs++;
}
run_container_t *run_container_from_array(const array_container_t *c) {
int32_t n_runs = array_container_number_of_runs(c);
run_container_t *answer = run_container_create_given_capacity(n_runs);
int prev = -2;
int run_start = -1;
int32_t card = c->cardinality;
if (card == 0) return answer;
for (int i = 0; i < card; ++i) {
const uint16_t cur_val = c->array[i];
if (cur_val != prev + 1) {
// new run starts; flush old one, if any
if (run_start != -1) add_run(answer, run_start, prev);
run_start = cur_val;
}
prev = c->array[i];
}
// now prev is the last seen value
add_run(answer, run_start, prev);
// assert(run_container_cardinality(answer) == c->cardinality);
return answer;
}
/**
* Convert the runcontainer to either a Bitmap or an Array Container, depending
* on the cardinality. Frees the container.
* Allocates and returns new container, which caller is responsible for freeing.
* It does not free the run container.
*/
container_t *convert_to_bitset_or_array_container(run_container_t *rc,
int32_t card,
uint8_t *resulttype) {
if (card <= DEFAULT_MAX_SIZE) {
array_container_t *answer = array_container_create_given_capacity(card);
answer->cardinality = 0;
for (int rlepos = 0; rlepos < rc->n_runs; ++rlepos) {
uint16_t run_start = rc->runs[rlepos].value;
uint16_t run_end = run_start + rc->runs[rlepos].length;
for (uint16_t run_value = run_start; run_value < run_end;
++run_value) {
answer->array[answer->cardinality++] = run_value;
}
answer->array[answer->cardinality++] = run_end;
}
assert(card == answer->cardinality);
*resulttype = ARRAY_CONTAINER_TYPE;
// run_container_free(r);
return answer;
}
bitset_container_t *answer = bitset_container_create();
for (int rlepos = 0; rlepos < rc->n_runs; ++rlepos) {
uint16_t run_start = rc->runs[rlepos].value;
bitset_set_lenrange(answer->words, run_start, rc->runs[rlepos].length);
}
answer->cardinality = card;
*resulttype = BITSET_CONTAINER_TYPE;
// run_container_free(r);
return answer;
}
/* Converts a run container to either an array or a bitset, IF it saves space.
*/
/* If a conversion occurs, the caller is responsible to free the original
* container and
* he becomes responsible to free the new one. */
container_t *convert_run_to_efficient_container(run_container_t *c,
uint8_t *typecode_after) {
int32_t size_as_run_container =
run_container_serialized_size_in_bytes(c->n_runs);
int32_t size_as_bitset_container =
bitset_container_serialized_size_in_bytes();
int32_t card = run_container_cardinality(c);
int32_t size_as_array_container =
array_container_serialized_size_in_bytes(card);
int32_t min_size_non_run =
size_as_bitset_container < size_as_array_container
? size_as_bitset_container
: size_as_array_container;
if (size_as_run_container <= min_size_non_run) { // no conversion
*typecode_after = RUN_CONTAINER_TYPE;
return c;
}
if (card <= DEFAULT_MAX_SIZE) {
// to array
array_container_t *answer = array_container_create_given_capacity(card);
answer->cardinality = 0;
for (int rlepos = 0; rlepos < c->n_runs; ++rlepos) {
int run_start = c->runs[rlepos].value;
int run_end = run_start + c->runs[rlepos].length;
for (int run_value = run_start; run_value <= run_end; ++run_value) {
answer->array[answer->cardinality++] = (uint16_t)run_value;
}
}
*typecode_after = ARRAY_CONTAINER_TYPE;
return answer;
}
// else to bitset
bitset_container_t *answer = bitset_container_create();
for (int rlepos = 0; rlepos < c->n_runs; ++rlepos) {
int start = c->runs[rlepos].value;
int end = start + c->runs[rlepos].length;
bitset_set_range(answer->words, start, end + 1);
}
answer->cardinality = card;
*typecode_after = BITSET_CONTAINER_TYPE;
return answer;
}
// like convert_run_to_efficient_container but frees the old result if needed
container_t *convert_run_to_efficient_container_and_free(
run_container_t *c, uint8_t *typecode_after) {
container_t *answer = convert_run_to_efficient_container(c, typecode_after);
if (answer != c) run_container_free(c);
return answer;
}
/* once converted, the original container is disposed here, rather than
in roaring_array
*/
// TODO: split into run- array- and bitset- subfunctions for sanity;
// a few function calls won't really matter.
container_t *convert_run_optimize(container_t *c, uint8_t typecode_original,
uint8_t *typecode_after) {
if (typecode_original == RUN_CONTAINER_TYPE) {
container_t *newc =
convert_run_to_efficient_container(CAST_run(c), typecode_after);
if (newc != c) {
container_free(c, typecode_original);
}
return newc;
} else if (typecode_original == ARRAY_CONTAINER_TYPE) {
// it might need to be converted to a run container.
array_container_t *c_qua_array = CAST_array(c);
int32_t n_runs = array_container_number_of_runs(c_qua_array);
int32_t size_as_run_container =
run_container_serialized_size_in_bytes(n_runs);
int32_t card = array_container_cardinality(c_qua_array);
int32_t size_as_array_container =
array_container_serialized_size_in_bytes(card);
if (size_as_run_container >= size_as_array_container) {
*typecode_after = ARRAY_CONTAINER_TYPE;
return c;
}
// else convert array to run container
run_container_t *answer = run_container_create_given_capacity(n_runs);
int prev = -2;
int run_start = -1;
assert(card > 0);
for (int i = 0; i < card; ++i) {
uint16_t cur_val = c_qua_array->array[i];
if (cur_val != prev + 1) {
// new run starts; flush old one, if any
if (run_start != -1) add_run(answer, run_start, prev);
run_start = cur_val;
}
prev = c_qua_array->array[i];
}
assert(run_start >= 0);
// now prev is the last seen value
add_run(answer, run_start, prev);
*typecode_after = RUN_CONTAINER_TYPE;
array_container_free(c_qua_array);
return answer;
} else if (typecode_original ==
BITSET_CONTAINER_TYPE) { // run conversions on bitset
// does bitset need conversion to run?
bitset_container_t *c_qua_bitset = CAST_bitset(c);
int32_t n_runs = bitset_container_number_of_runs(c_qua_bitset);
int32_t size_as_run_container =
run_container_serialized_size_in_bytes(n_runs);
int32_t size_as_bitset_container =
bitset_container_serialized_size_in_bytes();
if (size_as_bitset_container <= size_as_run_container) {
// no conversion needed.
*typecode_after = BITSET_CONTAINER_TYPE;
return c;
}
// bitset to runcontainer (ported from Java RunContainer(
// BitmapContainer bc, int nbrRuns))
assert(n_runs > 0); // no empty bitmaps
run_container_t *answer = run_container_create_given_capacity(n_runs);
int long_ctr = 0;
uint64_t cur_word = c_qua_bitset->words[0];
while (true) {
while (cur_word == UINT64_C(0) &&
long_ctr < BITSET_CONTAINER_SIZE_IN_WORDS - 1)
cur_word = c_qua_bitset->words[++long_ctr];
if (cur_word == UINT64_C(0)) {
bitset_container_free(c_qua_bitset);
*typecode_after = RUN_CONTAINER_TYPE;
return answer;
}
int local_run_start = roaring_trailing_zeroes(cur_word);
int run_start = local_run_start + 64 * long_ctr;
uint64_t cur_word_with_1s = cur_word | (cur_word - 1);
int run_end = 0;
while (cur_word_with_1s == UINT64_C(0xFFFFFFFFFFFFFFFF) &&
long_ctr < BITSET_CONTAINER_SIZE_IN_WORDS - 1)
cur_word_with_1s = c_qua_bitset->words[++long_ctr];
if (cur_word_with_1s == UINT64_C(0xFFFFFFFFFFFFFFFF)) {
run_end = 64 + long_ctr * 64; // exclusive, I guess
add_run(answer, run_start, run_end - 1);
bitset_container_free(c_qua_bitset);
*typecode_after = RUN_CONTAINER_TYPE;
return answer;
}
int local_run_end = roaring_trailing_zeroes(~cur_word_with_1s);
run_end = local_run_end + long_ctr * 64;
add_run(answer, run_start, run_end - 1);
cur_word = cur_word_with_1s & (cur_word_with_1s + 1);
}
return answer;
} else {
assert(false);
roaring_unreachable;
return NULL;
}
}
container_t *container_from_run_range(const run_container_t *run, uint32_t min,
uint32_t max, uint8_t *typecode_after) {
// We expect most of the time to end up with a bitset container
bitset_container_t *bitset = bitset_container_create();
*typecode_after = BITSET_CONTAINER_TYPE;
int32_t union_cardinality = 0;
for (int32_t i = 0; i < run->n_runs; ++i) {
uint32_t rle_min = run->runs[i].value;
uint32_t rle_max = rle_min + run->runs[i].length;
bitset_set_lenrange(bitset->words, rle_min, rle_max - rle_min);
union_cardinality += run->runs[i].length + 1;
}
union_cardinality += max - min + 1;
union_cardinality -=
bitset_lenrange_cardinality(bitset->words, min, max - min);
bitset_set_lenrange(bitset->words, min, max - min);
bitset->cardinality = union_cardinality;
if (bitset->cardinality <= DEFAULT_MAX_SIZE) {
// we need to convert to an array container
array_container_t *array = array_container_from_bitset(bitset);
*typecode_after = ARRAY_CONTAINER_TYPE;
bitset_container_free(bitset);
return array;
}
return bitset;
}
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
+522
View File
@@ -0,0 +1,522 @@
/*
* mixed_andnot.c. More methods since operation is not symmetric,
* except no "wide" andnot , so no lazy options motivated.
*/
#include <assert.h>
#include <string.h>
#include <roaring/array_util.h>
#include <roaring/bitset_util.h>
#include <roaring/containers/containers.h>
#include <roaring/containers/convert.h>
#include <roaring/containers/mixed_andnot.h>
#include <roaring/containers/perfparameters.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/* Compute the andnot of src_1 and src_2 and write the result to
* dst, a valid array container that could be the same as dst.*/
void array_bitset_container_andnot(const array_container_t *src_1,
const bitset_container_t *src_2,
array_container_t *dst) {
// follows Java implementation as of June 2016
if (dst->capacity < src_1->cardinality) {
array_container_grow(dst, src_1->cardinality, false);
}
int32_t newcard = 0;
const int32_t origcard = src_1->cardinality;
for (int i = 0; i < origcard; ++i) {
uint16_t key = src_1->array[i];
dst->array[newcard] = key;
newcard += 1 - bitset_container_contains(src_2, key);
}
dst->cardinality = newcard;
}
/* Compute the andnot of src_1 and src_2 and write the result to
* src_1 */
void array_bitset_container_iandnot(array_container_t *src_1,
const bitset_container_t *src_2) {
array_bitset_container_andnot(src_1, src_2, src_1);
}
/* Compute the andnot of src_1 and src_2 and write the result to
* dst, which does not initially have a valid container.
* Return true for a bitset result; false for array
*/
bool bitset_array_container_andnot(const bitset_container_t *src_1,
const array_container_t *src_2,
container_t **dst) {
// Java did this directly, but we have option of asm or avx
bitset_container_t *result = bitset_container_create();
bitset_container_copy(src_1, result);
result->cardinality =
(int32_t)bitset_clear_list(result->words, (uint64_t)result->cardinality,
src_2->array, (uint64_t)src_2->cardinality);
// do required type conversions.
if (result->cardinality <= DEFAULT_MAX_SIZE) {
*dst = array_container_from_bitset(result);
bitset_container_free(result);
return false;
}
*dst = result;
return true;
}
/* Compute the andnot of src_1 and src_2 and write the result to
* dst (which has no container initially). It will modify src_1
* to be dst if the result is a bitset. Otherwise, it will
* free src_1 and dst will be a new array container. In both
* cases, the caller is responsible for deallocating dst.
* Returns true iff dst is a bitset */
bool bitset_array_container_iandnot(bitset_container_t *src_1,
const array_container_t *src_2,
container_t **dst) {
*dst = src_1;
src_1->cardinality =
(int32_t)bitset_clear_list(src_1->words, (uint64_t)src_1->cardinality,
src_2->array, (uint64_t)src_2->cardinality);
if (src_1->cardinality <= DEFAULT_MAX_SIZE) {
*dst = array_container_from_bitset(src_1);
bitset_container_free(src_1);
return false; // not bitset
} else
return true;
}
/* Compute the andnot of src_1 and src_2 and write the result to
* dst. Result may be either a bitset or an array container
* (returns "result is bitset"). dst does not initially have
* any container, but becomes either a bitset container (return
* result true) or an array container.
*/
bool run_bitset_container_andnot(const run_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst) {
// follows the Java implementation as of June 2016
int card = run_container_cardinality(src_1);
if (card <= DEFAULT_MAX_SIZE) {
// must be an array
array_container_t *answer = array_container_create_given_capacity(card);
answer->cardinality = 0;
for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) {
rle16_t rle = src_1->runs[rlepos];
for (int run_value = rle.value; run_value <= rle.value + rle.length;
++run_value) {
if (!bitset_container_get(src_2, (uint16_t)run_value)) {
answer->array[answer->cardinality++] = (uint16_t)run_value;
}
}
}
*dst = answer;
return false;
} else { // we guess it will be a bitset, though have to check guess when
// done
bitset_container_t *answer = bitset_container_clone(src_2);
uint32_t last_pos = 0;
for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) {
rle16_t rle = src_1->runs[rlepos];
uint32_t start = rle.value;
uint32_t end = start + rle.length + 1;
bitset_reset_range(answer->words, last_pos, start);
bitset_flip_range(answer->words, start, end);
last_pos = end;
}
bitset_reset_range(answer->words, last_pos, (uint32_t)(1 << 16));
answer->cardinality = bitset_container_compute_cardinality(answer);
if (answer->cardinality <= DEFAULT_MAX_SIZE) {
*dst = array_container_from_bitset(answer);
bitset_container_free(answer);
return false; // not bitset
}
*dst = answer;
return true; // bitset
}
}
/* Compute the andnot of src_1 and src_2 and write the result to
* dst. Result may be either a bitset or an array container
* (returns "result is bitset"). dst does not initially have
* any container, but becomes either a bitset container (return
* result true) or an array container.
*/
bool run_bitset_container_iandnot(run_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst) {
// dummy implementation
bool ans = run_bitset_container_andnot(src_1, src_2, dst);
run_container_free(src_1);
return ans;
}
/* Compute the andnot of src_1 and src_2 and write the result to
* dst. Result may be either a bitset or an array container
* (returns "result is bitset"). dst does not initially have
* any container, but becomes either a bitset container (return
* result true) or an array container.
*/
bool bitset_run_container_andnot(const bitset_container_t *src_1,
const run_container_t *src_2,
container_t **dst) {
// follows Java implementation
bitset_container_t *result = bitset_container_create();
bitset_container_copy(src_1, result);
for (int32_t rlepos = 0; rlepos < src_2->n_runs; ++rlepos) {
rle16_t rle = src_2->runs[rlepos];
bitset_reset_range(result->words, rle.value,
rle.value + rle.length + UINT32_C(1));
}
result->cardinality = bitset_container_compute_cardinality(result);
if (result->cardinality <= DEFAULT_MAX_SIZE) {
*dst = array_container_from_bitset(result);
bitset_container_free(result);
return false; // not bitset
}
*dst = result;
return true; // bitset
}
/* Compute the andnot of src_1 and src_2 and write the result to
* dst (which has no container initially). It will modify src_1
* to be dst if the result is a bitset. Otherwise, it will
* free src_1 and dst will be a new array container. In both
* cases, the caller is responsible for deallocating dst.
* Returns true iff dst is a bitset */
bool bitset_run_container_iandnot(bitset_container_t *src_1,
const run_container_t *src_2,
container_t **dst) {
*dst = src_1;
for (int32_t rlepos = 0; rlepos < src_2->n_runs; ++rlepos) {
rle16_t rle = src_2->runs[rlepos];
bitset_reset_range(src_1->words, rle.value,
rle.value + rle.length + UINT32_C(1));
}
src_1->cardinality = bitset_container_compute_cardinality(src_1);
if (src_1->cardinality <= DEFAULT_MAX_SIZE) {
*dst = array_container_from_bitset(src_1);
bitset_container_free(src_1);
return false; // not bitset
} else
return true;
}
/* helper. a_out must be a valid array container with adequate capacity.
* Returns the cardinality of the output container. Partly Based on Java
* implementation Util.unsignedDifference.
*
* TODO: Util.unsignedDifference does not use advanceUntil. Is it cheaper
* to avoid advanceUntil?
*/
static int run_array_array_subtract(const run_container_t *rc,
const array_container_t *a_in,
array_container_t *a_out) {
int out_card = 0;
int32_t in_array_pos =
-1; // since advanceUntil always assumes we start the search AFTER this
for (int rlepos = 0; rlepos < rc->n_runs; rlepos++) {
int32_t start = rc->runs[rlepos].value;
int32_t end = start + rc->runs[rlepos].length + 1;
in_array_pos = advanceUntil(a_in->array, in_array_pos,
a_in->cardinality, (uint16_t)start);
if (in_array_pos >= a_in->cardinality) { // run has no items subtracted
for (int32_t i = start; i < end; ++i)
a_out->array[out_card++] = (uint16_t)i;
} else {
uint16_t next_nonincluded = a_in->array[in_array_pos];
if (next_nonincluded >= end) {
// another case when run goes unaltered
for (int32_t i = start; i < end; ++i)
a_out->array[out_card++] = (uint16_t)i;
in_array_pos--; // ensure we see this item again if necessary
} else {
for (int32_t i = start; i < end; ++i)
if (i != next_nonincluded)
a_out->array[out_card++] = (uint16_t)i;
else // 0 should ensure we don't match
next_nonincluded =
(in_array_pos + 1 >= a_in->cardinality)
? 0
: a_in->array[++in_array_pos];
in_array_pos--; // see again
}
}
}
return out_card;
}
/* dst does not indicate a valid container initially. Eventually it
* can become any type of container.
*/
int run_array_container_andnot(const run_container_t *src_1,
const array_container_t *src_2,
container_t **dst) {
// follows the Java impl as of June 2016
int card = run_container_cardinality(src_1);
const int arbitrary_threshold = 32;
if (card <= arbitrary_threshold) {
if (src_2->cardinality == 0) {
*dst = run_container_clone(src_1);
return RUN_CONTAINER_TYPE;
}
// Java's "lazyandNot.toEfficientContainer" thing
run_container_t *answer = run_container_create_given_capacity(
card + array_container_cardinality(src_2));
int rlepos = 0;
int xrlepos = 0; // "x" is src_2
rle16_t rle = src_1->runs[rlepos];
int32_t start = rle.value;
int32_t end = start + rle.length + 1;
int32_t xstart = src_2->array[xrlepos];
while ((rlepos < src_1->n_runs) && (xrlepos < src_2->cardinality)) {
if (end <= xstart) {
// output the first run
answer->runs[answer->n_runs++] =
CROARING_MAKE_RLE16(start, end - start - 1);
rlepos++;
if (rlepos < src_1->n_runs) {
start = src_1->runs[rlepos].value;
end = start + src_1->runs[rlepos].length + 1;
}
} else if (xstart + 1 <= start) {
// exit the second run
xrlepos++;
if (xrlepos < src_2->cardinality) {
xstart = src_2->array[xrlepos];
}
} else {
if (start < xstart) {
answer->runs[answer->n_runs++] =
CROARING_MAKE_RLE16(start, xstart - start - 1);
}
if (xstart + 1 < end) {
start = xstart + 1;
} else {
rlepos++;
if (rlepos < src_1->n_runs) {
start = src_1->runs[rlepos].value;
end = start + src_1->runs[rlepos].length + 1;
}
}
}
}
if (rlepos < src_1->n_runs) {
answer->runs[answer->n_runs++] =
CROARING_MAKE_RLE16(start, end - start - 1);
rlepos++;
if (rlepos < src_1->n_runs) {
memcpy(answer->runs + answer->n_runs, src_1->runs + rlepos,
(src_1->n_runs - rlepos) * sizeof(rle16_t));
answer->n_runs += (src_1->n_runs - rlepos);
}
}
uint8_t return_type;
*dst = convert_run_to_efficient_container(answer, &return_type);
if (answer != *dst) run_container_free(answer);
return return_type;
}
// else it's a bitmap or array
if (card <= DEFAULT_MAX_SIZE) {
array_container_t *ac = array_container_create_given_capacity(card);
// nb Java code used a generic iterator-based merge to compute
// difference
ac->cardinality = run_array_array_subtract(src_1, src_2, ac);
*dst = ac;
return ARRAY_CONTAINER_TYPE;
}
bitset_container_t *ans = bitset_container_from_run(src_1);
bool result_is_bitset = bitset_array_container_iandnot(ans, src_2, dst);
return (result_is_bitset ? BITSET_CONTAINER_TYPE : ARRAY_CONTAINER_TYPE);
}
/* Compute the andnot of src_1 and src_2 and write the result to
* dst (which has no container initially). It will modify src_1
* to be dst if the result is a bitset. Otherwise, it will
* free src_1 and dst will be a new array container. In both
* cases, the caller is responsible for deallocating dst.
* Returns true iff dst is a bitset */
int run_array_container_iandnot(run_container_t *src_1,
const array_container_t *src_2,
container_t **dst) {
// dummy implementation same as June 2016 Java
int ans = run_array_container_andnot(src_1, src_2, dst);
run_container_free(src_1);
return ans;
}
/* dst must be a valid array container, allowed to be src_1 */
void array_run_container_andnot(const array_container_t *src_1,
const run_container_t *src_2,
array_container_t *dst) {
// basically following Java impl as of June 2016
if (src_1->cardinality > dst->capacity) {
array_container_grow(dst, src_1->cardinality, false);
}
if (src_2->n_runs == 0) {
memmove(dst->array, src_1->array,
sizeof(uint16_t) * src_1->cardinality);
dst->cardinality = src_1->cardinality;
return;
}
int32_t run_start = src_2->runs[0].value;
int32_t run_end = run_start + src_2->runs[0].length;
int which_run = 0;
uint16_t val = 0;
int dest_card = 0;
for (int i = 0; i < src_1->cardinality; ++i) {
val = src_1->array[i];
if (val < run_start)
dst->array[dest_card++] = val;
else if (val <= run_end) {
; // omitted item
} else {
do {
if (which_run + 1 < src_2->n_runs) {
++which_run;
run_start = src_2->runs[which_run].value;
run_end = run_start + src_2->runs[which_run].length;
} else
run_start = run_end = (1 << 16) + 1;
} while (val > run_end);
--i;
}
}
dst->cardinality = dest_card;
}
/* dst does not indicate a valid container initially. Eventually it
* can become any kind of container.
*/
void array_run_container_iandnot(array_container_t *src_1,
const run_container_t *src_2) {
array_run_container_andnot(src_1, src_2, src_1);
}
/* dst does not indicate a valid container initially. Eventually it
* can become any kind of container.
*/
int run_run_container_andnot(const run_container_t *src_1,
const run_container_t *src_2, container_t **dst) {
run_container_t *ans = run_container_create();
run_container_andnot(src_1, src_2, ans);
uint8_t typecode_after;
*dst = convert_run_to_efficient_container_and_free(ans, &typecode_after);
return typecode_after;
}
/* Compute the andnot of src_1 and src_2 and write the result to
* dst (which has no container initially). It will modify src_1
* to be dst if the result is a bitset. Otherwise, it will
* free src_1 and dst will be a new array container. In both
* cases, the caller is responsible for deallocating dst.
* Returns true iff dst is a bitset */
int run_run_container_iandnot(run_container_t *src_1,
const run_container_t *src_2, container_t **dst) {
// following Java impl as of June 2016 (dummy)
int ans = run_run_container_andnot(src_1, src_2, dst);
run_container_free(src_1);
return ans;
}
/*
* dst is a valid array container and may be the same as src_1
*/
void array_array_container_andnot(const array_container_t *src_1,
const array_container_t *src_2,
array_container_t *dst) {
array_container_andnot(src_1, src_2, dst);
}
/* inplace array-array andnot will always be able to reuse the space of
* src_1 */
void array_array_container_iandnot(array_container_t *src_1,
const array_container_t *src_2) {
array_container_andnot(src_1, src_2, src_1);
}
/* Compute the andnot of src_1 and src_2 and write the result to
* dst (which has no container initially). Return value is
* "dst is a bitset"
*/
bool bitset_bitset_container_andnot(const bitset_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst) {
bitset_container_t *ans = bitset_container_create();
int card = bitset_container_andnot(src_1, src_2, ans);
if (card <= DEFAULT_MAX_SIZE) {
*dst = array_container_from_bitset(ans);
bitset_container_free(ans);
return false; // not bitset
} else {
*dst = ans;
return true;
}
}
/* Compute the andnot of src_1 and src_2 and write the result to
* dst (which has no container initially). It will modify src_1
* to be dst if the result is a bitset. Otherwise, it will
* free src_1 and dst will be a new array container. In both
* cases, the caller is responsible for deallocating dst.
* Returns true iff dst is a bitset */
bool bitset_bitset_container_iandnot(bitset_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst) {
int card = bitset_container_andnot(src_1, src_2, src_1);
if (card <= DEFAULT_MAX_SIZE) {
*dst = array_container_from_bitset(src_1);
bitset_container_free(src_1);
return false; // not bitset
} else {
*dst = src_1;
return true;
}
}
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
+88
View File
@@ -0,0 +1,88 @@
#include <roaring/containers/mixed_equal.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
bool array_container_equal_bitset(const array_container_t* container1,
const bitset_container_t* container2) {
if (container2->cardinality != BITSET_UNKNOWN_CARDINALITY) {
if (container2->cardinality != container1->cardinality) {
return false;
}
}
int32_t pos = 0;
for (int32_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; ++i) {
uint64_t w = container2->words[i];
while (w != 0) {
uint64_t t = w & (~w + 1);
uint16_t r = i * 64 + roaring_trailing_zeroes(w);
if (pos >= container1->cardinality) {
return false;
}
if (container1->array[pos] != r) {
return false;
}
++pos;
w ^= t;
}
}
return (pos == container1->cardinality);
}
bool run_container_equals_array(const run_container_t* container1,
const array_container_t* container2) {
if (run_container_cardinality(container1) != container2->cardinality)
return false;
int32_t pos = 0;
for (int i = 0; i < container1->n_runs; ++i) {
const uint32_t run_start = container1->runs[i].value;
const uint32_t le = container1->runs[i].length;
if (container2->array[pos] != run_start) {
return false;
}
if (container2->array[pos + le] != run_start + le) {
return false;
}
pos += le + 1;
}
return true;
}
bool run_container_equals_bitset(const run_container_t* container1,
const bitset_container_t* container2) {
int run_card = run_container_cardinality(container1);
int bitset_card = (container2->cardinality != BITSET_UNKNOWN_CARDINALITY)
? container2->cardinality
: bitset_container_compute_cardinality(container2);
if (bitset_card != run_card) {
return false;
}
for (int32_t i = 0; i < container1->n_runs; i++) {
uint32_t begin = container1->runs[i].value;
if (container1->runs[i].length) {
uint32_t end = begin + container1->runs[i].length + 1;
if (!bitset_container_contains_range(container2, begin, end)) {
return false;
}
} else {
if (!bitset_container_contains(container2, begin)) {
return false;
}
}
}
return true;
}
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
+351
View File
@@ -0,0 +1,351 @@
/*
* mixed_intersection.c
*
*/
#include <roaring/array_util.h>
#include <roaring/bitset_util.h>
#include <roaring/containers/convert.h>
#include <roaring/containers/mixed_intersection.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/* Compute the intersection of src_1 and src_2 and write the result to
* dst. */
void array_bitset_container_intersection(const array_container_t *src_1,
const bitset_container_t *src_2,
array_container_t *dst) {
if (dst->capacity < src_1->cardinality) {
array_container_grow(dst, src_1->cardinality, false);
}
int32_t newcard = 0; // dst could be src_1
const int32_t origcard = src_1->cardinality;
for (int i = 0; i < origcard; ++i) {
uint16_t key = src_1->array[i];
// this branchless approach is much faster...
dst->array[newcard] = key;
newcard += bitset_container_contains(src_2, key);
/**
* we could do it this way instead...
* if (bitset_container_contains(src_2, key)) {
* dst->array[newcard++] = key;
* }
* but if the result is unpredictible, the processor generates
* many mispredicted branches.
* Difference can be huge (from 3 cycles when predictible all the way
* to 16 cycles when unpredictible.
* See
* https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/extra/bitset/c/arraybitsetintersection.c
*/
}
dst->cardinality = newcard;
}
/* Compute the size of the intersection of src_1 and src_2. */
int array_bitset_container_intersection_cardinality(
const array_container_t *src_1, const bitset_container_t *src_2) {
int32_t newcard = 0;
const int32_t origcard = src_1->cardinality;
for (int i = 0; i < origcard; ++i) {
uint16_t key = src_1->array[i];
newcard += bitset_container_contains(src_2, key);
}
return newcard;
}
bool array_bitset_container_intersect(const array_container_t *src_1,
const bitset_container_t *src_2) {
const int32_t origcard = src_1->cardinality;
for (int i = 0; i < origcard; ++i) {
uint16_t key = src_1->array[i];
if (bitset_container_contains(src_2, key)) return true;
}
return false;
}
/* Compute the intersection of src_1 and src_2 and write the result to
* dst. It is allowed for dst to be equal to src_1. We assume that dst is a
* valid container. */
void array_run_container_intersection(const array_container_t *src_1,
const run_container_t *src_2,
array_container_t *dst) {
if (run_container_is_full(src_2)) {
if (dst != src_1) array_container_copy(src_1, dst);
return;
}
if (dst->capacity < src_1->cardinality) {
array_container_grow(dst, src_1->cardinality, false);
}
if (src_2->n_runs == 0) {
return;
}
int32_t rlepos = 0;
int32_t arraypos = 0;
rle16_t rle = src_2->runs[rlepos];
int32_t newcard = 0;
while (arraypos < src_1->cardinality) {
const uint16_t arrayval = src_1->array[arraypos];
while (rle.value + rle.length <
arrayval) { // this will frequently be false
++rlepos;
if (rlepos == src_2->n_runs) {
dst->cardinality = newcard;
return; // we are done
}
rle = src_2->runs[rlepos];
}
if (rle.value > arrayval) {
arraypos = advanceUntil(src_1->array, arraypos, src_1->cardinality,
rle.value);
} else {
dst->array[newcard] = arrayval;
newcard++;
arraypos++;
}
}
dst->cardinality = newcard;
}
/* Compute the intersection of src_1 and src_2 and write the result to
* *dst. If the result is true then the result is a bitset_container_t
* otherwise is a array_container_t. If *dst == src_2, an in-place processing
* is attempted.*/
bool run_bitset_container_intersection(const run_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst) {
if (run_container_is_full(src_1)) {
if (*dst != src_2) *dst = bitset_container_clone(src_2);
return true;
}
int32_t card = run_container_cardinality(src_1);
if (card <= DEFAULT_MAX_SIZE) {
// result can only be an array (assuming that we never make a
// RunContainer)
if (card > src_2->cardinality) {
card = src_2->cardinality;
}
array_container_t *answer = array_container_create_given_capacity(card);
*dst = answer;
if (*dst == NULL) {
return false;
}
for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) {
rle16_t rle = src_1->runs[rlepos];
uint32_t endofrun = (uint32_t)rle.value + rle.length;
for (uint32_t runValue = rle.value; runValue <= endofrun;
++runValue) {
answer->array[answer->cardinality] = (uint16_t)runValue;
answer->cardinality +=
bitset_container_contains(src_2, runValue);
}
}
return false;
}
if (*dst == src_2) { // we attempt in-place
bitset_container_t *answer = CAST_bitset(*dst);
uint32_t start = 0;
for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) {
const rle16_t rle = src_1->runs[rlepos];
uint32_t end = rle.value;
bitset_reset_range(src_2->words, start, end);
start = end + rle.length + 1;
}
bitset_reset_range(src_2->words, start, UINT32_C(1) << 16);
answer->cardinality = bitset_container_compute_cardinality(answer);
if (src_2->cardinality > DEFAULT_MAX_SIZE) {
return true;
} else {
array_container_t *newanswer = array_container_from_bitset(src_2);
if (newanswer == NULL) {
*dst = NULL;
return false;
}
*dst = newanswer;
return false;
}
} else { // no inplace
// we expect the answer to be a bitmap (if we are lucky)
bitset_container_t *answer = bitset_container_clone(src_2);
*dst = answer;
if (answer == NULL) {
return true;
}
uint32_t start = 0;
for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) {
const rle16_t rle = src_1->runs[rlepos];
uint32_t end = rle.value;
bitset_reset_range(answer->words, start, end);
start = end + rle.length + 1;
}
bitset_reset_range(answer->words, start, UINT32_C(1) << 16);
answer->cardinality = bitset_container_compute_cardinality(answer);
if (answer->cardinality > DEFAULT_MAX_SIZE) {
return true;
} else {
array_container_t *newanswer = array_container_from_bitset(answer);
bitset_container_free(CAST_bitset(*dst));
if (newanswer == NULL) {
*dst = NULL;
return false;
}
*dst = newanswer;
return false;
}
}
}
/* Compute the size of the intersection between src_1 and src_2 . */
int array_run_container_intersection_cardinality(const array_container_t *src_1,
const run_container_t *src_2) {
if (run_container_is_full(src_2)) {
return src_1->cardinality;
}
if (src_2->n_runs == 0) {
return 0;
}
int32_t rlepos = 0;
int32_t arraypos = 0;
rle16_t rle = src_2->runs[rlepos];
int32_t newcard = 0;
while (arraypos < src_1->cardinality) {
const uint16_t arrayval = src_1->array[arraypos];
while (rle.value + rle.length <
arrayval) { // this will frequently be false
++rlepos;
if (rlepos == src_2->n_runs) {
return newcard; // we are done
}
rle = src_2->runs[rlepos];
}
if (rle.value > arrayval) {
arraypos = advanceUntil(src_1->array, arraypos, src_1->cardinality,
rle.value);
} else {
newcard++;
arraypos++;
}
}
return newcard;
}
/* Compute the intersection between src_1 and src_2
**/
int run_bitset_container_intersection_cardinality(
const run_container_t *src_1, const bitset_container_t *src_2) {
if (run_container_is_full(src_1)) {
return bitset_container_cardinality(src_2);
}
int answer = 0;
for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) {
rle16_t rle = src_1->runs[rlepos];
answer +=
bitset_lenrange_cardinality(src_2->words, rle.value, rle.length);
}
return answer;
}
bool array_run_container_intersect(const array_container_t *src_1,
const run_container_t *src_2) {
if (run_container_is_full(src_2)) {
return !array_container_empty(src_1);
}
if (src_2->n_runs == 0) {
return false;
}
int32_t rlepos = 0;
int32_t arraypos = 0;
rle16_t rle = src_2->runs[rlepos];
while (arraypos < src_1->cardinality) {
const uint16_t arrayval = src_1->array[arraypos];
while (rle.value + rle.length <
arrayval) { // this will frequently be false
++rlepos;
if (rlepos == src_2->n_runs) {
return false; // we are done
}
rle = src_2->runs[rlepos];
}
if (rle.value > arrayval) {
arraypos = advanceUntil(src_1->array, arraypos, src_1->cardinality,
rle.value);
} else {
return true;
}
}
return false;
}
/* Compute the intersection between src_1 and src_2
**/
bool run_bitset_container_intersect(const run_container_t *src_1,
const bitset_container_t *src_2) {
if (run_container_is_full(src_1)) {
return !bitset_container_empty(src_2);
}
for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) {
rle16_t rle = src_1->runs[rlepos];
if (!bitset_lenrange_empty(src_2->words, rle.value, rle.length))
return true;
}
return false;
}
/*
* Compute the intersection between src_1 and src_2 and write the result
* to *dst. If the return function is true, the result is a bitset_container_t
* otherwise is a array_container_t.
*/
bool bitset_bitset_container_intersection(const bitset_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst) {
const int newCardinality = bitset_container_and_justcard(src_1, src_2);
if (newCardinality > DEFAULT_MAX_SIZE) {
*dst = bitset_container_create();
if (*dst != NULL) {
bitset_container_and_nocard(src_1, src_2, CAST_bitset(*dst));
CAST_bitset(*dst)->cardinality = newCardinality;
}
return true; // it is a bitset
}
*dst = array_container_create_given_capacity(newCardinality);
if (*dst != NULL) {
CAST_array(*dst)->cardinality = newCardinality;
bitset_extract_intersection_setbits_uint16(
src_1->words, src_2->words, BITSET_CONTAINER_SIZE_IN_WORDS,
CAST_array(*dst)->array, 0);
}
return false; // not a bitset
}
bool bitset_bitset_container_intersection_inplace(
bitset_container_t *src_1, const bitset_container_t *src_2,
container_t **dst) {
const int newCardinality = bitset_container_and_justcard(src_1, src_2);
if (newCardinality > DEFAULT_MAX_SIZE) {
*dst = src_1;
bitset_container_and_nocard(src_1, src_2, src_1);
CAST_bitset(*dst)->cardinality = newCardinality;
return true; // it is a bitset
}
*dst = array_container_create_given_capacity(newCardinality);
if (*dst != NULL) {
CAST_array(*dst)->cardinality = newCardinality;
bitset_extract_intersection_setbits_uint16(
src_1->words, src_2->words, BITSET_CONTAINER_SIZE_IN_WORDS,
CAST_array(*dst)->array, 0);
}
return false; // not a bitset
}
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
+354
View File
@@ -0,0 +1,354 @@
/*
* mixed_negation.c
*
*/
#include <assert.h>
#include <string.h>
#include <roaring/array_util.h>
#include <roaring/bitset_util.h>
#include <roaring/containers/containers.h>
#include <roaring/containers/convert.h>
#include <roaring/containers/mixed_negation.h>
#include <roaring/containers/run.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
// TODO: make simplified and optimized negation code across
// the full range.
/* Negation across the entire range of the container.
* Compute the negation of src and write the result
* to *dst. The complement of a
* sufficiently sparse set will always be dense and a hence a bitmap
' * We assume that dst is pre-allocated and a valid bitset container
* There can be no in-place version.
*/
void array_container_negation(const array_container_t *src,
bitset_container_t *dst) {
uint64_t card = UINT64_C(1 << 16);
bitset_container_set_all(dst);
if (src->cardinality == 0) {
return;
}
dst->cardinality = (int32_t)bitset_clear_list(dst->words, card, src->array,
(uint64_t)src->cardinality);
}
/* Negation across the entire range of the container
* Compute the negation of src and write the result
* to *dst. A true return value indicates a bitset result,
* otherwise the result is an array container.
* We assume that dst is not pre-allocated. In
* case of failure, *dst will be NULL.
*/
bool bitset_container_negation(const bitset_container_t *src,
container_t **dst) {
return bitset_container_negation_range(src, 0, (1 << 16), dst);
}
/* inplace version */
/*
* Same as bitset_container_negation except that if the output is to
* be a
* bitset_container_t, then src is modified and no allocation is made.
* If the output is to be an array_container_t, then caller is responsible
* to free the container.
* In all cases, the result is in *dst.
*/
bool bitset_container_negation_inplace(bitset_container_t *src,
container_t **dst) {
return bitset_container_negation_range_inplace(src, 0, (1 << 16), dst);
}
/* Negation across the entire range of container
* Compute the negation of src and write the result
* to *dst. Return values are the *_TYPECODES as defined * in containers.h
* We assume that dst is not pre-allocated. In
* case of failure, *dst will be NULL.
*/
int run_container_negation(const run_container_t *src, container_t **dst) {
return run_container_negation_range(src, 0, (1 << 16), dst);
}
/*
* Same as run_container_negation except that if the output is to
* be a
* run_container_t, and has the capacity to hold the result,
* then src is modified and no allocation is made.
* In all cases, the result is in *dst.
*/
int run_container_negation_inplace(run_container_t *src, container_t **dst) {
return run_container_negation_range_inplace(src, 0, (1 << 16), dst);
}
/* Negation across a range of the container.
* Compute the negation of src and write the result
* to *dst. Returns true if the result is a bitset container
* and false for an array container. *dst is not preallocated.
*/
bool array_container_negation_range(const array_container_t *src,
const int range_start, const int range_end,
container_t **dst) {
/* close port of the Java implementation */
if (range_start >= range_end) {
*dst = array_container_clone(src);
return false;
}
int32_t start_index =
binarySearch(src->array, src->cardinality, (uint16_t)range_start);
if (start_index < 0) start_index = -start_index - 1;
int32_t last_index =
binarySearch(src->array, src->cardinality, (uint16_t)(range_end - 1));
if (last_index < 0) last_index = -last_index - 2;
const int32_t current_values_in_range = last_index - start_index + 1;
const int32_t span_to_be_flipped = range_end - range_start;
const int32_t new_values_in_range =
span_to_be_flipped - current_values_in_range;
const int32_t cardinality_change =
new_values_in_range - current_values_in_range;
const int32_t new_cardinality = src->cardinality + cardinality_change;
if (new_cardinality > DEFAULT_MAX_SIZE) {
bitset_container_t *temp = bitset_container_from_array(src);
bitset_flip_range(temp->words, (uint32_t)range_start,
(uint32_t)range_end);
temp->cardinality = new_cardinality;
*dst = temp;
return true;
}
array_container_t *arr =
array_container_create_given_capacity(new_cardinality);
*dst = (container_t *)arr;
if (new_cardinality == 0) {
arr->cardinality = new_cardinality;
return false; // we are done.
}
// copy stuff before the active area
memcpy(arr->array, src->array, start_index * sizeof(uint16_t));
// work on the range
int32_t out_pos = start_index, in_pos = start_index;
int32_t val_in_range = range_start;
for (; val_in_range < range_end && in_pos <= last_index; ++val_in_range) {
if ((uint16_t)val_in_range != src->array[in_pos]) {
arr->array[out_pos++] = (uint16_t)val_in_range;
} else {
++in_pos;
}
}
for (; val_in_range < range_end; ++val_in_range)
arr->array[out_pos++] = (uint16_t)val_in_range;
// content after the active range
memcpy(arr->array + out_pos, src->array + (last_index + 1),
(src->cardinality - (last_index + 1)) * sizeof(uint16_t));
arr->cardinality = new_cardinality;
return false;
}
/* Even when the result would fit, it is unclear how to make an
* inplace version without inefficient copying.
*/
bool array_container_negation_range_inplace(array_container_t *src,
const int range_start,
const int range_end,
container_t **dst) {
bool ans = array_container_negation_range(src, range_start, range_end, dst);
// TODO : try a real inplace version
array_container_free(src);
return ans;
}
/* Negation across a range of the container
* Compute the negation of src and write the result
* to *dst. A true return value indicates a bitset result,
* otherwise the result is an array container.
* We assume that dst is not pre-allocated. In
* case of failure, *dst will be NULL.
*/
bool bitset_container_negation_range(const bitset_container_t *src,
const int range_start, const int range_end,
container_t **dst) {
// TODO maybe consider density-based estimate
// and sometimes build result directly as array, with
// conversion back to bitset if wrong. Or determine
// actual result cardinality, then go directly for the known final cont.
// keep computation using bitsets as long as possible.
bitset_container_t *t = bitset_container_clone(src);
bitset_flip_range(t->words, (uint32_t)range_start, (uint32_t)range_end);
t->cardinality = bitset_container_compute_cardinality(t);
if (t->cardinality > DEFAULT_MAX_SIZE) {
*dst = t;
return true;
} else {
*dst = array_container_from_bitset(t);
bitset_container_free(t);
return false;
}
}
/* inplace version */
/*
* Same as bitset_container_negation except that if the output is to
* be a
* bitset_container_t, then src is modified and no allocation is made.
* If the output is to be an array_container_t, then caller is responsible
* to free the container.
* In all cases, the result is in *dst.
*/
bool bitset_container_negation_range_inplace(bitset_container_t *src,
const int range_start,
const int range_end,
container_t **dst) {
bitset_flip_range(src->words, (uint32_t)range_start, (uint32_t)range_end);
src->cardinality = bitset_container_compute_cardinality(src);
if (src->cardinality > DEFAULT_MAX_SIZE) {
*dst = src;
return true;
}
*dst = array_container_from_bitset(src);
bitset_container_free(src);
return false;
}
/* Negation across a range of container
* Compute the negation of src and write the result
* to *dst. Return values are the *_TYPECODES as defined * in containers.h
* We assume that dst is not pre-allocated. In
* case of failure, *dst will be NULL.
*/
int run_container_negation_range(const run_container_t *src,
const int range_start, const int range_end,
container_t **dst) {
uint8_t return_typecode;
// follows the Java implementation
if (range_end <= range_start) {
*dst = run_container_clone(src);
return RUN_CONTAINER_TYPE;
}
run_container_t *ans = run_container_create_given_capacity(
src->n_runs + 1); // src->n_runs + 1);
int k = 0;
for (; k < src->n_runs && src->runs[k].value < range_start; ++k) {
ans->runs[k] = src->runs[k];
ans->n_runs++;
}
run_container_smart_append_exclusive(
ans, (uint16_t)range_start, (uint16_t)(range_end - range_start - 1));
for (; k < src->n_runs; ++k) {
run_container_smart_append_exclusive(ans, src->runs[k].value,
src->runs[k].length);
}
*dst = convert_run_to_efficient_container(ans, &return_typecode);
if (return_typecode != RUN_CONTAINER_TYPE) run_container_free(ans);
return return_typecode;
}
/*
* Same as run_container_negation except that if the output is to
* be a
* run_container_t, and has the capacity to hold the result,
* then src is modified and no allocation is made.
* In all cases, the result is in *dst.
*/
int run_container_negation_range_inplace(run_container_t *src,
const int range_start,
const int range_end,
container_t **dst) {
uint8_t return_typecode;
if (range_end <= range_start) {
*dst = src;
return RUN_CONTAINER_TYPE;
}
// TODO: efficient special case when range is 0 to 65535 inclusive
if (src->capacity == src->n_runs) {
// no excess room. More checking to see if result can fit
bool last_val_before_range = false;
bool first_val_in_range = false;
bool last_val_in_range = false;
bool first_val_past_range = false;
if (range_start > 0)
last_val_before_range =
run_container_contains(src, (uint16_t)(range_start - 1));
first_val_in_range = run_container_contains(src, (uint16_t)range_start);
if (last_val_before_range == first_val_in_range) {
last_val_in_range =
run_container_contains(src, (uint16_t)(range_end - 1));
if (range_end != 0x10000)
first_val_past_range =
run_container_contains(src, (uint16_t)range_end);
if (last_val_in_range ==
first_val_past_range) { // no space for inplace
int ans = run_container_negation_range(src, range_start,
range_end, dst);
run_container_free(src);
return ans;
}
}
}
// all other cases: result will fit
run_container_t *ans = src;
int my_nbr_runs = src->n_runs;
ans->n_runs = 0;
int k = 0;
for (; (k < my_nbr_runs) && (src->runs[k].value < range_start); ++k) {
// ans->runs[k] = src->runs[k]; (would be self-copy)
ans->n_runs++;
}
// as with Java implementation, use locals to give self a buffer of depth 1
rle16_t buffered = CROARING_MAKE_RLE16(0, 0);
rle16_t next = buffered;
if (k < my_nbr_runs) buffered = src->runs[k];
run_container_smart_append_exclusive(
ans, (uint16_t)range_start, (uint16_t)(range_end - range_start - 1));
for (; k < my_nbr_runs; ++k) {
if (k + 1 < my_nbr_runs) next = src->runs[k + 1];
run_container_smart_append_exclusive(ans, buffered.value,
buffered.length);
buffered = next;
}
*dst = convert_run_to_efficient_container(ans, &return_typecode);
if (return_typecode != RUN_CONTAINER_TYPE) run_container_free(ans);
return return_typecode;
}
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
+145
View File
@@ -0,0 +1,145 @@
#include <roaring/array_util.h>
#include <roaring/containers/mixed_subset.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
bool array_container_is_subset_bitset(const array_container_t* container1,
const bitset_container_t* container2) {
if (container2->cardinality != BITSET_UNKNOWN_CARDINALITY) {
if (container2->cardinality < container1->cardinality) {
return false;
}
}
for (int i = 0; i < container1->cardinality; ++i) {
if (!bitset_container_contains(container2, container1->array[i])) {
return false;
}
}
return true;
}
bool run_container_is_subset_array(const run_container_t* container1,
const array_container_t* container2) {
if (run_container_cardinality(container1) > container2->cardinality)
return false;
int32_t start_pos = -1, stop_pos = -1;
for (int i = 0; i < container1->n_runs; ++i) {
int32_t start = container1->runs[i].value;
int32_t stop = start + container1->runs[i].length;
start_pos = advanceUntil(container2->array, stop_pos,
container2->cardinality, start);
stop_pos = advanceUntil(container2->array, stop_pos,
container2->cardinality, stop);
if (stop_pos == container2->cardinality) {
return false;
} else if (stop_pos - start_pos != stop - start ||
container2->array[start_pos] != start ||
container2->array[stop_pos] != stop) {
return false;
}
}
return true;
}
bool array_container_is_subset_run(const array_container_t* container1,
const run_container_t* container2) {
if (container1->cardinality > run_container_cardinality(container2))
return false;
int i_array = 0, i_run = 0;
while (i_array < container1->cardinality && i_run < container2->n_runs) {
uint32_t start = container2->runs[i_run].value;
uint32_t stop = start + container2->runs[i_run].length;
if (container1->array[i_array] < start) {
return false;
} else if (container1->array[i_array] > stop) {
i_run++;
} else { // the value of the array is in the run
i_array++;
}
}
if (i_array == container1->cardinality) {
return true;
} else {
return false;
}
}
bool run_container_is_subset_bitset(const run_container_t* container1,
const bitset_container_t* container2) {
// todo: this code could be much faster
if (container2->cardinality != BITSET_UNKNOWN_CARDINALITY) {
if (container2->cardinality < run_container_cardinality(container1)) {
return false;
}
} else {
int32_t card = bitset_container_compute_cardinality(
container2); // modify container2?
if (card < run_container_cardinality(container1)) {
return false;
}
}
for (int i = 0; i < container1->n_runs; ++i) {
uint32_t run_start = container1->runs[i].value;
uint32_t le = container1->runs[i].length;
for (uint32_t j = run_start; j <= run_start + le; ++j) {
if (!bitset_container_contains(container2, j)) {
return false;
}
}
}
return true;
}
bool bitset_container_is_subset_run(const bitset_container_t* container1,
const run_container_t* container2) {
// todo: this code could be much faster
if (container1->cardinality != BITSET_UNKNOWN_CARDINALITY) {
if (container1->cardinality > run_container_cardinality(container2)) {
return false;
}
}
int32_t i_bitset = 0, i_run = 0;
while (i_bitset < BITSET_CONTAINER_SIZE_IN_WORDS &&
i_run < container2->n_runs) {
uint64_t w = container1->words[i_bitset];
while (w != 0 && i_run < container2->n_runs) {
uint32_t start = container2->runs[i_run].value;
uint32_t stop = start + container2->runs[i_run].length;
uint64_t t = w & (~w + 1);
uint16_t r = i_bitset * 64 + roaring_trailing_zeroes(w);
if (r < start) {
return false;
} else if (r > stop) {
i_run++;
continue;
} else {
w ^= t;
}
}
if (w == 0) {
i_bitset++;
} else {
return false;
}
}
if (i_bitset < BITSET_CONTAINER_SIZE_IN_WORDS) {
// terminated iterating on the run containers, check that rest of bitset
// is empty
for (; i_bitset < BITSET_CONTAINER_SIZE_IN_WORDS; i_bitset++) {
if (container1->words[i_bitset] != 0) {
return false;
}
}
}
return true;
}
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
+372
View File
@@ -0,0 +1,372 @@
/*
* mixed_union.c
*
*/
#include <assert.h>
#include <string.h>
#include <roaring/bitset_util.h>
#include <roaring/containers/convert.h>
#include <roaring/containers/mixed_union.h>
#include <roaring/containers/perfparameters.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/* Compute the union of src_1 and src_2 and write the result to
* dst. */
void array_bitset_container_union(const array_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst) {
if (src_2 != dst) bitset_container_copy(src_2, dst);
dst->cardinality = (int32_t)bitset_set_list_withcard(
dst->words, dst->cardinality, src_1->array, src_1->cardinality);
}
/* Compute the union of src_1 and src_2 and write the result to
* dst. It is allowed for src_2 to be dst. This version does not
* update the cardinality of dst (it is set to BITSET_UNKNOWN_CARDINALITY). */
void array_bitset_container_lazy_union(const array_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst) {
if (src_2 != dst) bitset_container_copy(src_2, dst);
bitset_set_list(dst->words, src_1->array, src_1->cardinality);
dst->cardinality = BITSET_UNKNOWN_CARDINALITY;
}
void run_bitset_container_union(const run_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst) {
assert(!run_container_is_full(src_1)); // catch this case upstream
if (src_2 != dst) bitset_container_copy(src_2, dst);
for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) {
rle16_t rle = src_1->runs[rlepos];
bitset_set_lenrange(dst->words, rle.value, rle.length);
}
dst->cardinality = bitset_container_compute_cardinality(dst);
}
void run_bitset_container_lazy_union(const run_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst) {
assert(!run_container_is_full(src_1)); // catch this case upstream
if (src_2 != dst) bitset_container_copy(src_2, dst);
for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) {
rle16_t rle = src_1->runs[rlepos];
bitset_set_lenrange(dst->words, rle.value, rle.length);
}
dst->cardinality = BITSET_UNKNOWN_CARDINALITY;
}
// why do we leave the result as a run container??
void array_run_container_union(const array_container_t *src_1,
const run_container_t *src_2,
run_container_t *dst) {
if (run_container_is_full(src_2)) {
run_container_copy(src_2, dst);
return;
}
// TODO: see whether the "2*" is spurious
run_container_grow(dst, 2 * (src_1->cardinality + src_2->n_runs), false);
int32_t rlepos = 0;
int32_t arraypos = 0;
rle16_t previousrle;
if (src_2->runs[rlepos].value <= src_1->array[arraypos]) {
previousrle = run_container_append_first(dst, src_2->runs[rlepos]);
rlepos++;
} else {
previousrle =
run_container_append_value_first(dst, src_1->array[arraypos]);
arraypos++;
}
while ((rlepos < src_2->n_runs) && (arraypos < src_1->cardinality)) {
if (src_2->runs[rlepos].value <= src_1->array[arraypos]) {
run_container_append(dst, src_2->runs[rlepos], &previousrle);
rlepos++;
} else {
run_container_append_value(dst, src_1->array[arraypos],
&previousrle);
arraypos++;
}
}
if (arraypos < src_1->cardinality) {
while (arraypos < src_1->cardinality) {
run_container_append_value(dst, src_1->array[arraypos],
&previousrle);
arraypos++;
}
} else {
while (rlepos < src_2->n_runs) {
run_container_append(dst, src_2->runs[rlepos], &previousrle);
rlepos++;
}
}
}
void array_run_container_inplace_union(const array_container_t *src_1,
run_container_t *src_2) {
if (run_container_is_full(src_2)) {
return;
}
const int32_t maxoutput = src_1->cardinality + src_2->n_runs;
const int32_t neededcapacity = maxoutput + src_2->n_runs;
if (src_2->capacity < neededcapacity)
run_container_grow(src_2, neededcapacity, true);
memmove(src_2->runs + maxoutput, src_2->runs,
src_2->n_runs * sizeof(rle16_t));
rle16_t *inputsrc2 = src_2->runs + maxoutput;
int32_t rlepos = 0;
int32_t arraypos = 0;
int src2nruns = src_2->n_runs;
src_2->n_runs = 0;
rle16_t previousrle;
if (inputsrc2[rlepos].value <= src_1->array[arraypos]) {
previousrle = run_container_append_first(src_2, inputsrc2[rlepos]);
rlepos++;
} else {
previousrle =
run_container_append_value_first(src_2, src_1->array[arraypos]);
arraypos++;
}
while ((rlepos < src2nruns) && (arraypos < src_1->cardinality)) {
if (inputsrc2[rlepos].value <= src_1->array[arraypos]) {
run_container_append(src_2, inputsrc2[rlepos], &previousrle);
rlepos++;
} else {
run_container_append_value(src_2, src_1->array[arraypos],
&previousrle);
arraypos++;
}
}
if (arraypos < src_1->cardinality) {
while (arraypos < src_1->cardinality) {
run_container_append_value(src_2, src_1->array[arraypos],
&previousrle);
arraypos++;
}
} else {
while (rlepos < src2nruns) {
run_container_append(src_2, inputsrc2[rlepos], &previousrle);
rlepos++;
}
}
}
bool array_array_container_union(const array_container_t *src_1,
const array_container_t *src_2,
container_t **dst) {
int totalCardinality = src_1->cardinality + src_2->cardinality;
if (totalCardinality <= DEFAULT_MAX_SIZE) {
*dst = array_container_create_given_capacity(totalCardinality);
if (*dst != NULL) {
array_container_union(src_1, src_2, CAST_array(*dst));
} else {
return true; // otherwise failure won't be caught
}
return false; // not a bitset
}
*dst = bitset_container_create();
bool returnval = true; // expect a bitset
if (*dst != NULL) {
bitset_container_t *ourbitset = CAST_bitset(*dst);
bitset_set_list(ourbitset->words, src_1->array, src_1->cardinality);
ourbitset->cardinality = (int32_t)bitset_set_list_withcard(
ourbitset->words, src_1->cardinality, src_2->array,
src_2->cardinality);
if (ourbitset->cardinality <= DEFAULT_MAX_SIZE) {
// need to convert!
*dst = array_container_from_bitset(ourbitset);
bitset_container_free(ourbitset);
returnval = false; // not going to be a bitset
}
}
return returnval;
}
bool array_array_container_inplace_union(array_container_t *src_1,
const array_container_t *src_2,
container_t **dst) {
int totalCardinality = src_1->cardinality + src_2->cardinality;
*dst = NULL;
if (totalCardinality <= DEFAULT_MAX_SIZE) {
if (src_1->capacity < totalCardinality) {
*dst = array_container_create_given_capacity(
2 * totalCardinality); // be purposefully generous
if (*dst != NULL) {
array_container_union(src_1, src_2, CAST_array(*dst));
} else {
return true; // otherwise failure won't be caught
}
return false; // not a bitset
} else {
memmove(src_1->array + src_2->cardinality, src_1->array,
src_1->cardinality * sizeof(uint16_t));
// In theory, we could use fast_union_uint16, but it is unsafe. It
// fails with Intel compilers in particular.
// https://github.com/RoaringBitmap/CRoaring/pull/452
// See report https://github.com/RoaringBitmap/CRoaring/issues/476
src_1->cardinality = (int32_t)union_uint16(
src_1->array + src_2->cardinality, src_1->cardinality,
src_2->array, src_2->cardinality, src_1->array);
return false; // not a bitset
}
}
*dst = bitset_container_create();
bool returnval = true; // expect a bitset
if (*dst != NULL) {
bitset_container_t *ourbitset = CAST_bitset(*dst);
bitset_set_list(ourbitset->words, src_1->array, src_1->cardinality);
ourbitset->cardinality = (int32_t)bitset_set_list_withcard(
ourbitset->words, src_1->cardinality, src_2->array,
src_2->cardinality);
if (ourbitset->cardinality <= DEFAULT_MAX_SIZE) {
// need to convert!
if (src_1->capacity < ourbitset->cardinality) {
array_container_grow(src_1, ourbitset->cardinality, false);
}
bitset_extract_setbits_uint16(ourbitset->words,
BITSET_CONTAINER_SIZE_IN_WORDS,
src_1->array, 0);
src_1->cardinality = ourbitset->cardinality;
*dst = src_1;
bitset_container_free(ourbitset);
returnval = false; // not going to be a bitset
}
}
return returnval;
}
bool array_array_container_lazy_union(const array_container_t *src_1,
const array_container_t *src_2,
container_t **dst) {
int totalCardinality = src_1->cardinality + src_2->cardinality;
//
// We assume that operations involving bitset containers will be faster than
// operations involving solely array containers, except maybe when array
// containers are small. Indeed, for example, it is cheap to compute the
// union between an array and a bitset container, generally more so than
// between a large array and another array. So it is advantageous to favour
// bitset containers during the computation. Of course, if we convert array
// containers eagerly to bitset containers, we may later need to revert the
// bitset containers to array containerr to satisfy the Roaring format
// requirements, but such one-time conversions at the end may not be overly
// expensive. We arrived to this design based on extensive benchmarking.
//
if (totalCardinality <= ARRAY_LAZY_LOWERBOUND) {
*dst = array_container_create_given_capacity(totalCardinality);
if (*dst != NULL) {
array_container_union(src_1, src_2, CAST_array(*dst));
} else {
return true; // otherwise failure won't be caught
}
return false; // not a bitset
}
*dst = bitset_container_create();
bool returnval = true; // expect a bitset
if (*dst != NULL) {
bitset_container_t *ourbitset = CAST_bitset(*dst);
bitset_set_list(ourbitset->words, src_1->array, src_1->cardinality);
bitset_set_list(ourbitset->words, src_2->array, src_2->cardinality);
ourbitset->cardinality = BITSET_UNKNOWN_CARDINALITY;
}
return returnval;
}
bool array_array_container_lazy_inplace_union(array_container_t *src_1,
const array_container_t *src_2,
container_t **dst) {
int totalCardinality = src_1->cardinality + src_2->cardinality;
*dst = NULL;
//
// We assume that operations involving bitset containers will be faster than
// operations involving solely array containers, except maybe when array
// containers are small. Indeed, for example, it is cheap to compute the
// union between an array and a bitset container, generally more so than
// between a large array and another array. So it is advantageous to favour
// bitset containers during the computation. Of course, if we convert array
// containers eagerly to bitset containers, we may later need to revert the
// bitset containers to array containerr to satisfy the Roaring format
// requirements, but such one-time conversions at the end may not be overly
// expensive. We arrived to this design based on extensive benchmarking.
//
if (totalCardinality <= ARRAY_LAZY_LOWERBOUND) {
if (src_1->capacity < totalCardinality) {
*dst = array_container_create_given_capacity(
2 * totalCardinality); // be purposefully generous
if (*dst != NULL) {
array_container_union(src_1, src_2, CAST_array(*dst));
} else {
return true; // otherwise failure won't be caught
}
return false; // not a bitset
} else {
memmove(src_1->array + src_2->cardinality, src_1->array,
src_1->cardinality * sizeof(uint16_t));
/*
Next line is safe:
We just need to focus on the reading and writing performed on
array1. In `union_vector16`, both vectorized and scalar code still
obey the basic rule: read from two inputs, do the union, and then
write the output.
Let's say the length(cardinality) of input2 is L2:
```
|<- L2 ->|
array1: [output--- |input 1---|---]
array2: [input 2---]
```
Let's define 3 __m128i pointers, `pos1` starts from `input1`,
`pos2` starts from `input2`, these 2 point at the next byte to
read, `out` starts from `output`, pointing at the next byte to
overwrite.
```
array1: [output--- |input 1---|---]
^ ^
out pos1
array2: [input 2---]
^
pos2
```
The union output always contains less or equal number of elements
than all inputs added, so we have:
```
out <= pos1 + pos2
```
therefore:
```
out <= pos1 + L2
```
which means you will not overwrite data beyond pos1, so the data
haven't read is safe, and we don't care the data already read.
*/
src_1->cardinality = (int32_t)fast_union_uint16(
src_1->array + src_2->cardinality, src_1->cardinality,
src_2->array, src_2->cardinality, src_1->array);
return false; // not a bitset
}
}
*dst = bitset_container_create();
bool returnval = true; // expect a bitset
if (*dst != NULL) {
bitset_container_t *ourbitset = CAST_bitset(*dst);
bitset_set_list(ourbitset->words, src_1->array, src_1->cardinality);
bitset_set_list(ourbitset->words, src_2->array, src_2->cardinality);
ourbitset->cardinality = BITSET_UNKNOWN_CARDINALITY;
}
return returnval;
}
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
+385
View File
@@ -0,0 +1,385 @@
/*
* mixed_xor.c
*/
#include <assert.h>
#include <string.h>
#include <roaring/bitset_util.h>
#include <roaring/containers/containers.h>
#include <roaring/containers/convert.h>
#include <roaring/containers/mixed_xor.h>
#include <roaring/containers/perfparameters.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
/* Compute the xor of src_1 and src_2 and write the result to
* dst (which has no container initially).
* Result is true iff dst is a bitset */
bool array_bitset_container_xor(const array_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst) {
bitset_container_t *result = bitset_container_create();
bitset_container_copy(src_2, result);
result->cardinality = (int32_t)bitset_flip_list_withcard(
result->words, result->cardinality, src_1->array, src_1->cardinality);
// do required type conversions.
if (result->cardinality <= DEFAULT_MAX_SIZE) {
*dst = array_container_from_bitset(result);
bitset_container_free(result);
return false; // not bitset
}
*dst = result;
return true; // bitset
}
/* Compute the xor of src_1 and src_2 and write the result to
* dst. It is allowed for src_2 to be dst. This version does not
* update the cardinality of dst (it is set to BITSET_UNKNOWN_CARDINALITY).
*/
void array_bitset_container_lazy_xor(const array_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst) {
if (src_2 != dst) bitset_container_copy(src_2, dst);
bitset_flip_list(dst->words, src_1->array, src_1->cardinality);
dst->cardinality = BITSET_UNKNOWN_CARDINALITY;
}
/* Compute the xor of src_1 and src_2 and write the result to
* dst. Result may be either a bitset or an array container
* (returns "result is bitset"). dst does not initially have
* any container, but becomes either a bitset container (return
* result true) or an array container.
*/
bool run_bitset_container_xor(const run_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst) {
bitset_container_t *result = bitset_container_create();
bitset_container_copy(src_2, result);
for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) {
rle16_t rle = src_1->runs[rlepos];
bitset_flip_range(result->words, rle.value,
rle.value + rle.length + UINT32_C(1));
}
result->cardinality = bitset_container_compute_cardinality(result);
if (result->cardinality <= DEFAULT_MAX_SIZE) {
*dst = array_container_from_bitset(result);
bitset_container_free(result);
return false; // not bitset
}
*dst = result;
return true; // bitset
}
/* lazy xor. Dst is initialized and may be equal to src_2.
* Result is left as a bitset container, even if actual
* cardinality would dictate an array container.
*/
void run_bitset_container_lazy_xor(const run_container_t *src_1,
const bitset_container_t *src_2,
bitset_container_t *dst) {
if (src_2 != dst) bitset_container_copy(src_2, dst);
for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) {
rle16_t rle = src_1->runs[rlepos];
bitset_flip_range(dst->words, rle.value,
rle.value + rle.length + UINT32_C(1));
}
dst->cardinality = BITSET_UNKNOWN_CARDINALITY;
}
/* dst does not indicate a valid container initially. Eventually it
* can become any kind of container.
*/
int array_run_container_xor(const array_container_t *src_1,
const run_container_t *src_2, container_t **dst) {
// semi following Java XOR implementation as of May 2016
// the C OR implementation works quite differently and can return a run
// container
// TODO could optimize for full run containers.
// use of lazy following Java impl.
const int arbitrary_threshold = 32;
if (src_1->cardinality < arbitrary_threshold) {
run_container_t *ans = run_container_create();
array_run_container_lazy_xor(src_1, src_2, ans); // keeps runs.
uint8_t typecode_after;
*dst =
convert_run_to_efficient_container_and_free(ans, &typecode_after);
return typecode_after;
}
int card = run_container_cardinality(src_2);
if (card <= DEFAULT_MAX_SIZE) {
// Java implementation works with the array, xoring the run elements via
// iterator
array_container_t *temp = array_container_from_run(src_2);
bool ret_is_bitset = array_array_container_xor(temp, src_1, dst);
array_container_free(temp);
return ret_is_bitset ? BITSET_CONTAINER_TYPE : ARRAY_CONTAINER_TYPE;
} else { // guess that it will end up as a bitset
bitset_container_t *result = bitset_container_from_run(src_2);
bool is_bitset = bitset_array_container_ixor(result, src_1, dst);
// any necessary type conversion has been done by the ixor
int retval = (is_bitset ? BITSET_CONTAINER_TYPE : ARRAY_CONTAINER_TYPE);
return retval;
}
}
/* Dst is a valid run container. (Can it be src_2? Let's say not.)
* Leaves result as run container, even if other options are
* smaller.
*/
void array_run_container_lazy_xor(const array_container_t *src_1,
const run_container_t *src_2,
run_container_t *dst) {
run_container_grow(dst, src_1->cardinality + src_2->n_runs, false);
int32_t rlepos = 0;
int32_t arraypos = 0;
dst->n_runs = 0;
while ((rlepos < src_2->n_runs) && (arraypos < src_1->cardinality)) {
if (src_2->runs[rlepos].value <= src_1->array[arraypos]) {
run_container_smart_append_exclusive(dst, src_2->runs[rlepos].value,
src_2->runs[rlepos].length);
rlepos++;
} else {
run_container_smart_append_exclusive(dst, src_1->array[arraypos],
0);
arraypos++;
}
}
while (arraypos < src_1->cardinality) {
run_container_smart_append_exclusive(dst, src_1->array[arraypos], 0);
arraypos++;
}
while (rlepos < src_2->n_runs) {
run_container_smart_append_exclusive(dst, src_2->runs[rlepos].value,
src_2->runs[rlepos].length);
rlepos++;
}
}
/* dst does not indicate a valid container initially. Eventually it
* can become any kind of container.
*/
int run_run_container_xor(const run_container_t *src_1,
const run_container_t *src_2, container_t **dst) {
run_container_t *ans = run_container_create();
run_container_xor(src_1, src_2, ans);
uint8_t typecode_after;
*dst = convert_run_to_efficient_container_and_free(ans, &typecode_after);
return typecode_after;
}
/*
* Java implementation (as of May 2016) for array_run, run_run
* and bitset_run don't do anything different for inplace.
* Could adopt the mixed_union.c approach instead (ie, using
* smart_append_exclusive)
*
*/
bool array_array_container_xor(const array_container_t *src_1,
const array_container_t *src_2,
container_t **dst) {
int totalCardinality =
src_1->cardinality + src_2->cardinality; // upper bound
if (totalCardinality <= DEFAULT_MAX_SIZE) {
*dst = array_container_create_given_capacity(totalCardinality);
array_container_xor(src_1, src_2, CAST_array(*dst));
return false; // not a bitset
}
*dst = bitset_container_from_array(src_1);
bool returnval = true; // expect a bitset
bitset_container_t *ourbitset = CAST_bitset(*dst);
ourbitset->cardinality = (uint32_t)bitset_flip_list_withcard(
ourbitset->words, src_1->cardinality, src_2->array, src_2->cardinality);
if (ourbitset->cardinality <= DEFAULT_MAX_SIZE) {
// need to convert!
*dst = array_container_from_bitset(ourbitset);
bitset_container_free(ourbitset);
returnval = false; // not going to be a bitset
}
return returnval;
}
bool array_array_container_lazy_xor(const array_container_t *src_1,
const array_container_t *src_2,
container_t **dst) {
int totalCardinality = src_1->cardinality + src_2->cardinality;
//
// We assume that operations involving bitset containers will be faster than
// operations involving solely array containers, except maybe when array
// containers are small. Indeed, for example, it is cheap to compute the
// exclusive union between an array and a bitset container, generally more
// so than between a large array and another array. So it is advantageous to
// favour bitset containers during the computation. Of course, if we convert
// array containers eagerly to bitset containers, we may later need to
// revert the bitset containers to array containerr to satisfy the Roaring
// format requirements, but such one-time conversions at the end may not be
// overly expensive. We arrived to this design based on extensive
// benchmarking on unions. For XOR/exclusive union, we simply followed the
// heuristic used by the unions (see mixed_union.c). Further tuning is
// possible.
//
if (totalCardinality <= ARRAY_LAZY_LOWERBOUND) {
*dst = array_container_create_given_capacity(totalCardinality);
if (*dst != NULL) array_container_xor(src_1, src_2, CAST_array(*dst));
return false; // not a bitset
}
*dst = bitset_container_from_array(src_1);
bool returnval = true; // expect a bitset (maybe, for XOR??)
if (*dst != NULL) {
bitset_container_t *ourbitset = CAST_bitset(*dst);
bitset_flip_list(ourbitset->words, src_2->array, src_2->cardinality);
ourbitset->cardinality = BITSET_UNKNOWN_CARDINALITY;
}
return returnval;
}
/* Compute the xor of src_1 and src_2 and write the result to
* dst (which has no container initially). Return value is
* "dst is a bitset"
*/
bool bitset_bitset_container_xor(const bitset_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst) {
bitset_container_t *ans = bitset_container_create();
int card = bitset_container_xor(src_1, src_2, ans);
if (card <= DEFAULT_MAX_SIZE) {
*dst = array_container_from_bitset(ans);
bitset_container_free(ans);
return false; // not bitset
} else {
*dst = ans;
return true;
}
}
/* Compute the xor of src_1 and src_2 and write the result to
* dst (which has no container initially). It will modify src_1
* to be dst if the result is a bitset. Otherwise, it will
* free src_1 and dst will be a new array container. In both
* cases, the caller is responsible for deallocating dst.
* Returns true iff dst is a bitset */
bool bitset_array_container_ixor(bitset_container_t *src_1,
const array_container_t *src_2,
container_t **dst) {
*dst = src_1;
src_1->cardinality = (uint32_t)bitset_flip_list_withcard(
src_1->words, src_1->cardinality, src_2->array, src_2->cardinality);
if (src_1->cardinality <= DEFAULT_MAX_SIZE) {
*dst = array_container_from_bitset(src_1);
bitset_container_free(src_1);
return false; // not bitset
} else
return true;
}
/* a bunch of in-place, some of which may not *really* be inplace.
* TODO: write actual inplace routine if efficiency warrants it
* Anything inplace with a bitset is a good candidate
*/
bool bitset_bitset_container_ixor(bitset_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst) {
int card = bitset_container_xor(src_1, src_2, src_1);
if (card <= DEFAULT_MAX_SIZE) {
*dst = array_container_from_bitset(src_1);
bitset_container_free(src_1);
return false; // not bitset
} else {
*dst = src_1;
return true;
}
}
bool array_bitset_container_ixor(array_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst) {
bool ans = array_bitset_container_xor(src_1, src_2, dst);
array_container_free(src_1);
return ans;
}
/* Compute the xor of src_1 and src_2 and write the result to
* dst. Result may be either a bitset or an array container
* (returns "result is bitset"). dst does not initially have
* any container, but becomes either a bitset container (return
* result true) or an array container.
*/
bool run_bitset_container_ixor(run_container_t *src_1,
const bitset_container_t *src_2,
container_t **dst) {
bool ans = run_bitset_container_xor(src_1, src_2, dst);
run_container_free(src_1);
return ans;
}
bool bitset_run_container_ixor(bitset_container_t *src_1,
const run_container_t *src_2,
container_t **dst) {
bool ans = run_bitset_container_xor(src_2, src_1, dst);
bitset_container_free(src_1);
return ans;
}
/* dst does not indicate a valid container initially. Eventually it
* can become any kind of container.
*/
int array_run_container_ixor(array_container_t *src_1,
const run_container_t *src_2, container_t **dst) {
int ans = array_run_container_xor(src_1, src_2, dst);
array_container_free(src_1);
return ans;
}
int run_array_container_ixor(run_container_t *src_1,
const array_container_t *src_2,
container_t **dst) {
int ans = array_run_container_xor(src_2, src_1, dst);
run_container_free(src_1);
return ans;
}
bool array_array_container_ixor(array_container_t *src_1,
const array_container_t *src_2,
container_t **dst) {
bool ans = array_array_container_xor(src_1, src_2, dst);
array_container_free(src_1);
return ans;
}
int run_run_container_ixor(run_container_t *src_1, const run_container_t *src_2,
container_t **dst) {
int ans = run_run_container_xor(src_1, src_2, dst);
run_container_free(src_1);
return ans;
}
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
+1163
View File
File diff suppressed because it is too large. Load diff
+347
View File
@@ -0,0 +1,347 @@
/* From
https://github.com/endorno/pytorch/blob/master/torch/lib/TH/generic/simd/simd.h
Highly modified.
Copyright (c) 2016- Facebook, Inc (Adam Paszke)
Copyright (c) 2014- Facebook, Inc (Soumith Chintala)
Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert)
Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu)
Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu)
Copyright (c) 2011-2013 NYU (Clement Farabet)
Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou,
Iain Melvin, Jason Weston) Copyright (c) 2006 Idiap Research Institute
(Samy Bengio) Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert,
Samy Bengio, Johnny Mariethoz)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the names of Facebook, Deepmind Technologies, NYU, NEC Laboratories
America and IDIAP Research Institute nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
// Binaries produced by Visual Studio 19.38 with solely AVX2 routines
// can compile to AVX-512 thus causing crashes on non-AVX-512 systems.
// This appears to affect VS 17.8 and 17.9. We disable AVX-512 and AVX2
// on these systems. It seems that ClangCL is not affected.
// https://github.com/RoaringBitmap/CRoaring/pull/603
#ifndef __clang__
#if _MSC_VER == 1938
#define ROARING_DISABLE_AVX 1
#endif // _MSC_VER == 1938
#endif // __clang__
#ifdef __FILC__
#include <stdfil.h>
#endif
// We need portability.h to be included first, see
// https://github.com/RoaringBitmap/CRoaring/issues/394
#include <roaring/portability.h>
#if CROARING_REGULAR_VISUAL_STUDIO
#include <intrin.h>
#elif (defined(HAVE_GCC_GET_CPUID) && defined(USE_GCC_GET_CPUID)) || \
defined(__FILC__)
#include <cpuid.h>
#endif // CROARING_REGULAR_VISUAL_STUDIO
#include <roaring/isadetection.h>
#if CROARING_IS_X64
#ifndef CROARING_COMPILER_SUPPORTS_AVX512
#error "CROARING_COMPILER_SUPPORTS_AVX512 needs to be defined."
#endif // CROARING_COMPILER_SUPPORTS_AVX512
#endif
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
enum croaring_instruction_set {
CROARING_DEFAULT = 0x0,
CROARING_NEON = 0x1,
CROARING_AVX2 = 0x4,
CROARING_SSE42 = 0x8,
CROARING_PCLMULQDQ = 0x10,
CROARING_BMI1 = 0x20,
CROARING_BMI2 = 0x40,
CROARING_ALTIVEC = 0x80,
CROARING_AVX512F = 0x100,
CROARING_AVX512DQ = 0x200,
CROARING_AVX512BW = 0x400,
CROARING_AVX512VBMI2 = 0x800,
CROARING_AVX512BITALG = 0x1000,
CROARING_AVX512VPOPCNTDQ = 0x2000,
CROARING_UNINITIALIZED = 0x8000
};
#if CROARING_COMPILER_SUPPORTS_AVX512
unsigned int CROARING_AVX512_REQUIRED =
(CROARING_AVX512F | CROARING_AVX512DQ | CROARING_AVX512BW |
CROARING_AVX512VBMI2 | CROARING_AVX512BITALG | CROARING_AVX512VPOPCNTDQ);
#endif
#if defined(__x86_64__) || defined(_M_AMD64) // x64
static inline void cpuid(uint32_t *eax, uint32_t *ebx, uint32_t *ecx,
uint32_t *edx) {
#if CROARING_REGULAR_VISUAL_STUDIO
int cpu_info[4];
__cpuidex(cpu_info, *eax, *ecx);
*eax = cpu_info[0];
*ebx = cpu_info[1];
*ecx = cpu_info[2];
*edx = cpu_info[3];
#elif (defined(HAVE_GCC_GET_CPUID) && defined(USE_GCC_GET_CPUID)) || \
defined(__FILC__)
uint32_t level = *eax;
__get_cpuid(level, eax, ebx, ecx, edx);
#else
uint32_t a = *eax, b, c = *ecx, d;
__asm__("cpuid\n\t" : "+a"(a), "=b"(b), "+c"(c), "=d"(d));
*eax = a;
*ebx = b;
*ecx = c;
*edx = d;
#endif
}
static inline uint64_t xgetbv(void) {
#if defined(_MSC_VER)
return _xgetbv(0);
#elif defined(__FILC__)
return zxgetbv();
#else
uint32_t xcr0_lo, xcr0_hi;
__asm__("xgetbv\n\t" : "=a"(xcr0_lo), "=d"(xcr0_hi) : "c"(0));
return xcr0_lo | ((uint64_t)xcr0_hi << 32);
#endif
}
/**
* This is a relatively expensive function but it will get called at most
* *once* per compilation units. Normally, the CRoaring library is built
* as one compilation unit.
*/
static inline uint32_t dynamic_croaring_detect_supported_architectures(void) {
uint32_t eax, ebx, ecx, edx;
uint32_t host_isa = 0x0;
// Can be found on Intel ISA Reference for CPUID
static uint32_t cpuid_avx2_bit =
1 << 5; ///< @private Bit 5 of EBX for EAX=0x7
static uint32_t cpuid_bmi1_bit =
1 << 3; ///< @private bit 3 of EBX for EAX=0x7
static uint32_t cpuid_bmi2_bit =
1 << 8; ///< @private bit 8 of EBX for EAX=0x7
static uint32_t cpuid_avx512f_bit =
1 << 16; ///< @private bit 16 of EBX for EAX=0x7
static uint32_t cpuid_avx512dq_bit =
1 << 17; ///< @private bit 17 of EBX for EAX=0x7
static uint32_t cpuid_avx512bw_bit =
1 << 30; ///< @private bit 30 of EBX for EAX=0x7
static uint32_t cpuid_avx512vbmi2_bit =
1 << 6; ///< @private bit 6 of ECX for EAX=0x7
static uint32_t cpuid_avx512bitalg_bit =
1 << 12; ///< @private bit 12 of ECX for EAX=0x7
static uint32_t cpuid_avx512vpopcntdq_bit =
1 << 14; ///< @private bit 14 of ECX for EAX=0x7
static uint64_t cpuid_avx256_saved = 1 << 2; ///< @private bit 2 = AVX
static uint64_t cpuid_avx512_saved =
7 << 5; ///< @private bits 5,6,7 = opmask, ZMM_hi256, hi16_ZMM
static uint32_t cpuid_sse42_bit =
1 << 20; ///< @private bit 20 of ECX for EAX=0x1
static uint32_t cpuid_osxsave =
(1 << 26) | (1 << 27); ///< @private bits 26+27 of ECX for EAX=0x1
static uint32_t cpuid_pclmulqdq_bit =
1 << 1; ///< @private bit 1 of ECX for EAX=0x1
// EBX for EAX=0x1
eax = 0x1;
ecx = 0x0;
cpuid(&eax, &ebx, &ecx, &edx);
if (ecx & cpuid_sse42_bit) {
host_isa |= CROARING_SSE42;
} else {
return host_isa; // everything after is redundant
}
if (ecx & cpuid_pclmulqdq_bit) {
host_isa |= CROARING_PCLMULQDQ;
}
if ((ecx & cpuid_osxsave) != cpuid_osxsave) {
return host_isa;
}
// xgetbv for checking if the OS saves registers
uint64_t xcr0 = xgetbv();
if ((xcr0 & cpuid_avx256_saved) == 0) {
return host_isa;
}
// ECX for EAX=0x7
eax = 0x7;
ecx = 0x0;
cpuid(&eax, &ebx, &ecx, &edx);
if (ebx & cpuid_avx2_bit) {
host_isa |= CROARING_AVX2;
}
if (ebx & cpuid_bmi1_bit) {
host_isa |= CROARING_BMI1;
}
if (ebx & cpuid_bmi2_bit) {
host_isa |= CROARING_BMI2;
}
if (!((xcr0 & cpuid_avx512_saved) == cpuid_avx512_saved)) {
return host_isa;
}
if (ebx & cpuid_avx512f_bit) {
host_isa |= CROARING_AVX512F;
}
if (ebx & cpuid_avx512bw_bit) {
host_isa |= CROARING_AVX512BW;
}
if (ebx & cpuid_avx512dq_bit) {
host_isa |= CROARING_AVX512DQ;
}
if (ecx & cpuid_avx512vbmi2_bit) {
host_isa |= CROARING_AVX512VBMI2;
}
if (ecx & cpuid_avx512bitalg_bit) {
host_isa |= CROARING_AVX512BITALG;
}
if (ecx & cpuid_avx512vpopcntdq_bit) {
host_isa |= CROARING_AVX512VPOPCNTDQ;
}
return host_isa;
}
#endif // end SIMD extension detection code
#if defined(__x86_64__) || defined(_M_AMD64) // x64
#if CROARING_ATOMIC_IMPL == CROARING_ATOMIC_IMPL_CPP
static inline uint32_t croaring_detect_supported_architectures(void) {
// thread-safe as per the C++11 standard.
static uint32_t buffer = dynamic_croaring_detect_supported_architectures();
return buffer;
}
#elif CROARING_ATOMIC_IMPL == CROARING_ATOMIC_IMPL_C
static uint32_t croaring_detect_supported_architectures(void) {
// we use an atomic for thread safety
static _Atomic uint32_t buffer = CROARING_UNINITIALIZED;
if (buffer == CROARING_UNINITIALIZED) {
// atomicity is sufficient
buffer = dynamic_croaring_detect_supported_architectures();
}
return buffer;
}
#else
// If we do not have atomics, we do the best we can.
static inline uint32_t croaring_detect_supported_architectures(void) {
static uint32_t buffer = CROARING_UNINITIALIZED;
if (buffer == CROARING_UNINITIALIZED) {
buffer = dynamic_croaring_detect_supported_architectures();
}
return buffer;
}
#endif // CROARING_C_ATOMIC
#ifdef ROARING_DISABLE_AVX
int croaring_hardware_support(void) { return 0; }
#elif defined(__AVX512F__) && defined(__AVX512DQ__) && \
defined(__AVX512BW__) && defined(__AVX512VBMI2__) && \
defined(__AVX512BITALG__) && defined(__AVX512VPOPCNTDQ__)
int croaring_hardware_support(void) {
return ROARING_SUPPORTS_AVX2 | ROARING_SUPPORTS_AVX512;
}
#elif defined(__AVX2__)
int croaring_hardware_support(void) {
static
#if CROARING_ATOMIC_IMPL == CROARING_ATOMIC_IMPL_C
_Atomic
#endif
int support = 0xFFFFFFF;
if (support == 0xFFFFFFF) {
bool avx512_support = false;
#if CROARING_COMPILER_SUPPORTS_AVX512
avx512_support =
((croaring_detect_supported_architectures() &
CROARING_AVX512_REQUIRED) == CROARING_AVX512_REQUIRED);
#endif
support = ROARING_SUPPORTS_AVX2 |
(avx512_support ? ROARING_SUPPORTS_AVX512 : 0);
}
return support;
}
#else
int croaring_hardware_support(void) {
static
#if CROARING_ATOMIC_IMPL == CROARING_ATOMIC_IMPL_C
_Atomic
#endif
int support = 0xFFFFFFF;
if (support == 0xFFFFFFF) {
bool has_avx2 = (croaring_detect_supported_architectures() &
CROARING_AVX2) == CROARING_AVX2;
bool has_avx512 = false;
#if CROARING_COMPILER_SUPPORTS_AVX512
has_avx512 = (croaring_detect_supported_architectures() &
CROARING_AVX512_REQUIRED) == CROARING_AVX512_REQUIRED;
#endif // CROARING_COMPILER_SUPPORTS_AVX512
support = (has_avx2 ? ROARING_SUPPORTS_AVX2 : 0) |
(has_avx512 ? ROARING_SUPPORTS_AVX512 : 0);
}
return support;
}
#endif
#endif // defined(__x86_64__) || defined(_M_AMD64) // x64
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace internal {
#endif
+52
View File
@@ -0,0 +1,52 @@
/*
* The CRoaring project is under a dual license (Apache/MIT).
* Users of the library may choose one or the other license.
*/
/*
* Copyright 2016-2022 The CRoaring authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* MIT License
*
* Copyright 2016-2022 The CRoaring authors
*
* Permission is hereby granted, free of charge, to any
* person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the
* Software without restriction, including without
* limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice
* shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
* SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
+64
View File
@@ -0,0 +1,64 @@
#include <stdlib.h>
#include <roaring/memory.h>
// without the following, we get lots of warnings about posix_memalign
#ifndef __cplusplus
extern int posix_memalign(void** __memptr, size_t __alignment, size_t __size);
#endif //__cplusplus // C++ does not have a well defined signature
// portable version of posix_memalign
static void* roaring_bitmap_aligned_malloc(size_t alignment, size_t size) {
void* p;
#ifdef _MSC_VER
p = _aligned_malloc(size, alignment);
#elif defined(__MINGW32__) || defined(__MINGW64__)
p = __mingw_aligned_malloc(size, alignment);
#else
// somehow, if this is used before including "x86intrin.h", it creates an
// implicit defined warning.
if (posix_memalign(&p, alignment, size) != 0) return NULL;
#endif
return p;
}
static void roaring_bitmap_aligned_free(void* memblock) {
#ifdef _MSC_VER
_aligned_free(memblock);
#elif defined(__MINGW32__) || defined(__MINGW64__)
__mingw_aligned_free(memblock);
#else
free(memblock);
#endif
}
static roaring_memory_t global_memory_hook = {
.malloc = malloc,
.realloc = realloc,
.calloc = calloc,
.free = free,
.aligned_malloc = roaring_bitmap_aligned_malloc,
.aligned_free = roaring_bitmap_aligned_free,
};
void roaring_init_memory_hook(roaring_memory_t memory_hook) {
global_memory_hook = memory_hook;
}
void* roaring_malloc(size_t n) { return global_memory_hook.malloc(n); }
void* roaring_realloc(void* p, size_t new_sz) {
return global_memory_hook.realloc(p, new_sz);
}
void* roaring_calloc(size_t n_elements, size_t element_size) {
return global_memory_hook.calloc(n_elements, element_size);
}
void roaring_free(void* p) { global_memory_hook.free(p); }
void* roaring_aligned_malloc(size_t alignment, size_t size) {
return global_memory_hook.aligned_malloc(alignment, size);
}
void roaring_aligned_free(void* p) { global_memory_hook.aligned_free(p); }
+3579
View File
File diff suppressed because it is too large. Load diff
+3025
View File
File diff suppressed because it is too large. Load diff
+819
View File
@@ -0,0 +1,819 @@
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <roaring/containers/bitset.h>
#include <roaring/containers/containers.h>
#include <roaring/memory.h>
#include <roaring/roaring_array.h>
#ifdef __cplusplus
extern "C" {
namespace roaring {
namespace internal {
#endif
// Convention: [0,ra->size) all elements are initialized
// [ra->size, ra->allocation_size) is junk and contains nothing needing freeing
extern inline int32_t ra_get_size(const roaring_array_t *ra);
extern inline int32_t ra_get_index(const roaring_array_t *ra, uint16_t x);
extern inline container_t *ra_get_container_at_index(const roaring_array_t *ra,
uint16_t i,
uint8_t *typecode);
extern inline void ra_unshare_container_at_index(roaring_array_t *ra,
uint16_t i);
extern inline void ra_replace_key_and_container_at_index(roaring_array_t *ra,
int32_t i,
uint16_t key,
container_t *c,
uint8_t typecode);
extern inline void ra_set_container_at_index(const roaring_array_t *ra,
int32_t i, container_t *c,
uint8_t typecode);
static bool realloc_array(roaring_array_t *ra, int32_t new_capacity) {
//
// Note: not implemented using C's realloc(), because the memory layout is
// Struct-of-Arrays vs. Array-of-Structs:
// https://github.com/RoaringBitmap/CRoaring/issues/256
if (new_capacity == 0) {
roaring_free(ra->containers);
ra->containers = NULL;
ra->keys = NULL;
ra->typecodes = NULL;
ra->allocation_size = 0;
return true;
}
const size_t memoryneeded =
new_capacity *
(sizeof(uint16_t) + sizeof(container_t *) + sizeof(uint8_t));
void *bigalloc = roaring_malloc(memoryneeded);
if (!bigalloc) return false;
void *oldbigalloc = ra->containers;
container_t **newcontainers = (container_t **)bigalloc;
uint16_t *newkeys = (uint16_t *)(newcontainers + new_capacity);
uint8_t *newtypecodes = (uint8_t *)(newkeys + new_capacity);
assert((char *)(newtypecodes + new_capacity) ==
(char *)bigalloc + memoryneeded);
if (ra->size > 0) {
memcpy(newcontainers, ra->containers, sizeof(container_t *) * ra->size);
memcpy(newkeys, ra->keys, sizeof(uint16_t) * ra->size);
memcpy(newtypecodes, ra->typecodes, sizeof(uint8_t) * ra->size);
}
ra->containers = newcontainers;
ra->keys = newkeys;
ra->typecodes = newtypecodes;
ra->allocation_size = new_capacity;
roaring_free(oldbigalloc);
return true;
}
bool ra_init_with_capacity(roaring_array_t *new_ra, uint32_t cap) {
if (!new_ra) return false;
ra_init(new_ra);
// Containers hold 64Ki elements, so 64Ki containers is enough to hold
// `0x10000 * 0x10000` (all 2^32) elements
if (cap > 0x10000) {
cap = 0x10000;
}
if (cap > 0) {
void *bigalloc = roaring_malloc(
cap * (sizeof(uint16_t) + sizeof(container_t *) + sizeof(uint8_t)));
if (bigalloc == NULL) return false;
new_ra->containers = (container_t **)bigalloc;
new_ra->keys = (uint16_t *)(new_ra->containers + cap);
new_ra->typecodes = (uint8_t *)(new_ra->keys + cap);
// Narrowing is safe because of above check
new_ra->allocation_size = (int32_t)cap;
}
return true;
}
int ra_shrink_to_fit(roaring_array_t *ra) {
int savings = (ra->allocation_size - ra->size) *
(sizeof(uint16_t) + sizeof(container_t *) + sizeof(uint8_t));
if (!realloc_array(ra, ra->size)) {
return 0;
}
ra->allocation_size = ra->size;
return savings;
}
void ra_init(roaring_array_t *new_ra) {
if (!new_ra) {
return;
}
new_ra->keys = NULL;
new_ra->containers = NULL;
new_ra->typecodes = NULL;
new_ra->allocation_size = 0;
new_ra->size = 0;
new_ra->flags = 0;
}
bool ra_overwrite(const roaring_array_t *source, roaring_array_t *dest,
bool copy_on_write) {
ra_clear_containers(dest); // we are going to overwrite them
if (source->size == 0) { // Note: can't call memcpy(NULL), even w/size
dest->size = 0; // <--- This is important.
return true; // output was just cleared, so they match
}
if (dest->allocation_size < source->size) {
if (!realloc_array(dest, source->size)) {
return false;
}
}
dest->size = source->size;
memcpy(dest->keys, source->keys, dest->size * sizeof(uint16_t));
// we go through the containers, turning them into shared containers...
if (copy_on_write) {
for (int32_t i = 0; i < dest->size; ++i) {
source->containers[i] = get_copy_of_container(
source->containers[i], &source->typecodes[i], copy_on_write);
}
// we do a shallow copy to the other bitmap
memcpy(dest->containers, source->containers,
dest->size * sizeof(container_t *));
memcpy(dest->typecodes, source->typecodes,
dest->size * sizeof(uint8_t));
} else {
memcpy(dest->typecodes, source->typecodes,
dest->size * sizeof(uint8_t));
for (int32_t i = 0; i < dest->size; i++) {
dest->containers[i] =
container_clone(source->containers[i], source->typecodes[i]);
if (dest->containers[i] == NULL) {
for (int32_t j = 0; j < i; j++) {
container_free(dest->containers[j], dest->typecodes[j]);
}
ra_clear_without_containers(dest);
return false;
}
}
}
return true;
}
void ra_clear_containers(roaring_array_t *ra) {
for (int32_t i = 0; i < ra->size; ++i) {
container_free(ra->containers[i], ra->typecodes[i]);
}
}
void ra_reset(roaring_array_t *ra) {
ra_clear_containers(ra);
ra->size = 0;
ra_shrink_to_fit(ra);
}
void ra_clear_without_containers(roaring_array_t *ra) {
roaring_free(
ra->containers); // keys and typecodes are allocated with containers
ra->size = 0;
ra->allocation_size = 0;
ra->containers = NULL;
ra->keys = NULL;
ra->typecodes = NULL;
}
void ra_clear(roaring_array_t *ra) {
ra_clear_containers(ra);
ra_clear_without_containers(ra);
}
bool extend_array(roaring_array_t *ra, int32_t k) {
int32_t desired_size = ra->size + k;
const int32_t max_containers = 65536;
assert(desired_size <= max_containers);
if (desired_size > ra->allocation_size) {
int32_t new_capacity =
(ra->size < 1024) ? 2 * desired_size : 5 * desired_size / 4;
if (new_capacity > max_containers) {
new_capacity = max_containers;
}
return realloc_array(ra, new_capacity);
}
return true;
}
void ra_append(roaring_array_t *ra, uint16_t key, container_t *c,
uint8_t typecode) {
extend_array(ra, 1);
const int32_t pos = ra->size;
ra->keys[pos] = key;
ra->containers[pos] = c;
ra->typecodes[pos] = typecode;
ra->size++;
}
void ra_append_copy(roaring_array_t *ra, const roaring_array_t *sa,
uint16_t index, bool copy_on_write) {
extend_array(ra, 1);
const int32_t pos = ra->size;
// old contents is junk that does not need freeing
ra->keys[pos] = sa->keys[index];
// the shared container will be in two bitmaps
if (copy_on_write) {
sa->containers[index] = get_copy_of_container(
sa->containers[index], &sa->typecodes[index], copy_on_write);
ra->containers[pos] = sa->containers[index];
ra->typecodes[pos] = sa->typecodes[index];
} else {
ra->containers[pos] =
container_clone(sa->containers[index], sa->typecodes[index]);
ra->typecodes[pos] = sa->typecodes[index];
}
ra->size++;
}
void ra_append_copies_until(roaring_array_t *ra, const roaring_array_t *sa,
uint16_t stopping_key, bool copy_on_write) {
for (int32_t i = 0; i < sa->size; ++i) {
if (sa->keys[i] >= stopping_key) break;
ra_append_copy(ra, sa, (uint16_t)i, copy_on_write);
}
}
void ra_append_copy_range(roaring_array_t *ra, const roaring_array_t *sa,
int32_t start_index, int32_t end_index,
bool copy_on_write) {
extend_array(ra, end_index - start_index);
for (int32_t i = start_index; i < end_index; ++i) {
const int32_t pos = ra->size;
ra->keys[pos] = sa->keys[i];
if (copy_on_write) {
sa->containers[i] = get_copy_of_container(
sa->containers[i], &sa->typecodes[i], copy_on_write);
ra->containers[pos] = sa->containers[i];
ra->typecodes[pos] = sa->typecodes[i];
} else {
ra->containers[pos] =
container_clone(sa->containers[i], sa->typecodes[i]);
ra->typecodes[pos] = sa->typecodes[i];
}
ra->size++;
}
}
void ra_append_copies_after(roaring_array_t *ra, const roaring_array_t *sa,
uint16_t before_start, bool copy_on_write) {
int start_location = ra_get_index(sa, before_start);
if (start_location >= 0)
++start_location;
else
start_location = -start_location - 1;
ra_append_copy_range(ra, sa, start_location, sa->size, copy_on_write);
}
void ra_append_move_range(roaring_array_t *ra, roaring_array_t *sa,
int32_t start_index, int32_t end_index) {
extend_array(ra, end_index - start_index);
for (int32_t i = start_index; i < end_index; ++i) {
const int32_t pos = ra->size;
ra->keys[pos] = sa->keys[i];
ra->containers[pos] = sa->containers[i];
ra->typecodes[pos] = sa->typecodes[i];
ra->size++;
}
}
void ra_append_range(roaring_array_t *ra, roaring_array_t *sa,
int32_t start_index, int32_t end_index,
bool copy_on_write) {
extend_array(ra, end_index - start_index);
for (int32_t i = start_index; i < end_index; ++i) {
const int32_t pos = ra->size;
ra->keys[pos] = sa->keys[i];
if (copy_on_write) {
sa->containers[i] = get_copy_of_container(
sa->containers[i], &sa->typecodes[i], copy_on_write);
ra->containers[pos] = sa->containers[i];
ra->typecodes[pos] = sa->typecodes[i];
} else {
ra->containers[pos] =
container_clone(sa->containers[i], sa->typecodes[i]);
ra->typecodes[pos] = sa->typecodes[i];
}
ra->size++;
}
}
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);
extern inline uint16_t ra_get_key_at_index(const roaring_array_t *ra,
uint16_t i);
extern inline int32_t ra_get_index(const roaring_array_t *ra, uint16_t x);
extern inline int32_t ra_advance_until(const roaring_array_t *ra, uint16_t x,
int32_t pos);
// everything skipped over is freed
int32_t ra_advance_until_freeing(roaring_array_t *ra, uint16_t x, int32_t pos) {
while (pos < ra->size && ra->keys[pos] < x) {
container_free(ra->containers[pos], ra->typecodes[pos]);
++pos;
}
return pos;
}
void ra_insert_new_key_value_at(roaring_array_t *ra, int32_t i, uint16_t key,
container_t *c, uint8_t typecode) {
extend_array(ra, 1);
// May be an optimization opportunity with DIY memmove
memmove(&(ra->keys[i + 1]), &(ra->keys[i]),
sizeof(uint16_t) * (ra->size - i));
memmove(&(ra->containers[i + 1]), &(ra->containers[i]),
sizeof(container_t *) * (ra->size - i));
memmove(&(ra->typecodes[i + 1]), &(ra->typecodes[i]),
sizeof(uint8_t) * (ra->size - i));
ra->keys[i] = key;
ra->containers[i] = c;
ra->typecodes[i] = typecode;
ra->size++;
}
// note: Java routine set things to 0, enabling GC.
// Java called it "resize" but it was always used to downsize.
// Allowing upsize would break the conventions about
// valid containers below ra->size.
void ra_downsize(roaring_array_t *ra, int32_t new_length) {
assert(new_length <= ra->size);
ra->size = new_length;
}
void ra_remove_at_index(roaring_array_t *ra, int32_t i) {
memmove(&(ra->containers[i]), &(ra->containers[i + 1]),
sizeof(container_t *) * (ra->size - i - 1));
memmove(&(ra->keys[i]), &(ra->keys[i + 1]),
sizeof(uint16_t) * (ra->size - i - 1));
memmove(&(ra->typecodes[i]), &(ra->typecodes[i + 1]),
sizeof(uint8_t) * (ra->size - i - 1));
ra->size--;
}
void ra_remove_at_index_and_free(roaring_array_t *ra, int32_t i) {
container_free(ra->containers[i], ra->typecodes[i]);
ra_remove_at_index(ra, i);
}
// used in inplace andNot only, to slide left the containers from
// the mutated RoaringBitmap that are after the largest container of
// the argument RoaringBitmap. In use it should be followed by a call to
// downsize.
//
void ra_copy_range(roaring_array_t *ra, uint32_t begin, uint32_t end,
uint32_t new_begin) {
assert(begin <= end);
assert(new_begin < begin);
const int range = end - begin;
// We ensure to previously have freed overwritten containers
// that are not copied elsewhere
memmove(&(ra->containers[new_begin]), &(ra->containers[begin]),
sizeof(container_t *) * range);
memmove(&(ra->keys[new_begin]), &(ra->keys[begin]),
sizeof(uint16_t) * range);
memmove(&(ra->typecodes[new_begin]), &(ra->typecodes[begin]),
sizeof(uint8_t) * range);
}
void ra_shift_tail(roaring_array_t *ra, int32_t count, int32_t distance) {
if (distance > 0) {
extend_array(ra, distance);
}
int32_t srcpos = ra->size - count;
int32_t dstpos = srcpos + distance;
memmove(&(ra->keys[dstpos]), &(ra->keys[srcpos]), sizeof(uint16_t) * count);
memmove(&(ra->containers[dstpos]), &(ra->containers[srcpos]),
sizeof(container_t *) * count);
memmove(&(ra->typecodes[dstpos]), &(ra->typecodes[srcpos]),
sizeof(uint8_t) * count);
ra->size += distance;
}
void ra_to_uint32_array(const roaring_array_t *ra, uint32_t *ans) {
size_t ctr = 0;
for (int32_t i = 0; i < ra->size; ++i) {
int num_added = container_to_uint32_array(
ans + ctr, ra->containers[i], ra->typecodes[i],
((uint32_t)ra->keys[i]) << 16);
ctr += num_added;
}
}
bool ra_has_run_container(const roaring_array_t *ra) {
for (int32_t k = 0; k < ra->size; ++k) {
if (get_container_type(ra->containers[k], ra->typecodes[k]) ==
RUN_CONTAINER_TYPE)
return true;
}
return false;
}
uint32_t ra_portable_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; // - 4 because we pack the size with the cookie
} else {
return 4 + 4 + 8 * ra->size;
}
}
size_t ra_portable_size_in_bytes(const roaring_array_t *ra) {
size_t count = ra_portable_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;
}
// The portable serialization format is little-endian. On big-endian hosts we
// byte-swap multi-byte fields before writing them to the buffer.
size_t ra_portable_serialize(const roaring_array_t *ra, char *buf) {
char *initbuf = buf;
uint32_t startOffset = 0;
bool hasrun = ra_has_run_container(ra);
if (hasrun) {
uint32_t cookie = SERIAL_COOKIE | ((uint32_t)(ra->size - 1) << 16);
uint32_t cookie_le = croaring_htole32(cookie);
memcpy(buf, &cookie_le, sizeof(cookie_le));
buf += sizeof(cookie_le);
uint32_t s = (ra->size + 7) / 8;
memset(buf, 0, s);
for (int32_t i = 0; i < ra->size; ++i) {
if (get_container_type(ra->containers[i], ra->typecodes[i]) ==
RUN_CONTAINER_TYPE) {
buf[i / 8] |= 1 << (i % 8);
}
}
buf += s;
if (ra->size < NO_OFFSET_THRESHOLD) {
startOffset = 4 + 4 * ra->size + s;
} else {
startOffset = 4 + 8 * ra->size + s;
}
} else { // backwards compatibility
uint32_t cookie = SERIAL_COOKIE_NO_RUNCONTAINER;
uint32_t cookie_le = croaring_htole32(cookie);
memcpy(buf, &cookie_le, sizeof(cookie_le));
buf += sizeof(cookie_le);
uint32_t size_le = croaring_htole32((uint32_t)ra->size);
memcpy(buf, &size_le, sizeof(size_le));
buf += sizeof(size_le);
startOffset = 4 + 4 + 4 * ra->size + 4 * ra->size;
}
for (int32_t k = 0; k < ra->size; ++k) {
uint16_t key_le = croaring_htole16(ra->keys[k]);
memcpy(buf, &key_le, sizeof(key_le));
buf += sizeof(key_le);
// get_cardinality returns a value in [1,1<<16], subtracting one
// we get [0,1<<16 - 1] which fits in 16 bits
uint16_t card = (uint16_t)(container_get_cardinality(ra->containers[k],
ra->typecodes[k]) -
1);
uint16_t card_le = croaring_htole16(card);
memcpy(buf, &card_le, sizeof(card_le));
buf += sizeof(card_le);
}
if ((!hasrun) || (ra->size >= NO_OFFSET_THRESHOLD)) {
// writing the containers offsets
for (int32_t k = 0; k < ra->size; k++) {
uint32_t off_le = croaring_htole32(startOffset);
memcpy(buf, &off_le, sizeof(off_le));
buf += sizeof(off_le);
startOffset =
startOffset +
container_size_in_bytes(ra->containers[k], ra->typecodes[k]);
}
}
for (int32_t k = 0; k < ra->size; ++k) {
buf += container_write(ra->containers[k], ra->typecodes[k], buf);
}
return buf - initbuf;
}
// Quickly checks whether there is a serialized bitmap at the pointer,
// not exceeding size "maxbytes" in bytes. This function does not allocate
// memory dynamically.
//
// This function returns 0 if and only if no valid bitmap is found.
// Otherwise, it returns how many bytes are occupied.
//
size_t ra_portable_deserialize_size(const char *buf, const size_t maxbytes) {
size_t bytestotal = sizeof(int32_t); // for cookie
if (bytestotal > maxbytes) return 0;
uint32_t cookie;
memcpy(&cookie, buf, sizeof(int32_t));
cookie = croaring_letoh32(cookie);
buf += sizeof(uint32_t);
if ((cookie & 0xFFFF) != SERIAL_COOKIE &&
cookie != SERIAL_COOKIE_NO_RUNCONTAINER) {
return 0;
}
int32_t size;
if ((cookie & 0xFFFF) == SERIAL_COOKIE)
size = (cookie >> 16) + 1;
else {
bytestotal += sizeof(int32_t);
if (bytestotal > maxbytes) return 0;
uint32_t size_le;
memcpy(&size_le, buf, sizeof(int32_t));
size = (int32_t)croaring_letoh32(size_le);
buf += sizeof(uint32_t);
}
if (size > (1 << 16) || size < 0) {
return 0;
}
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;
const char *keyscards = 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 + 4 * k + 2, sizeof(tmp));
tmp = croaring_letoh16(tmp);
uint32_t thiscard = tmp + 1;
bool isbitmap = (thiscard > DEFAULT_MAX_SIZE);
bool isrun = false;
if (hasrun) {
if ((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 = croaring_letoh16(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. If it returns true, readbytes is populated by how many bytes
// were read, we have that *readbytes <= maxbytes.
//
// The portable serialization format is little-endian. On big-endian hosts we
// byte-swap multi-byte fields after reading them from the buffer.
bool ra_portable_deserialize(roaring_array_t *answer, const char *buf,
const size_t maxbytes, size_t *readbytes) {
*readbytes = sizeof(int32_t); // for cookie
if (*readbytes > maxbytes) {
// Ran out of bytes while reading first 4 bytes.
return false;
}
uint32_t cookie;
memcpy(&cookie, buf, sizeof(int32_t));
cookie = croaring_letoh32(cookie);
buf += sizeof(uint32_t);
if ((cookie & 0xFFFF) != SERIAL_COOKIE &&
cookie != SERIAL_COOKIE_NO_RUNCONTAINER) {
// "I failed to find one of the right cookies.
return false;
}
int32_t size;
if ((cookie & 0xFFFF) == SERIAL_COOKIE)
size = (cookie >> 16) + 1;
else {
*readbytes += sizeof(int32_t);
if (*readbytes > maxbytes) {
// Ran out of bytes while reading second part of the cookie.
return false;
}
uint32_t size_le;
memcpy(&size_le, buf, sizeof(int32_t));
size = (int32_t)croaring_letoh32(size_le);
buf += sizeof(uint32_t);
}
if (size < 0) {
// You cannot have a negative number of containers, the data must be
// corrupted.
return false;
}
if (size > (1 << 16)) {
// You cannot have so many containers, the data must be corrupted.
return false;
}
const char *bitmapOfRunContainers = NULL;
bool hasrun = (cookie & 0xFFFF) == SERIAL_COOKIE;
if (hasrun) {
int32_t s = (size + 7) / 8;
*readbytes += s;
if (*readbytes > maxbytes) { // data is corrupted?
// Ran out of bytes while reading run bitmap.
return false;
}
bitmapOfRunContainers = buf;
buf += s;
}
const char *keyscards = buf;
*readbytes += size * 2 * sizeof(uint16_t);
if (*readbytes > maxbytes) {
// Ran out of bytes while reading key-cardinality array.
return false;
}
buf += size * 2 * sizeof(uint16_t);
bool is_ok = ra_init_with_capacity(answer, size);
if (!is_ok) {
// Failed to allocate memory for roaring array. Bailing out.
return false;
}
for (int32_t k = 0; k < size; ++k) {
uint16_t tmp;
memcpy(&tmp, keyscards + 4 * k, sizeof(tmp));
answer->keys[k] = croaring_letoh16(tmp);
}
if ((!hasrun) || (size >= NO_OFFSET_THRESHOLD)) {
*readbytes += size * 4;
if (*readbytes > maxbytes) { // data is corrupted?
// Ran out of bytes while reading offsets.
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;
memcpy(&tmp, keyscards + 4 * k + 2, sizeof(tmp));
tmp = croaring_letoh16(tmp);
uint32_t thiscard = tmp + 1;
bool isbitmap = (thiscard > DEFAULT_MAX_SIZE);
bool isrun = false;
if (hasrun) {
if ((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) {
// Running out of bytes while reading a bitset container.
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
// Failed to allocate memory for a bitset container.
ra_clear(answer); // we need to clear the containers already
// allocated, and the roaring array
return false;
}
answer->size++;
buf += bitset_container_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) {
// Running out of bytes while reading a run container (header).
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 = croaring_letoh16(n_runs);
size_t containersize = n_runs * sizeof(rle16_t);
*readbytes += containersize;
if (*readbytes > maxbytes) { // data is corrupted?
// Running out of bytes while reading a run container.
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
// Failed to allocate memory for a run container.
ra_clear(answer); // we need to clear the containers already
// allocated, and the roaring array
return false;
}
answer->size++;
buf += run_container_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?
// Running out of bytes while reading an array container.
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
// Failed to allocate memory for an array container.
ra_clear(answer); // we need to clear the containers already
// allocated, and the roaring array
return false;
}
answer->size++;
buf += array_container_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
+253
View File
@@ -0,0 +1,253 @@
#include <roaring/roaring.h>
#include <roaring/roaring_array.h>
#ifdef __cplusplus
using namespace ::roaring::internal;
extern "C" {
namespace roaring {
namespace api {
#endif
struct roaring_pq_element_s {
uint64_t size;
bool is_temporary;
roaring_bitmap_t *bitmap;
};
typedef struct roaring_pq_element_s roaring_pq_element_t;
struct roaring_pq_s {
roaring_pq_element_t *elements;
uint64_t size;
};
typedef struct roaring_pq_s roaring_pq_t;
static inline bool compare(roaring_pq_element_t *t1, roaring_pq_element_t *t2) {
return t1->size < t2->size;
}
static void pq_add(roaring_pq_t *pq, roaring_pq_element_t *t) {
uint64_t i = pq->size;
pq->elements[pq->size++] = *t;
while (i > 0) {
uint64_t p = (i - 1) >> 1;
roaring_pq_element_t ap = pq->elements[p];
if (!compare(t, &ap)) break;
pq->elements[i] = ap;
i = p;
}
pq->elements[i] = *t;
}
static void pq_free(roaring_pq_t *pq) { roaring_free(pq); }
static void percolate_down(roaring_pq_t *pq, uint32_t i) {
uint32_t size = (uint32_t)pq->size;
uint32_t hsize = size >> 1;
roaring_pq_element_t ai = pq->elements[i];
while (i < hsize) {
uint32_t l = (i << 1) + 1;
uint32_t r = l + 1;
roaring_pq_element_t bestc = pq->elements[l];
if (r < size) {
if (compare(pq->elements + r, &bestc)) {
l = r;
bestc = pq->elements[r];
}
}
if (!compare(&bestc, &ai)) {
break;
}
pq->elements[i] = bestc;
i = l;
}
pq->elements[i] = ai;
}
static roaring_pq_t *create_pq(const roaring_bitmap_t **arr, uint32_t length) {
size_t alloc_size =
sizeof(roaring_pq_t) + sizeof(roaring_pq_element_t) * length;
roaring_pq_t *answer = (roaring_pq_t *)roaring_malloc(alloc_size);
answer->elements = (roaring_pq_element_t *)(answer + 1);
answer->size = length;
for (uint32_t i = 0; i < length; i++) {
answer->elements[i].bitmap = (roaring_bitmap_t *)arr[i];
answer->elements[i].is_temporary = false;
answer->elements[i].size =
roaring_bitmap_portable_size_in_bytes(arr[i]);
}
for (int32_t i = (length >> 1); i >= 0; i--) {
percolate_down(answer, i);
}
return answer;
}
static roaring_pq_element_t pq_poll(roaring_pq_t *pq) {
roaring_pq_element_t ans = *pq->elements;
if (pq->size > 1) {
pq->elements[0] = pq->elements[--pq->size];
percolate_down(pq, 0);
} else
--pq->size;
// memmove(pq->elements,pq->elements+1,(pq->size-1)*sizeof(roaring_pq_element_t));--pq->size;
return ans;
}
// this function consumes and frees the inputs
static roaring_bitmap_t *lazy_or_from_lazy_inputs(roaring_bitmap_t *x1,
roaring_bitmap_t *x2) {
uint8_t result_type = 0;
const int length1 = ra_get_size(&x1->high_low_container),
length2 = ra_get_size(&x2->high_low_container);
if (0 == length1) {
roaring_bitmap_free(x1);
return x2;
}
if (0 == length2) {
roaring_bitmap_free(x2);
return x1;
}
uint32_t neededcap = length1 > length2 ? length2 : length1;
roaring_bitmap_t *answer = roaring_bitmap_create_with_capacity(neededcap);
int pos1 = 0, pos2 = 0;
uint8_t type1, type2;
uint16_t s1 = ra_get_key_at_index(&x1->high_low_container, (uint16_t)pos1);
uint16_t s2 = ra_get_key_at_index(&x2->high_low_container, (uint16_t)pos2);
while (true) {
if (s1 == s2) {
// todo: unsharing can be inefficient as it may create a clone where
// none
// is needed, but it has the benefit of being easy to reason about.
ra_unshare_container_at_index(&x1->high_low_container,
(uint16_t)pos1);
container_t *c1 = ra_get_container_at_index(&x1->high_low_container,
(uint16_t)pos1, &type1);
assert(type1 != SHARED_CONTAINER_TYPE);
ra_unshare_container_at_index(&x2->high_low_container,
(uint16_t)pos2);
container_t *c2 = ra_get_container_at_index(&x2->high_low_container,
(uint16_t)pos2, &type2);
assert(type2 != SHARED_CONTAINER_TYPE);
container_t *c;
if ((type2 == BITSET_CONTAINER_TYPE) &&
(type1 != BITSET_CONTAINER_TYPE)) {
c = container_lazy_ior(c2, type2, c1, type1, &result_type);
container_free(c1, type1);
if (c != c2) {
container_free(c2, type2);
}
} else {
c = container_lazy_ior(c1, type1, c2, type2, &result_type);
container_free(c2, type2);
if (c != c1) {
container_free(c1, type1);
}
}
// since we assume that the initial containers are non-empty, the
// result here
// can only be non-empty
ra_append(&answer->high_low_container, s1, c, result_type);
++pos1;
++pos2;
if (pos1 == length1) break;
if (pos2 == length2) break;
s1 = ra_get_key_at_index(&x1->high_low_container, (uint16_t)pos1);
s2 = ra_get_key_at_index(&x2->high_low_container, (uint16_t)pos2);
} else if (s1 < s2) { // s1 < s2
container_t *c1 = ra_get_container_at_index(&x1->high_low_container,
(uint16_t)pos1, &type1);
ra_append(&answer->high_low_container, s1, c1, type1);
pos1++;
if (pos1 == length1) break;
s1 = ra_get_key_at_index(&x1->high_low_container, (uint16_t)pos1);
} else { // s1 > s2
container_t *c2 = ra_get_container_at_index(&x2->high_low_container,
(uint16_t)pos2, &type2);
ra_append(&answer->high_low_container, s2, c2, type2);
pos2++;
if (pos2 == length2) break;
s2 = ra_get_key_at_index(&x2->high_low_container, (uint16_t)pos2);
}
}
if (pos1 == length1) {
ra_append_move_range(&answer->high_low_container,
&x2->high_low_container, pos2, length2);
} else if (pos2 == length2) {
ra_append_move_range(&answer->high_low_container,
&x1->high_low_container, pos1, length1);
}
ra_clear_without_containers(&x1->high_low_container);
ra_clear_without_containers(&x2->high_low_container);
roaring_free(x1);
roaring_free(x2);
return answer;
}
/**
* Compute the union of 'number' bitmaps using a heap. This can
* sometimes be faster than roaring_bitmap_or_many which uses
* a naive algorithm. Caller is responsible for freeing the
* result.
*/
roaring_bitmap_t *roaring_bitmap_or_many_heap(uint32_t number,
const roaring_bitmap_t **x) {
if (number == 0) {
return roaring_bitmap_create();
}
if (number == 1) {
return roaring_bitmap_copy(x[0]);
}
roaring_pq_t *pq = create_pq(x, number);
while (pq->size > 1) {
roaring_pq_element_t x1 = pq_poll(pq);
roaring_pq_element_t x2 = pq_poll(pq);
if (x1.is_temporary && x2.is_temporary) {
roaring_bitmap_t *newb =
lazy_or_from_lazy_inputs(x1.bitmap, x2.bitmap);
// should normally return a fresh new bitmap *except* that
// it can return x1.bitmap or x2.bitmap in degenerate cases
bool temporary = !((newb == x1.bitmap) && (newb == x2.bitmap));
uint64_t bsize = roaring_bitmap_portable_size_in_bytes(newb);
roaring_pq_element_t newelement = {
.size = bsize, .is_temporary = temporary, .bitmap = newb};
pq_add(pq, &newelement);
} else if (x2.is_temporary) {
roaring_bitmap_lazy_or_inplace(x2.bitmap, x1.bitmap, false);
x2.size = roaring_bitmap_portable_size_in_bytes(x2.bitmap);
pq_add(pq, &x2);
} else if (x1.is_temporary) {
roaring_bitmap_lazy_or_inplace(x1.bitmap, x2.bitmap, false);
x1.size = roaring_bitmap_portable_size_in_bytes(x1.bitmap);
pq_add(pq, &x1);
} else {
roaring_bitmap_t *newb =
roaring_bitmap_lazy_or(x1.bitmap, x2.bitmap, false);
uint64_t bsize = roaring_bitmap_portable_size_in_bytes(newb);
roaring_pq_element_t newelement = {
.size = bsize, .is_temporary = true, .bitmap = newb};
pq_add(pq, &newelement);
}
}
roaring_pq_element_t X = pq_poll(pq);
roaring_bitmap_t *answer = X.bitmap;
roaring_bitmap_repair_after_lazy(answer);
pq_free(pq);
return answer;
}
#ifdef __cplusplus
}
}
} // extern "C" { namespace roaring { namespace api {
#endif
+17
View File
@@ -0,0 +1,17 @@
#include <roaring/portability.h>
int main(void) {
croaring_refcount_t refcount = 2;
if (croaring_refcount_dec(&refcount)) return 1;
if (croaring_refcount_get(&refcount) != 1) return 2;
croaring_refcount_inc(&refcount);
if (croaring_refcount_get(&refcount) != 2) return 3;
if (croaring_refcount_dec(&refcount)) return 4;
if (!croaring_refcount_dec(&refcount)) return 5;
if (croaring_refcount_get(&refcount) != 0) return 6;
return 0;
}
+74 -1
View File
@@ -2191,6 +2191,7 @@ latency-monitor-threshold 0
# E Keyevent events, published with __keyevent@<db>__ prefix.
# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
# $ String commands
# b Bitmap commands on native bitmaps (string bitmaps use '$')
# l List commands
# s Set commands
# h Hash commands
@@ -2212,7 +2213,7 @@ latency-monitor-threshold 0
# __subkeyspaceitem@<db>__:<key>\n<subkey> prefix.
# V Subkeyspaceevent events, published with
# __subkeyspaceevent@<db>__:<event>|<key> prefix.
# A Alias for g$lshzxetad, so that the "AKE" string means all the events
# A Alias for g$lshzxetdba, so that the "AKE" string means all the events
# except key-miss, new key, overwritten and type-changed.
#
# The "notify-keyspace-events" takes as argument a string that is composed
@@ -2538,6 +2539,78 @@ client-output-buffer-limit pubsub 32mb 8mb 60
#
# proto-max-bulk-len 512mb
# Roaring bitmaps are a distinct, compressed bitmap type backed by
# 64-bit-capable Roaring internals. Their client-visible logical length is
# controlled by proto-max-bulk-len. TYPE reports Roaring bitmaps as "bitmap"
# and generic string commands return WRONGTYPE for them; all bitmap commands
# (SETBIT, GETBIT, BITCOUNT, BITPOS, BITOP, BITFIELD, BITFIELD_RO) work on both
# representations.
#
# Offset semantics across the two representations:
#
# * Bit offsets follow the historical string bitmap rule for both
# representations: commands reject offsets at or beyond proto-max-bulk-len
# times eight (except when applying the replication stream or AOF), and
# bits within that limit but past the end of the value read as 0.
# * BITOP NOT with a Roaring bitmap source may materialize at most 65,536
# missing 8 KiB chunks, or the number of chunks spanned by the longest value
# SETBIT or BITFIELD can create under proto-max-bulk-len when that is larger
# (except when applying the replication stream or AOF). This bounds
# sparse-value allocation amplification while allowing dense values above
# 512 MiB. Plain string sources are exempt because their memory is already
# proportional to their logical length. Other BITOP operations retain sparse
# high-offset support.
#
# This flag controls whether bitmap write commands default to Roaring bitmaps:
#
# * no (the default): bitmap writes keep creating plain strings. Roaring
# bitmaps enter the keyspace when BITOP runs with at least one Roaring bitmap
# source (the destination then stays Roaring), via RESTORE or replication,
# or by loading an RDB or AOF that already contains them.
# * yes: bitmap write commands create new keys as Roaring bitmaps, and a write
# against an existing string value converts it to a Roaring bitmap first.
# Created BITOP destinations are Roaring as well, except for the writes
# described below that keep plain strings.
#
# The flag has no effect while the server is a replica, including for local
# writes on a writable replica (replica-read-only no), so that only the master
# decides the representation of the keys it replicates.
#
# A conversion is an observable in-place type change, not an overwrite. On the
# server executing the bitmap write, the Roaring bitmap is installed first,
# then the conversion emits "type_changed" under the 'c' notification class
# followed, when the write changes the value, by the triggering command's
# normal event under the 'b' bitmap class. It does not emit "overwritten". The transition callback therefore
# observes the bitmap value; RedisModule_KeyType() reports string before
# conversion and bitmap after it. Conversion does not fire module
# key-overwrite/key-unlink or key-metadata unlink callbacks, and preserves the
# key's expiration and module metadata.
#
# Replicas and the AOF never re-derive the representation decision from their
# own configuration: the decision travels in the replication stream as one of
# two internal commands. SETBIT and BITFIELD convert before they write, so a
# write that creates a Roaring bitmap or converts an existing string to one
# replays as the internal BITCONVERT command followed by the original command,
# grouped in a MULTI/EXEC transaction. BITOP computes its result before
# storing it, so a BITOP whose destination is Roaring only because of this
# configuration replays as the internal BITROAROP command, which takes
# the same arguments and stores the result directly as a Roaring bitmap, with
# the same notifications and no transient string value. Commands operating on
# values that are already Roaring bitmaps propagate verbatim.
#
# Automatic Roaring creation/conversion is disabled for writes whose
# propagation excludes either AOF or replicas.
#
# During replay BITCONVERT performs the same in-place conversion described
# above -- same notifications and module callbacks, expiration and module
# metadata preserved -- and creates a missing key as an empty Roaring bitmap
# with the usual "new" event. Replicas and AOF replay targets must be new
# enough to execute the internal BITCONVERT and BITROAROP commands and to
# load Roaring bitmap RDB and DUMP/RESTORE payloads before Roaring bitmap
# creation or conversion is enabled on a master.
#
# bitmap-default-roaring no
# Redis calls an internal function to perform many background tasks, like
# closing connections of clients in timeout, purging expired keys that are
# never requested, and so forth.
+4 -4
View File
@@ -37,7 +37,7 @@ endif
ifneq ($(OPTIMIZATION),-O0)
OPTIMIZATION+=-fno-omit-frame-pointer
endif
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv xxhash tre
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv xxhash tre croaring
NODEPS:=clean distclean
# Default settings
@@ -276,7 +276,7 @@ ifdef OPENSSL_PREFIX
endif
# Include paths to dependencies
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram -I../deps/fpconv -I../deps/xxhash
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram -I../deps/fpconv -I../deps/xxhash -I../deps/croaring/include
# Determine systemd support and/or build preference (defaulting to auto-detection)
BUILD_WITH_SYSTEMD=no
@@ -401,7 +401,7 @@ endif
REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX)
REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX)
REDIS_SERVER_OBJ=threads_mngr.o memory_prefetch.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o entry.o kvstore.o fwtree.o estore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_set_hashtable.o t_set_listpack.o t_set_intset.o t_zset.o t_hash.o bless.o t_array.o sparsearray.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_asm.o cluster_legacy.o cluster_slot_stats.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crccombine.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o lolwut8.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o keymeta.o chk.o hotkeys.o gcra.o vector.o fast_float_strtod.o client_comp.o
REDIS_SERVER_OBJ=threads_mngr.o memory_prefetch.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o entry.o kvstore.o fwtree.o estore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_set_hashtable.o t_set_listpack.o t_set_intset.o t_zset.o t_hash.o bless.o t_array.o sparsearray.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_asm.o cluster_legacy.o cluster_slot_stats.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crccombine.o crc64.o bitops.o bitroar.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o lolwut8.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o keymeta.o chk.o hotkeys.o gcra.o vector.o fast_float_strtod.o client_comp.o
REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX)
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o redisassert.o crcspeed.o crccombine.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o strl.o cli_commands.o
REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX)
@@ -462,7 +462,7 @@ endif
# redis-server
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ) $(REDIS_VEC_SETS_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a ../deps/xxhash/libxxhash.a ../deps/tre/libtre.a $(FINAL_LIBS)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a ../deps/xxhash/libxxhash.a ../deps/tre/libtre.a ../deps/croaring/libroaring.a $(FINAL_LIBS)
# redis-sentinel
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
+18
View File
@@ -2854,6 +2854,22 @@ int rewriteModuleObject(rio *r, robj *key, robj *o, int dbid) {
return io.error ? 0 : 1;
}
int rewriteBitmapObject(rio *r, robj *key, robj *o, int dbid) {
rio payload;
/* KeyMeta is emitted below through its AOF callbacks, so the RESTORE
* payload must not contain another copy. */
createDumpPayload(&payload, o, key, dbid, DUMP_PAYLOAD_SKIP_KEY_META, 0);
int ok = rioWriteBulkCount(r,'*',5) &&
rioWriteBulkString(r,"RESTORE",7) &&
rioWriteBulkObject(r,key) &&
rioWriteBulkString(r,"0",1) &&
rioWriteBulkString(r,payload.io.buffer.ptr,sdslen(payload.io.buffer.ptr)) &&
rioWriteBulkString(r,"REPLACE",7);
sdsfree(payload.io.buffer.ptr);
return ok;
}
static int rewriteFunctions(rio *aof) {
dict *functions = functionsLibGet();
dictIterator iter;
@@ -3009,6 +3025,8 @@ int rewriteObject(rio *r, robj *key, robj *o, int dbid, long long expiretime) {
#endif
} else if (o->type == OBJ_ARRAY) {
if (rewriteArrayObject(r,key,o) == 0) return C_ERR;
} else if (o->type == OBJ_BITMAP) {
if (rewriteBitmapObject(r,key,o,dbid) == 0) return C_ERR;
} else if (o->type == OBJ_MODULE) {
if (rewriteModuleObject(r,key,o,dbid) == 0) return C_ERR;
} else {
+965 -276
View File
File diff suppressed because it is too large. Load diff
+1877
View File
File diff suppressed because it is too large. Load diff
+107
View File
@@ -0,0 +1,107 @@
/*
* Copyright Redis Ltd. 2026 - present
*
* Licensed under your choice of (a) the Redis Source Available License 2.0
* (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
* GNU Affero General Public License v3 (AGPLv3).
*
*
* WHAT IS BITROAR?
* ----------------
* bitroar backs the OBJ_ENCODING_BITMAP_ROARING encoding of bitmap objects
* with a 64-bit Roaring bitmap (CRoaring). It exposes the operations the
* bitmap commands (SETBIT, GETBIT, BITCOUNT, BITPOS, BITFIELD, BITOP, ...)
* and the object machinery (dup, free, defrag, dismiss, RDB persistence)
* need, while keeping the string-bitmap semantics: a logical byte length,
* bit offsets addressed from the most significant bit of byte 0, and
* materialization back to a flat string when required.
*/
#ifndef __BITROAR_H
#define __BITROAR_H
#include "sds.h" /* sds */
#include "object.h" /* robj */
#include <stdint.h>
#include <sys/types.h>
/* Internal representability bound for Roaring bitmap logical length. Command
* handlers enforce the user-configured proto-max-bulk-len limit separately;
* this bound only protects signed range and length arithmetic. */
#define BITROAR_MAX_BYTES_RAW (INT64_MAX >> 3)
#define BITROAR_MAX_BYTES ((uint64_t)BITROAR_MAX_BYTES_RAW)
/* BITOP NOT fills every missing 2^16-bit Roaring chunk in the logical range.
* Bound that allocation amplification rather than the source's byte length:
* dense sources above 512 MiB can remain cheap to complement, while a sparse
* source cannot make one command materialize an unbounded number of chunks.
* Plain strings already occupy memory proportional to their logical length.
* This is a floor: bitroarBitopNotMissingChunkLimit() raises it to cover every
* value clients can create under the current proto-max-bulk-len. */
#define BITROAR_BITOP_NOT_MAX_MISSING_CHUNKS (1ULL << 16)
/* Bitwise operations supported by bitroarApplyOp() (the BITOP command). */
typedef enum bitroarOp {
BITOP_AND = 0,
BITOP_OR,
BITOP_XOR,
BITOP_NOT,
BITOP_DIFF, /* DIFF(X, A1, A2, ..., An) = X & !(A1 | A2 | ... | An) */
BITOP_DIFF1, /* DIFF1(X, A1, A2, ..., An) = !X & (A1 | A2 | ... | An) */
BITOP_ANDOR, /* ANDOR(X, A1, A2, ..., An) = X & (A1 | A2 | ... | An) */
/* ONE(A1, A2, ..., An) = X.
* If X[i] is the i-th bit of X then:
* X[i] == 1 if and only if there is m such that:
* Am[i] == 1 and Al[i] == 0 for all l != m. */
BITOP_ONE
} bitroarOp;
/* Called on invocation, per-bit visitor for bitroarVisitSetBitRanges().
* Reports one maximal run of set bits as [start, end) bit offsets. */
typedef void bitroarRangeCallback(uint64_t start, uint64_t end, void *privdata);
/* Initialization (once at server startup: plugs zmalloc into CRoaring) */
void bitroarInit(void);
/* Object lifecycle */
robj *bitroarCreate(void);
robj *bitroarCreateFromString(const unsigned char *buf, size_t len);
robj *bitroarCreateFromPortable(const unsigned char *buf, size_t len, uint64_t byte_len);
robj *bitroarDup(const robj *o);
void bitroarFree(robj *o);
/* Memory management (fork-child dismissal, active defrag, accounting) */
void bitroarDismiss(robj *o, size_t size_hint);
void bitroarDefrag(robj *o);
unsigned long bitroarDefragIncremental(robj *o, unsigned long cursor);
size_t bitroarAllocSize(const robj *o);
size_t bitroarContainerCount(const robj *o);
size_t bitroarContainerCountUpTo(const robj *o, size_t limit);
/* Read operations */
uint64_t bitroarLen(const robj *o);
uint64_t bitroarCardinality(const robj *o);
uint64_t bitroarRangeCardinality(const robj *o, uint64_t start, uint64_t end);
void bitroarVisitSetBitRanges(const robj *o, bitroarRangeCallback *callback, void *privdata);
long long bitroarBitpos(const robj *o, int bit, uint64_t start, uint64_t end, int end_given);
int bitroarCanRepresentBit(uint64_t bitoffset);
int bitroarGetBit(const robj *o, uint64_t bitoffset);
uint64_t bitroarGetUnsignedBitfield(const robj *o, uint64_t offset, uint64_t bits);
uint64_t bitroarBitopNotMissingChunkLimit(void);
int bitroarBitopNotWithinMissingChunkLimit(const robj *o, uint64_t max_missing);
/* Write operations */
int bitroarSetBit(robj *o, uint64_t bitoffset, int on);
int bitroarSetUnsignedBitfield(robj *o, uint64_t offset, uint64_t bits, uint64_t value);
robj *bitroarApplyOp(bitroarOp op, robj **objects, size_t numkeys, uint64_t maxlen,
int try_alloc);
/* Serialization. MaterializeForDebug flattens to the logical raw string bytes
* for DEBUG BITMAP-RAW and rejects lengths above proto-max-bulk-len.
* SerializePortable emits the RoaringFormatSpec 64-bit portable format, whose
* size tracks resident data rather than the logical length. */
sds bitroarMaterializeForDebug(const robj *o);
sds bitroarSerializePortable(const robj *o);
#endif /* __BITROAR_H */
+4 -4
View File
@@ -3230,15 +3230,15 @@ static void asmTrimJobPopulateDeltaHistograms(kvstore *kvs, void *userdata) {
if (!keysizes_row) continue; /* untracked type, e.g. OBJ_MODULE */
size_t len = getObjectLength(kv);
int size_bin = (len == 0) ? 0 : log2ceil(len) + 1; /* Only strings can be empty */
debugServerAssert(size_bin < MAX_KEYSIZES_BINS);
/* Strings, streams and bitmaps can be empty. */
int size_bin = (len == 0) ? 0 : min(log2ceil(len) + 1, MAX_KEYSIZES_BINS - 1);
keysizes_row[size_bin]++;
if (server.memory_tracking_enabled) {
int64_t *allocsizes_row = keysizesHistRow(trim_job->bg->delta_allocsizes_hist, kv->type);
size_t alloc_size = kvobjAllocSize(kv);
int alloc_bin = (alloc_size == 0) ? 0 : log2ceil(alloc_size) + 1;
debugServerAssert(alloc_bin < MAX_KEYSIZES_BINS);
int alloc_bin = (alloc_size == 0) ? 0 :
min(log2ceil(alloc_size) + 1, MAX_KEYSIZES_BINS - 1);
allocsizes_row[alloc_bin]++;
}
}
+64
View File
@@ -563,6 +563,30 @@ struct COMMAND_ARG ARSET_Args[] = {
{MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
};
/********** BITCONVERT ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* BITCONVERT history */
#define BITCONVERT_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* BITCONVERT tips */
#define BITCONVERT_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* BITCONVERT key specs */
keySpec BITCONVERT_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* BITCONVERT argument table */
struct COMMAND_ARG BITCONVERT_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** BITCOUNT ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
@@ -788,6 +812,44 @@ struct COMMAND_ARG BITPOS_Args[] = {
{MAKE_ARG("range",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=BITPOS_range_Subargs},
};
/********** BITROAROP ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* BITROAROP history */
#define BITROAROP_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* BITROAROP tips */
#define BITROAROP_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* BITROAROP key specs */
keySpec BITROAROP_Keyspecs[2] = {
{NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={3},KSPEC_FK_RANGE,.fk.range={-1,1,0}}
};
#endif
/* BITROAROP operation argument table */
struct COMMAND_ARG BITROAROP_operation_Subargs[] = {
{MAKE_ARG("and",ARG_TYPE_PURE_TOKEN,-1,"AND",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("or",ARG_TYPE_PURE_TOKEN,-1,"OR",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("xor",ARG_TYPE_PURE_TOKEN,-1,"XOR",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("not",ARG_TYPE_PURE_TOKEN,-1,"NOT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("diff",ARG_TYPE_PURE_TOKEN,-1,"DIFF",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("diff1",ARG_TYPE_PURE_TOKEN,-1,"DIFF1",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("andor",ARG_TYPE_PURE_TOKEN,-1,"ANDOR",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("one",ARG_TYPE_PURE_TOKEN,-1,"ONE",NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* BITROAROP argument table */
struct COMMAND_ARG BITROAROP_Args[] = {
{MAKE_ARG("operation",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,8,NULL),.subargs=BITROAROP_operation_Subargs},
{MAKE_ARG("destkey",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("key",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
};
/********** GETBIT ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
@@ -12944,11 +13006,13 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("arseek","Sets the ARINSERT / ARRING cursor to a specific index.","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARSEEK_History,0,ARSEEK_Tips,0,arseekCommand,3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_ARRAY,ARSEEK_Keyspecs,1,NULL,2),.args=ARSEEK_Args},
{MAKE_CMD("arset","Sets one or more contiguous values starting at an index in an array.","O(N) where N is the number of values","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARSET_History,0,ARSET_Tips,0,arsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_ARRAY,ARSET_Keyspecs,1,NULL,3),.args=ARSET_Args},
/* bitmap */
{MAKE_CMD("bitconvert","Converts a string bitmap to the native bitmap representation.","O(N) where N is the length of the string value.","8.12.0",CMD_DOC_SYSCMD,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITCONVERT_History,0,BITCONVERT_Tips,0,bitconvertCommand,2,CMD_WRITE|CMD_DENYOOM|CMD_NOSCRIPT|CMD_INTERNAL,ACL_CATEGORY_BITMAP,BITCONVERT_Keyspecs,1,NULL,1),.args=BITCONVERT_Args},
{MAKE_CMD("bitcount","Counts the number of set bits (population counting) in a string.","O(N)","2.6.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITCOUNT_History,1,BITCOUNT_Tips,0,bitcountCommand,-2,CMD_READONLY,ACL_CATEGORY_BITMAP,BITCOUNT_Keyspecs,1,NULL,2),.args=BITCOUNT_Args},
{MAKE_CMD("bitfield","Performs arbitrary bitfield integer operations on strings.","O(1) for each subcommand specified","3.2.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITFIELD_History,0,BITFIELD_Tips,0,bitfieldCommand,-2,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_BITMAP,BITFIELD_Keyspecs,1,bitfieldGetKeys,2),.args=BITFIELD_Args},
{MAKE_CMD("bitfield_ro","Performs arbitrary read-only bitfield integer operations on strings.","O(1) for each subcommand specified","6.0.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITFIELD_RO_History,0,BITFIELD_RO_Tips,0,bitfieldroCommand,-2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_BITMAP,BITFIELD_RO_Keyspecs,1,NULL,2),.args=BITFIELD_RO_Args},
{MAKE_CMD("bitop","Performs bitwise operations on multiple strings, and stores the result.","O(N)","2.6.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITOP_History,0,BITOP_Tips,0,bitopCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_BITMAP,BITOP_Keyspecs,2,NULL,3),.args=BITOP_Args},
{MAKE_CMD("bitpos","Finds the first set (1) or clear (0) bit in a string.","O(N)","2.8.7",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITPOS_History,1,BITPOS_Tips,0,bitposCommand,-3,CMD_READONLY,ACL_CATEGORY_BITMAP,BITPOS_Keyspecs,1,NULL,3),.args=BITPOS_Args},
{MAKE_CMD("bitroarop","Performs a bitwise operation and stores a native bitmap result for replication and AOF replay.","O(N)","8.12.0",CMD_DOC_SYSCMD,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITROAROP_History,0,BITROAROP_Tips,0,bitroaropCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_NOSCRIPT|CMD_INTERNAL,ACL_CATEGORY_BITMAP,BITROAROP_Keyspecs,2,NULL,3),.args=BITROAROP_Args},
{MAKE_CMD("getbit","Returns a bit value by offset.","O(1)","2.2.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,GETBIT_History,0,GETBIT_Tips,0,getbitCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_BITMAP,GETBIT_Keyspecs,1,NULL,2),.args=GETBIT_Args},
{MAKE_CMD("setbit","Sets or clears the bit at offset of the string value. Creates the key if it doesn't exist.","O(1)","2.2.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,SETBIT_History,0,SETBIT_Tips,0,setbitCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_BITMAP,SETBIT_Keyspecs,1,NULL,3),.args=SETBIT_Args},
/* cluster */
+53
View File
@@ -0,0 +1,53 @@
{
"BITCONVERT": {
"summary": "Converts a string bitmap to the native bitmap representation.",
"complexity": "O(N) where N is the length of the string value.",
"group": "bitmap",
"since": "8.12.0",
"arity": 2,
"function": "bitconvertCommand",
"doc_flags": [
"SYSCMD"
],
"command_flags": [
"WRITE",
"DENYOOM",
"NOSCRIPT",
"INTERNAL"
],
"acl_categories": [
"BITMAP"
],
"key_specs": [
{
"flags": [
"RW",
"ACCESS",
"UPDATE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
}
],
"reply_schema": {
"const": "OK"
}
}
}
+124
View File
@@ -0,0 +1,124 @@
{
"BITROAROP": {
"summary": "Performs a bitwise operation and stores a native bitmap result for replication and AOF replay.",
"complexity": "O(N)",
"group": "bitmap",
"since": "8.12.0",
"arity": -4,
"function": "bitroaropCommand",
"command_flags": [
"WRITE",
"DENYOOM",
"NOSCRIPT",
"INTERNAL"
],
"acl_categories": [
"BITMAP"
],
"key_specs": [
{
"flags": [
"OW",
"UPDATE"
],
"begin_search": {
"index": {
"pos": 2
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
},
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 3
}
},
"find_keys": {
"range": {
"lastkey": -1,
"step": 1,
"limit": 0
}
}
}
],
"arguments": [
{
"name": "operation",
"type": "oneof",
"arguments": [
{
"name": "and",
"type": "pure-token",
"token": "AND"
},
{
"name": "or",
"type": "pure-token",
"token": "OR"
},
{
"name": "xor",
"type": "pure-token",
"token": "XOR"
},
{
"name": "not",
"type": "pure-token",
"token": "NOT"
},
{
"name": "diff",
"type": "pure-token",
"token": "DIFF"
},
{
"name": "diff1",
"type": "pure-token",
"token": "DIFF1"
},
{
"name": "andor",
"type": "pure-token",
"token": "ANDOR"
},
{
"name": "one",
"type": "pure-token",
"token": "ONE"
}
]
},
{
"name": "destkey",
"type": "key",
"key_spec_index": 0
},
{
"name": "key",
"type": "key",
"key_spec_index": 1,
"multiple": true
}
],
"reply_schema": {
"description": "the logical size in bytes of the native bitmap result, equal to the longest input",
"type": "integer",
"minimum": 0
},
"doc_flags": [
"SYSCMD"
]
}
}
+1
View File
@@ -3496,6 +3496,7 @@ standardConfig static_configs[] = {
createEnumConfig("cluster-slot-stats-enabled", NULL, MODIFIABLE_CONFIG | MULTI_ARG_CONFIG, cluster_slot_stats_enum, server.cluster_slot_stats_enabled, 0, NULL, updateMemoryTrackingEnabled),
createBoolConfig("lua-enable-deprecated-api", NULL, IMMUTABLE_CONFIG | HIDDEN_CONFIG, server.lua_enable_deprecated_api, 0, NULL, NULL),
createBoolConfig("key-memory-histograms", NULL, MODIFIABLE_CONFIG, server.key_memory_histograms, 0, NULL, updateMemoryTrackingEnabled),
createBoolConfig("bitmap-default-roaring", NULL, MODIFIABLE_CONFIG, server.bitmap_default_roaring, 0, NULL, NULL),
/* String Configs */
createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.acl_filename, "", NULL, NULL),
+16 -13
View File
@@ -13,6 +13,7 @@
*/
#include "server.h"
#include "bitroar.h"
#include "vector.h"
#include "cluster.h"
#include "atomicvar.h"
@@ -74,6 +75,7 @@ int64_t *keysizesHistRow(keysizesHist hist, uint32_t type) {
case OBJ_ZSET: return hist[KEYSIZES_ROW_ZSET];
case OBJ_HASH: return hist[KEYSIZES_ROW_HASH];
case OBJ_STREAM: return hist[KEYSIZES_ROW_STREAM];
case OBJ_BITMAP: return hist[KEYSIZES_ROW_BITMAP];
default: return NULL;
}
}
@@ -84,16 +86,17 @@ int64_t *keysizesHistRow(keysizesHist hist, uint32_t type) {
* It is used to track the distribution of key sizes in the dataset. It is updated
* every time key's length is modified. Available to user via INFO command.
*
* The histogram is a base-2 logarithmic histogram, with 60 bins. The i'th bin
* represents the number of keys with a size in the range 2^i and 2^(i+1)
* exclusive. oldLen/newLen must be smaller than 2^48, and if their value
* equals -1, it means that the key is being created/deleted, respectively. Each
* data type has its own histogram and it is maintained per database.
* The histogram is a base-2 logarithmic histogram, with MAX_KEYSIZES_BINS bins.
* For positive sizes, bin i represents the number of keys in the range 2^(i-1)
* through 2^i exclusive. Sizes beyond the final bin's range, for any data type,
* saturate into the final bin. A value of -1 for oldLen/newLen
* means that the key is being created/deleted, respectively. Each data type has
* its own histogram and it is maintained per database.
*
* Example mapping of key lengths to bins:
* [1,2)->1 [2,4)->2 [4,8)->3 [8,16)->4 ...
*
* Since strings and streams can be empty (zero length), the histogram also tracks:
* Since strings, streams and bitmaps can be empty (zero length), the histogram also tracks:
* [0,1)->0
*/
void kvsUpdateHistogram(keysizesHist kvstoreHist, uint32_t type, int64_t oldLen, int64_t newLen) {
@@ -102,14 +105,13 @@ void kvsUpdateHistogram(keysizesHist kvstoreHist, uint32_t type, int64_t oldLen,
return;
if (oldLen > 0) {
int old_bin = log2ceil(oldLen) + 1;
debugServerAssert(old_bin < MAX_KEYSIZES_BINS);
int old_bin = min(log2ceil(oldLen) + 1, MAX_KEYSIZES_BINS - 1);
hist[old_bin]--;
debugServerAssert(hist[old_bin] >= 0);
} else {
/* here, oldLen can be either 0 or -1 */
if (oldLen == 0) {
/* Only strings and streams can be empty. Yet, a command flow might
/* Only strings, streams and bitmaps can be empty. Yet, a command flow might
* temporarily dbAdd() empty collection, and only after add elements. */
hist[0]--;
debugServerAssert(hist[0] >= 0);
@@ -117,13 +119,12 @@ void kvsUpdateHistogram(keysizesHist kvstoreHist, uint32_t type, int64_t oldLen,
}
if (newLen > 0) {
int new_bin = log2ceil(newLen) + 1;
debugServerAssert(new_bin < MAX_KEYSIZES_BINS);
int new_bin = min(log2ceil(newLen) + 1, MAX_KEYSIZES_BINS - 1);
hist[new_bin]++;
} else {
/* here, newLen can be either 0 or -1 */
if (newLen == 0) {
/* Only strings and streams can be empty. Yet, a command flow might
/* Only strings, streams and bitmaps can be empty. Yet, a command flow might
* temporarily dbAdd() empty collection, and only after add elements. */
hist[0]++;
}
@@ -171,7 +172,7 @@ static void dbgAssertHist(kvstore *kvs, keysizesHist hist,
int64_t *scanRow = keysizesHistRow(scanHist, kv->type);
if (scanRow) {
int64_t len = fn(kv);
scanRow[(len == 0) ? 0 : log2ceil(len) + 1]++;
scanRow[(len == 0) ? 0 : min(log2ceil(len) + 1, MAX_KEYSIZES_BINS - 1)]++;
}
}
kvstoreIteratorReset(&kvs_it);
@@ -1780,6 +1781,7 @@ char *obj_type_name[OBJ_TYPE_MAX] = {
NULL, /* module type is special */
"stream",
"array",
"bitmap",
#ifdef ENABLE_GCRA
"gcra"
#endif
@@ -2519,6 +2521,7 @@ void copyCommand(client *c) {
if (!newobj) return;
break;
case OBJ_ARRAY: newobj = arrayTypeDup(o); break;
case OBJ_BITMAP: newobj = bitroarDup(o); break;
default:
addReplyError(c, "unknown type object");
return;
+47
View File
@@ -13,6 +13,7 @@
*/
#include "server.h"
#include "bitroar.h"
#include "util.h"
#include "sha1.h" /* SHA1 is used for DEBUG DIGEST */
#include "crc64.h"
@@ -123,6 +124,33 @@ void mixStringObjectDigest(unsigned char *digest, robj *o) {
decrRefCount(o);
}
/* Stream one [start, end) set-bit run into the bitmap SHA1 as two fixed-width
* little-endian integers. */
static void mixBitmapObjectRangeDigest(uint64_t start, uint64_t end, void *privdata) {
SHA1_CTX *ctx = privdata;
uint64_t range[2] = {intrev64ifbe(start), intrev64ifbe(end)};
SHA1Update(ctx, (unsigned char *)range, sizeof(range));
}
void mixBitmapObjectDigest(unsigned char *digest, robj *o) {
/* Digest Roaring bitmaps by logical length plus canonical set-bit ranges.
* This avoids materializing sparse high-offset bitmaps and keeps the
* digest independent from CRoaring's history-dependent container choices.
* All the ranges are streamed into a single SHA1 that is mixed into the
* digest once, so each run costs one 16-byte SHA1Update rather than two
* mixDigest() calls with a SHA1 finalization each. */
SHA1_CTX ctx;
unsigned char hash[20];
uint64_t len = intrev64ifbe(bitroarLen(o));
SHA1Init(&ctx);
SHA1Update(&ctx, (unsigned char *)&len, sizeof(len));
bitroarVisitSetBitRanges(o, mixBitmapObjectRangeDigest, &ctx);
SHA1Final(hash, &ctx);
mixDigest(digest, hash, sizeof(hash));
}
#ifdef ENABLE_GCRA
void mixGCRAObjectDigest(unsigned char *digest, robj *o) {
char buf[LONG_STR_SIZE];
@@ -293,6 +321,8 @@ void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o)
mixDigest(digest, data, vlen);
}
}
} else if (o->type == OBJ_BITMAP) {
mixBitmapObjectDigest(digest, o);
} else {
serverPanic("Unknown object type");
}
@@ -424,6 +454,8 @@ void debugCommand(client *c) {
" Server will sleep before flushing the AOF, this is used for testing.",
"ASSERT",
" Crash by assertion failed.",
"BITMAP-RAW <key>",
" Return the raw byte materialization of a Roaring bitmap key.",
"CHANGE-REPL-ID",
" Change the replication IDs of the instance.",
" Dangerous: should be used only for testing the replication subsystem.",
@@ -982,6 +1014,16 @@ NULL
{
server.skip_checksum_validation = atoi(c->argv[2]->ptr);
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"bitmap-raw") && c->argc == 3) {
kvobj *kv = lookupKeyReadOrReply(c, c->argv[2], shared.nokeyerr);
if (kv == NULL || checkType(c, kv, OBJ_BITMAP)) return;
sds raw = bitroarMaterializeForDebug(kv);
if (raw == NULL) {
addReplyError(c, "bitmap length exceeds proto-max-bulk-len, cannot materialize");
return;
}
addReplyBulkSds(c, raw);
} else if (!strcasecmp(c->argv[1]->ptr,"enable-keymeta-runtime-registration") &&
c->argc == 3)
{
@@ -1348,6 +1390,11 @@ void serverLogObjectDebugInfo(const robj *o) {
serverLog(LL_WARNING,"Object raw string content: %s", repr);
sdsfree(repr);
}
} else if (o->type == OBJ_BITMAP) {
serverLog(LL_WARNING,"Bitmap logical byte length: %llu",
(unsigned long long)bitroarLen(o));
serverLog(LL_WARNING,"Bitmap allocation size: %zu",
bitroarAllocSize(o));
} else if (o->type == OBJ_LIST) {
serverLog(LL_WARNING,"List length: %d", (int) listTypeLength(o));
} else if (o->type == OBJ_SET) {
+16
View File
@@ -19,6 +19,7 @@
*/
#include "server.h"
#include "bitroar.h"
#include <stddef.h>
#include <math.h>
@@ -780,6 +781,17 @@ void defragArray(defragKeysCtx *ctx, kvobj *ob) {
ob->ptr = arDefrag(ob->ptr, activeDefragAlloc);
}
void defragBitmapObject(defragKeysCtx *ctx, kvobj *ob) {
serverAssert(ob->type == OBJ_BITMAP);
/* Only the threshold decision is needed here. Avoid a full main-thread
* ART walk before scheduling a large bitmap for incremental defrag. */
if (bitroarContainerCountUpTo(ob, server.active_defrag_max_scan_fields + 1) >
server.active_defrag_max_scan_fields)
defragLater(ctx, ob);
else
bitroarDefrag(ob);
}
/* Defrag a TMPL_ARRAY hash: small in one shot, large incrementally (like the
* hashtable/set/array paths) so a wide template hash can't stall defrag. */
void defragTmplArray(defragKeysCtx *ctx, kvobj *ob) {
@@ -1244,6 +1256,8 @@ void defragKey(defragKeysCtx *ctx, dictEntry *de, dictEntryLink link) {
defragModule(ctx,db, ob);
} else if (ob->type == OBJ_ARRAY) {
defragArray(ctx, ob);
} else if (ob->type == OBJ_BITMAP) {
defragBitmapObject(ctx, ob);
} else {
serverPanic("Unknown object type");
}
@@ -1366,6 +1380,8 @@ int defragLaterItem(kvobj *ob, unsigned long *cursor, monotime endtime, int dbid
redisArray *ar = ob->ptr;
*cursor = arDefragIncremental(&ar, *cursor, activeDefragAlloc);
ob->ptr = ar;
} else if (ob->type == OBJ_BITMAP) {
*cursor = bitroarDefragIncremental(ob, *cursor);
} else {
*cursor = 0; /* object type/encoding may have changed since we schedule it for later */
}
+17 -7
View File
@@ -1,4 +1,5 @@
#include "server.h"
#include "bitroar.h"
#include "bio.h"
#include "atomicvar.h"
#include "functions.h"
@@ -122,6 +123,13 @@ void lazyfreeResetStats(void) {
atomicSet(lazyfreed_objects,0);
}
/* If there are enough allocations to free the value object asynchronously, it
* may be put into a lazy free list instead of being freed synchronously. The
* lazy free list will be reclaimed in a different bio.c thread. If the value is
* composed of a few allocations, to free in a lazy way is actually just
* slower... So under a certain limit we just free the object synchronously. */
#define LAZYFREE_THRESHOLD 64
/* Return the amount of work needed in order to free an object.
* The return value is not always the actual number of allocations the
* object is composed of, but a number proportional to it.
@@ -185,18 +193,20 @@ size_t lazyfreeGetFreeEffort(robj *key, robj *obj, int dbid) {
} else if (obj->type == OBJ_ARRAY) {
redisArray *ar = obj->ptr;
return arCount(ar);
} else if (obj->type == OBJ_BITMAP) {
/* The caller only compares the returned effort with
* LAZYFREE_THRESHOLD. Stop at the first container count that exceeds
* it instead of walking every leaf of a large sparse bitmap. */
size_t container_limit = (LAZYFREE_THRESHOLD - 2) / 2 + 1;
size_t containers = bitroarContainerCountUpTo(obj, container_limit);
/* Each Roaring container owns at least one allocation for the container
* and often another for its payload, plus top-level bitmap metadata. */
return containers == 0 ? 1 : containers * 2 + 2;
} else {
return 1; /* Everything else is a single allocation. */
}
}
/* If there are enough allocations to free the value object asynchronously, it
* may be put into a lazy free list instead of being freed synchronously. The
* lazy free list will be reclaimed in a different bio.c thread. If the value is
* composed of a few allocations, to free in a lazy way is actually just
* slower... So under a certain limit we just free the object synchronously. */
#define LAZYFREE_THRESHOLD 64
/* Free an object, if the object is huge enough, free it in async way. */
void freeObjAsync(robj *key, robj *obj, int dbid) {
size_t free_effort = lazyfreeGetFreeEffort(key,obj,dbid);
+9
View File
@@ -4296,6 +4296,7 @@ int RM_KeyType(RedisModuleKey *key) {
case OBJ_GCRA: return REDISMODULE_KEYTYPE_GCRA;
#endif
case OBJ_ARRAY: return REDISMODULE_KEYTYPE_ARRAY;
case OBJ_BITMAP: return REDISMODULE_KEYTYPE_BITMAP;
default: return REDISMODULE_KEYTYPE_EMPTY;
}
}
@@ -9327,6 +9328,14 @@ void moduleReleaseGIL(void) {
* - REDISMODULE_NOTIFY_EVICTED: Eviction events
* - REDISMODULE_NOTIFY_STREAM: Stream events
* - REDISMODULE_NOTIFY_MODULE: Module types events
* - REDISMODULE_NOTIFY_BITMAP: Bitmap events (class 'b'). SETBIT, BITFIELD
* and BITOP writes on native bitmap keys are
* reported under this class, while the
* same commands on string bitmaps keep using
* REDISMODULE_NOTIFY_STRING, so a module that
* wants all bitmap writes must subscribe to
* both. REDISMODULE_NOTIFY_ALL from an older
* redismodule.h does not include this flag.
* - REDISMODULE_NOTIFY_KEYMISS: Key-miss events
* Notice, key-miss event is the only type
* of event that is fired from within a read command.
+2
View File
@@ -37,6 +37,7 @@ int keyspaceEventsStringToFlags(char *classes) {
case 't': flags |= NOTIFY_STREAM; break;
case 'm': flags |= NOTIFY_KEY_MISS; break;
case 'd': flags |= NOTIFY_MODULE; break;
case 'b': flags |= NOTIFY_BITMAP; break;
case 'a': flags |= NOTIFY_ARRAY; break;
case 'n': flags |= NOTIFY_NEW; break;
case 'o': flags |= NOTIFY_OVERWRITTEN; break;
@@ -75,6 +76,7 @@ sds keyspaceEventsFlagsToString(int flags) {
if (flags & NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
if (flags & NOTIFY_STREAM) res = sdscatlen(res,"t",1);
if (flags & NOTIFY_MODULE) res = sdscatlen(res,"d",1);
if (flags & NOTIFY_BITMAP) res = sdscatlen(res,"b",1);
if (flags & NOTIFY_ARRAY) res = sdscatlen(res,"a",1);
if (flags & NOTIFY_NEW) res = sdscatlen(res,"n",1);
if (flags & NOTIFY_OVERWRITTEN) res = sdscatlen(res,"o",1);
+18 -2
View File
@@ -12,6 +12,7 @@
*/
#include "server.h"
#include "bitroar.h"
#include "functions.h"
#include "intset.h" /* Compact integer set structure */
#include "cluster_asm.h"
@@ -676,6 +677,7 @@ void decrRefCount(robj *o) {
case OBJ_GCRA: freeGCRAObject(o); break;
#endif
case OBJ_ARRAY: freeArrayObject(o); break;
case OBJ_BITMAP: bitroarFree(o); break;
default: serverPanic("Unknown object type"); break;
}
}
@@ -882,6 +884,7 @@ void dismissObject(robj *o, size_t size_hint) {
case OBJ_GCRA: dismissGCRAObject(o, size_hint); break;
#endif
case OBJ_ARRAY: dismissArrayObject(o, size_hint); break;
case OBJ_BITMAP: bitroarDismiss(o, size_hint); break;
default: break;
}
#else
@@ -898,6 +901,14 @@ int checkType(client *c, robj *o, int type) {
return 0;
}
int checkStringOrBitmapType(client *c, robj *o) {
if (o && o->type != OBJ_STRING && o->type != OBJ_BITMAP) {
addReplyErrorObject(c,shared.wrongtypeerr);
return 1;
}
return 0;
}
int isSdsRepresentableAsLongLong(sds s, long long *llval) {
return string2ll(s,sdslen(s),llval) ? C_OK : C_ERR;
}
@@ -1007,6 +1018,7 @@ size_t getObjectLength(robj *o) {
case OBJ_GCRA: return gcraObjectLength(o);
#endif
case OBJ_ARRAY: return arCount(o->ptr);
case OBJ_BITMAP: return bitroarLen(o);
default: return 0;
}
}
@@ -1307,8 +1319,9 @@ char *strEncoding(int encoding) {
case OBJ_ENCODING_EMBSTR: return "embstr";
case OBJ_ENCODING_STREAM: return "stream";
case OBJ_ENCODING_SLICED_ARRAY: return "sliced-array";
case OBJ_ENCODING_TMPL_LP: return "template-listpack";
case OBJ_ENCODING_TMPL_ARRAY: return "template-array";
case OBJ_ENCODING_TMPL_LP: return "template-listpack";
case OBJ_ENCODING_TMPL_ARRAY: return "template-array";
case OBJ_ENCODING_BITMAP_ROARING: return "bitmap-roaring";
default: return "unknown";
}
}
@@ -1330,6 +1343,7 @@ size_t kvobjComputeSize(robj *key, kvobj *o, size_t sample_size, int dbid) {
#ifdef ENABLE_GCRA
o->type == OBJ_GCRA ||
#endif
o->type == OBJ_BITMAP ||
o->type == OBJ_ARRAY)
{
return kvobjAllocSize(o);
@@ -1376,6 +1390,8 @@ size_t kvobjAllocSize(kvobj *o) {
} else if (o->type == OBJ_ARRAY) {
redisArray *ar = o->ptr;
asize += ar->alloc_size;
} else if (o->type == OBJ_BITMAP) {
asize += bitroarAllocSize(o);
} else if (o->type == OBJ_MODULE) {
/* TODO: Provide moduleGetAllocSize() module API for O(1) allocation size retrieval */
}
+4 -2
View File
@@ -89,20 +89,21 @@ struct RedisModuleType;
#define OBJ_ENCODING_SLICED_ARRAY 13 /* Encoded as sliced array */
#define OBJ_ENCODING_TMPL_LP 14 /* Hash with shared template, values in listpack */
#define OBJ_ENCODING_TMPL_ARRAY 15 /* Hash with shared template, values in sds array */
#define OBJ_ENCODING_BITMAP_ROARING 16 /* Bitmap encoded as a Roaring bitmap. */
#define LRU_BITS 24
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
#define OBJ_NUM_KVMETA_BITS 8
#define OBJ_REFCOUNT_BITS 23
#define OBJ_REFCOUNT_BITS 22
#define OBJ_SHARED_REFCOUNT ((1 << OBJ_REFCOUNT_BITS) - 1) /* Global object never destroyed. */
#define OBJ_STATIC_REFCOUNT ((1 << OBJ_REFCOUNT_BITS) - 2) /* Object allocated in the stack. */
#define OBJ_FIRST_SPECIAL_REFCOUNT OBJ_STATIC_REFCOUNT
struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned encoding:5;
unsigned refcount : OBJ_REFCOUNT_BITS;
unsigned iskvobj : 1; /* 1 if this struct serves as a kvobj base */
@@ -188,6 +189,7 @@ int getLongFromObjectOrReply(struct client *c, robj *o, long *target, const char
int getPositiveLongFromObjectOrReply(struct client *c, robj *o, long *target, const char *msg);
int getRangeLongFromObjectOrReply(struct client *c, robj *o, long min, long max, long *target, const char *msg);
int checkType(struct client *c, robj *o, int type);
int checkStringOrBitmapType(struct client *c, robj *o);
int getLongLongFromObjectOrReply(struct client *c, robj *o, long long *target, const char *msg);
int getDoubleFromObjectOrReply(struct client *c, robj *o, double *target, const char *msg);
int getDoubleFromObject(const robj *o, double *target);
+71
View File
@@ -13,6 +13,7 @@
*/
#include "server.h"
#include "bitroar.h"
#include "lzf.h" /* LZF compression library */
#include "zipmap.h"
#include "endianconv.h"
@@ -752,6 +753,8 @@ int rdbSaveObjectType(rio *rdb, robj *o) {
return rdbSaveType(rdb,RDB_TYPE_MODULE_2);
case OBJ_ARRAY:
return rdbSaveType(rdb,RDB_TYPE_ARRAY);
case OBJ_BITMAP:
return rdbSaveType(rdb,RDB_TYPE_BITMAP);
default:
serverPanic("Unknown object type");
}
@@ -1136,6 +1139,69 @@ static ssize_t rdbSaveArraySlice(rio *rdb, arSlice *s, uint64_t slice_id,
return nwritten;
}
/* OBJ_BITMAP persistence (RDB_TYPE_BITMAP): the bitmap's logical byte
* length, then an RDB string holding CRoaring's 64-bit portable
* serialization (RoaringFormatSpec, canonical little-endian fields on every
* architecture). The logical length travels separately because trailing
* zero bits are observable through bitmap commands but are not represented
* by Roaring containers. The payload size is proportional to the resident
* containers, never to the highest set bit, and the same format is shared
* by RDB snapshots, DUMP/RESTORE, and the RDB payloads used by AOF. */
static ssize_t rdbSaveBitmapObject(rio *rdb, const robj *o) {
ssize_t n, nwritten = 0;
uint64_t byte_len = bitroarLen(o);
sds payload;
if ((n = rdbSaveLen(rdb, byte_len)) == -1) return -1;
nwritten += n;
payload = bitroarSerializePortable(o);
if ((n = rdbSaveRawString(rdb, (unsigned char *)payload, sdslen(payload))) == -1) {
sdsfree(payload);
return -1;
}
nwritten += n;
sdsfree(payload);
return nwritten;
}
/* Load a RDB_TYPE_BITMAP value. As for the other types, a read failure returns
* NULL without reporting it, so the caller handles it as a short read (which
* is not fatal when loading from a socket). Only a value that fails validation
* is reported as corruption. */
static robj *rdbLoadBitmapObject(rio *rdb) {
uint64_t byte_len;
size_t payload_len;
sds payload;
robj *o;
if ((byte_len = rdbLoadLen(rdb, NULL)) == RDB_LENERR) return NULL;
if (byte_len > BITROAR_MAX_BYTES) goto toolarge;
#if SIZE_MAX < UINT64_MAX
if (byte_len > (uint64_t)SIZE_MAX) goto toolarge;
#endif
payload = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, &payload_len);
if (payload == NULL) return NULL;
/* bitroarCreateFromPortable() validates the payload and rejects a byte_len
* that cannot hold the highest set bit. A byte_len beyond the highest set
* bit is legitimate: trailing zero bytes contain no set bits, yet extend
* the logical length. */
o = bitroarCreateFromPortable((unsigned char *)payload, payload_len, byte_len);
sdsfree(payload);
if (o == NULL) {
rdbReportCorruptRDB("Invalid bitmap RDB payload");
return NULL;
}
return o;
toolarge:
rdbReportCorruptRDB("Bitmap byte length %llu too large",
(unsigned long long)byte_len);
return NULL;
}
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {
ssize_t n = 0, nwritten = 0;
@@ -1667,6 +1733,9 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {
nwritten += n;
}
}
} else if (o->type == OBJ_BITMAP) {
if ((n = rdbSaveBitmapObject(rdb, o)) == -1) return -1;
nwritten += n;
} else {
serverPanic("Unknown object type");
}
@@ -4491,6 +4560,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error)
arSet(ar, idx, v);
}
} else if (rdbtype == RDB_TYPE_BITMAP) {
if ((o = rdbLoadBitmapObject(rdb)) == NULL) return NULL;
} else {
rdbReportReadError("Unknown RDB encoding type %d",rdbtype);
return NULL;
+4 -3
View File
@@ -85,16 +85,17 @@
#define RDB_TYPE_HASH_TMPL_LP_REF 30 /* TMPL_LP, with template ref (RDB save): raw lp blob, first entry is tid */
#define RDB_TYPE_HASH_TMPL_ARRAY 31 /* TMPL_ARRAY, self-contained (DUMP): [count][f0][v0]...[fN-1][vN-1] */
#define RDB_TYPE_HASH_TMPL_ARRAY_REF 32 /* TMPL_ARRAY, with template ref (RDB save): [tid][v0]...[vN-1] */
#define RDB_TYPE_BITMAP 33 /* [logical byte length][RDB string with CRoaring64 portable blob] */
#ifdef ENABLE_GCRA
#define RDB_TYPE_GCRA 33 /* GCRA object */
#define RDB_TYPE_GCRA 34 /* GCRA object */
#endif
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType(), and rdb_type_string[] */
/* Test if a type is an object type. */
#ifdef ENABLE_GCRA
#define rdbIsObjectType(t) (((t) >= 0 && (t) <= 7) || ((t) >= 9 && (t) <= 33))
#define rdbIsObjectType(t) (((t) >= 0 && (t) <= 7) || ((t) >= 9 && (t) <= 34))
#else
#define rdbIsObjectType(t) (((t) >= 0 && (t) <= 7) || ((t) >= 9 && (t) <= 32))
#define rdbIsObjectType(t) (((t) >= 0 && (t) <= 7) || ((t) >= 9 && (t) <= 33))
#endif
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
+1
View File
@@ -93,6 +93,7 @@ char *rdb_type_string[] = {
"hash-tmpl-lp-ref",
"hash-tmpl-array",
"hash-tmpl-array-ref",
"bitmap",
#ifdef ENABLE_GCRA
"gcra",
#endif
+4
View File
@@ -9782,6 +9782,7 @@ typeinfo type_set = { "set", "SCARD", "members" };
typeinfo type_hash = { "hash", "HLEN", "fields" };
typeinfo type_zset = { "zset", "ZCARD", "members" };
typeinfo type_stream = { "stream", "XLEN", "entries" };
typeinfo type_bitmap = { "bitmap", "BITCOUNT", "set bits" };
typeinfo type_other = { "other", NULL, "?" };
static typeinfo* typeinfo_add(dict *types, char* name, typeinfo* type_template) {
@@ -9959,6 +9960,7 @@ static void findBigKeys(int memkeys, long long memkeys_samples) {
typeinfo_add(types_dict, "hash", &type_hash);
typeinfo_add(types_dict, "zset", &type_zset);
typeinfo_add(types_dict, "stream", &type_stream);
typeinfo_add(types_dict, "bitmap", &type_bitmap);
signal(SIGINT, longStatLoopModeStop);
/* Total keys pre scanning */
@@ -11143,6 +11145,7 @@ static void keyStats(long long memkeys_samples, unsigned long long cursor, unsig
typeinfo_add(memkeys_types_dict, "hash", &type_hash);
typeinfo_add(memkeys_types_dict, "zset", &type_zset);
typeinfo_add(memkeys_types_dict, "stream", &type_stream);
typeinfo_add(memkeys_types_dict, "bitmap", &type_bitmap);
/* We could use only one typeinfo dictionary if we add new fields to save
* both memkey and bigkey info. Not sure it would make sense in findBigKeys(). */
@@ -11153,6 +11156,7 @@ static void keyStats(long long memkeys_samples, unsigned long long cursor, unsig
typeinfo_add(bigkeys_types_dict, "hash", &type_hash);
typeinfo_add(bigkeys_types_dict, "zset", &type_zset);
typeinfo_add(bigkeys_types_dict, "stream", &type_stream);
typeinfo_add(bigkeys_types_dict, "bitmap", &type_bitmap);
size_dist key_length_dist;
size_dist_entry distribution[] = {
+3 -1
View File
@@ -90,6 +90,7 @@ typedef long long ustime_t;
#define REDISMODULE_KEYTYPE_MODULE 6
#define REDISMODULE_KEYTYPE_STREAM 7
#define REDISMODULE_KEYTYPE_ARRAY 8
#define REDISMODULE_KEYTYPE_BITMAP 9
/* Reply types. */
#define REDISMODULE_REPLY_UNKNOWN -1
@@ -248,6 +249,7 @@ This flag should not be used directly by the module.
#define REDISMODULE_NOTIFY_OVERWRITTEN (1<<15) /* o, key overwrite notification */
#define REDISMODULE_NOTIFY_TYPE_CHANGED (1<<16) /* c, key type changed notification */
#define REDISMODULE_NOTIFY_KEY_TRIMMED (1<<17) /* module only key space notification, indicates a key trimmed during slot migration */
#define REDISMODULE_NOTIFY_BITMAP (1<<18) /* b, bitmap key space notification */
#define REDISMODULE_NOTIFY_SUBKEYSPACE (1<<19) /* S */
#define REDISMODULE_NOTIFY_SUBKEYEVENT (1<<20) /* T */
@@ -272,7 +274,7 @@ This flag should not be used directly by the module.
#define REDISMODULE_NOTIFY_FLAG_NONE 0 /* Invoke callback for all matching events */
#define REDISMODULE_NOTIFY_FLAG_SUBKEYS_REQUIRED (1<<0) /* Only invoke callback when subkeys are present */
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_MODULE | REDISMODULE_NOTIFY_ARRAY) /* A */
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_MODULE | REDISMODULE_NOTIFY_BITMAP | REDISMODULE_NOTIFY_ARRAY) /* A */
/* A special pointer that we can use between the core and the module to signal
* field deletion, and that is impossible to be a valid pointer. */
+8 -4
View File
@@ -33,6 +33,7 @@
#include "estore.h"
#include "chk.h"
#include "fast_float_strtod.h"
#include "bitroar.h"
#include <time.h>
#include <signal.h>
@@ -360,8 +361,8 @@ static void dictDestructorKV(dict *d, void *key) {
if (hist) {
/* we don't call kvsUpdateHistogram() because it contains debugServerAssert
* that may fail in bg thread as kvstore might not being fully initialized */
int old_bin = (alloc_size == 0) ? 0 : log2ceil(alloc_size) + 1;
debugServerAssert(old_bin < MAX_KEYSIZES_BINS);
int old_bin = (alloc_size == 0) ? 0 :
min(log2ceil(alloc_size) + 1, MAX_KEYSIZES_BINS - 1);
hist[old_bin]--;
}
}
@@ -7296,7 +7297,8 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
[OBJ_SET] = "distrib_sets_items",
[OBJ_ZSET] = "distrib_zsets_items",
[OBJ_HASH] = "distrib_hashes_items",
[OBJ_STREAM] = "distrib_streams_items"
[OBJ_STREAM] = "distrib_streams_items",
[OBJ_BITMAP] = "distrib_bitmaps_sizes"
};
static const char *type_sizes_str[OBJ_TYPE_MAX] = {
[OBJ_STRING] = NULL, /* Skip strings to avoid confusion with distrib_strings_sizes */
@@ -7304,7 +7306,8 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
[OBJ_SET] = "distrib_sets_sizes",
[OBJ_ZSET] = "distrib_zsets_sizes",
[OBJ_HASH] = "distrib_hashes_sizes",
[OBJ_STREAM] = "distrib_streams_sizes"
[OBJ_STREAM] = "distrib_streams_sizes",
[OBJ_BITMAP] = NULL, /* Skip bitmaps to avoid confusion with distrib_bitmaps_sizes */
};
for (int dbnum = 0; dbnum < server.dbnum; dbnum++) {
@@ -8327,6 +8330,7 @@ int main(int argc, char **argv) {
if (exec_name == NULL) exec_name = argv[0];
server.sentinel_mode = checkForSentinelMode(argc,argv, exec_name);
initServerConfig();
bitroarInit();
ACLInit(); /* The ACL subsystem must be initialized ASAP because the
basic networking code and client creation depends on it. */
moduleInitModulesSystem();
+13 -6
View File
@@ -843,6 +843,7 @@ typedef enum {
#define NOTIFY_OVERWRITTEN (1<<15) /* o, key overwrite notification (Note: excluded from NOTIFY_ALL) */
#define NOTIFY_TYPE_CHANGED (1<<16) /* c, key type changed notification (Note: excluded from NOTIFY_ALL) */
#define NOTIFY_KEY_TRIMMED (1<<17) /* module only key space notification, indicates a key trimmed during slot migration */
#define NOTIFY_BITMAP (1<<18) /* b, bitmap notification */
#define NOTIFY_SUBKEYSPACE (1<<19) /* S, subkey-level keyspace notification */
#define NOTIFY_SUBKEYEVENT (1<<20) /* T, subkey-level keyevent notification */
#define NOTIFY_SUBKEYSPACEITEM (1<<21) /* I, subkey-level notification per item: channel=key\nsubkey */
@@ -851,7 +852,7 @@ typedef enum {
#ifdef ENABLE_GCRA
#define NOTIFY_RATE_LIMIT (1<<24) /* r, notify rate limit event (Note: excluded from NOTIFY_ALL)*/
#endif
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_MODULE | NOTIFY_ARRAY) /* A flag */
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_MODULE | NOTIFY_BITMAP | NOTIFY_ARRAY) /* A flag */
#define _run_with_period(_cronloops_, _ms_, _hz_) if (((_ms_) <= 1000/(_hz_)) || !((_cronloops_)%((_ms_)/(1000/(_hz_)))))
@@ -920,11 +921,12 @@ typedef enum {
#define OBJ_MODULE 5 /* Module object. */
#define OBJ_STREAM 6 /* Stream object. */
#define OBJ_ARRAY 7 /* Array object. */
#define OBJ_BITMAP 8 /* Bitmap object. */
#ifdef ENABLE_GCRA
#define OBJ_GCRA 8 /* GCRA object. */
#define OBJ_TYPE_MAX 9 /* Maximum number of object types */
#define OBJ_GCRA 9 /* GCRA object. */
#define OBJ_TYPE_MAX 10 /* Maximum number of object types */
#else
#define OBJ_TYPE_MAX 8 /* Maximum number of object types */
#define OBJ_TYPE_MAX 9 /* Maximum number of object types */
#endif
/* NOTE: adding a new object requires changes in the following places:
@@ -1265,11 +1267,12 @@ typedef struct redisDb {
unsigned long expires_cursor; /* Cursor of the active expire cycle. */
} redisDb;
/* maximum number of bins of keysizes histogram */
/* Maximum number of bins of keysizes histogram. Sizes beyond the final bin's
* range, for any data type, saturate into the final bin. */
#define MAX_KEYSIZES_BINS 60
/* Per-type keysizes/allocsizes histograms: one row per tracked type, i.e. the
* basic types plus streams. Rows are addressed with keysizesHistRow() rather
* basic types plus streams and bitmaps. Rows are addressed with keysizesHistRow() rather
* than by object type, so untracked types in between (OBJ_MODULE) cost no row
* and the histogram stays plain storage: zeroing initializes it, and it can be
* copied or moved like any other array. */
@@ -1280,6 +1283,7 @@ enum {
KEYSIZES_ROW_ZSET,
KEYSIZES_ROW_HASH,
KEYSIZES_ROW_STREAM,
KEYSIZES_ROW_BITMAP,
MAX_KEYSIZES_ROWS /* must stay last */
};
typedef int64_t keysizesHist[MAX_KEYSIZES_ROWS][MAX_KEYSIZES_BINS];
@@ -2561,6 +2565,7 @@ struct redisServer {
int lfu_log_factor; /* LFU logarithmic counter factor. */
int lfu_decay_time; /* LFU counter decay factor. */
long long proto_max_bulk_len; /* Protocol bulk length maximum size. */
int bitmap_default_roaring; /* If true, bitmap writes default to Roaring bitmaps. */
int oom_score_adj_values[CONFIG_OOM_COUNT]; /* Linux oom_score_adj configuration */
int oom_score_adj; /* If true, oom_score_adj is managed */
int disable_thp; /* If true, disable THP by syscall */
@@ -4620,6 +4625,7 @@ void delCommand(client *c);
void delexCommand(client *c);
void unlinkCommand(client *c);
void existsCommand(client *c);
void bitconvertCommand(client *c);
void blessCommand(client *c);
void setbitCommand(client *c);
void getbitCommand(client *c);
@@ -4844,6 +4850,7 @@ void functionRestoreCommand(client *c);
void functionDumpCommand(client *c);
void timeCommand(client *c);
void bitopCommand(client *c);
void bitroaropCommand(client *c);
void bitcountCommand(client *c);
void bitposCommand(client *c);
void replconfCommand(client *c);
+68 -7
View File
@@ -72,6 +72,25 @@ if 0 {
r gcra gcra 10 5 60000
}
# Create Roaring bitmap keys spanning empty, array, bitset, run, and
# multiple high-32 buckets so corruption reaches each portable-format
# structural layer.
r restore bitmapempty 0 [empty_roaring_bitmap_dump_payload]
r config set bitmap-default-roaring yes
r setbit bitmap 7 1
r setbit bitmap 100 1
r setbit bitmap 4095 1
r set bitmapbitset [string repeat "\xaa" 8192]
r setbit bitmapbitset 0 1 ;# converts the dense string to roaring
r setrange bitmapbig 0 [string repeat "\xff" 1024]
r setbit bitmapbig 100000 1 ;# converts the dense string to roaring
set old_proto_max_bulk_len [config_get_set proto-max-bulk-len 536870913]
r setbit bitmap64 1 1
r setbit bitmap64 4294967296 1
r config set proto-max-bulk-len $old_proto_max_bulk_len
r config set bitmap-default-roaring no
# create bigger objects with 10 items (more than a single ziplist / listpack)
generate_collections big 10
@@ -111,6 +130,40 @@ proc corrupt_payload {payload} {
return $payload
}
# Corrupt bytes inside an uncompressed bitmap's portable Roaring blob rather
# than spending the deterministic seed passes on its RDB wrapper or checksum.
proc corrupt_roaring_payload {payload} {
set offset 1 ;# Skip RDB_TYPE_BITMAP.
foreach field {byte-length payload-length} {
binary scan [string index $payload $offset] cu first
set type [expr {$first >> 6}]
if {$type == 0} {
incr offset
} elseif {$type == 1} {
incr offset 2
} elseif {$first == 0x80} {
incr offset 5
} elseif {$first == 0x81} {
incr offset 9
} else {
error "unexpected encoded $field in bitmap DUMP"
}
}
set len [expr {[string length $payload] - $offset - 10}]
if {$len <= 0} { error "missing portable bitmap payload" }
set count 1
if {rand() > 0.9} { set count 2 }
while {$count > 0} {
set idx [expr {$offset + int(rand() * $len)}]
binary scan [string index $payload $idx] cu old
set ch [binary format c [expr {($old + 1 + int(rand() * 255)) & 0xff}]]
set payload [string replace $payload $idx $idx $ch]
incr count -1
}
return $payload
}
# fuzzy tester for corrupt RESTORE payloads
# valgrind will make sure there were no leaks in the rdb loader error handling code
foreach sanitize_dump {no yes} {
@@ -149,11 +202,22 @@ foreach sanitize_dump {no yes} {
set stat_successful_restore 0
set stat_rejected_restore 0
set stat_traffic_commands_sent 0
# repeatedly DUMP a random key, corrupt it and try RESTORE into a new key
# Exercise every Roaring portable shape once before returning to
# random key selection, then repeatedly corrupt DUMP payloads and
# try RESTORE into a new key.
set roaring_keys {bitmapempty bitmap bitmapbitset bitmapbig bitmap64}
while true {
set k [r randomkey]
set dump [r dump $k]
set dump [corrupt_payload $dump]
if {$cycle < [llength $roaring_keys]} {
set k [lindex $roaring_keys $cycle]
set old_compression [config_get_set rdbcompression no]
set dump [r dump $k]
r config set rdbcompression $old_compression
set dump [corrupt_roaring_payload $dump]
} else {
set k [r randomkey]
set dump [r dump $k]
set dump [corrupt_payload $dump]
}
set printable_dump [string2printable $dump]
set restore_failed false
set report_and_restart false
@@ -290,7 +354,4 @@ foreach sanitize_dump {no yes} {
}
}
} ;# tags
+17
View File
@@ -55,6 +55,22 @@ start_server {tags {"dismiss external:skip needs:debug"}} {
r arset sparse_array [expr {$i * 5000}] $bigstr
}
# bitmap: cover all three roaring container types. Six 64K-bit chunks
# hold alternating bits (bitset containers), the next chunk is all
# ones and collapses into a run container when the conversion
# run-optimizes, and the distant bit lands alone in a later chunk
# (sparse array container). Multiple bitsets keep the compact portable
# payload's average above the per-container page threshold.
r set bigbitmap [binary format H* \
"[string repeat aa 49152][string repeat ff 8192]"]
r config set bitmap-default-roaring yes
r setbit bigbitmap 600000 1 ;# converts the dense string to roaring
r config set bitmap-default-roaring no
assert_equal bitmap [r type bigbitmap]
# Disable LZF so the size hint tracks the portable container payload.
set old_rdbcompression [config_get_set rdbcompression no]
set digest [debug_digest]
# Test both RDB (yes) and AOF (no) rewrite paths.
foreach preamble {yes no} {
@@ -65,6 +81,7 @@ start_server {tags {"dismiss external:skip needs:debug"}} {
set newdigest [debug_digest]
assert {$digest eq $newdigest}
}
r config set rdbcompression $old_rdbcompression
}
test {dismiss client output buffer} {
+55
View File
@@ -987,6 +987,61 @@ start_server {tags {"cli external:skip"}} {
}
}
start_server {tags {"cli" "bitmap" "bitmap-roaring" "external:skip"}} {
test "bigkeys reports native bitmap cardinality" {
r config set bitmap-default-roaring yes
r setbit bitmap:small 0 1
r setbit bitmap:small 64 1
for {set bit 0} {$bit < 5} {incr bit} {
r setbit bitmap:large $bit 1
}
r config set bitmap-default-roaring no
assert_equal bitmap-roaring [r object encoding bitmap:small]
assert_equal bitmap-roaring [r object encoding bitmap:large]
set cmd [rediscli [srv host] [srv port] [list -n $::dbnum --bigkeys]]
assert_equal 0 [catch {exec {*}$cmd 2>@1} result]
assert_match {*Biggest bitmap found "bitmap:large" has 5 set bits*} $result
assert_match {*2 bitmaps with 7 set bits*avg size 3.50*} $result
assert_no_match {*bitmaps with 0 ?*} $result
}
test "keystats reports native bitmap cardinality" {
r config set bitmap-default-roaring yes
r setbit bitmap:small 0 1
r setbit bitmap:small 64 1
for {set bit 0} {$bit < 5} {incr bit} {
r setbit bitmap:large $bit 1
}
r config set bitmap-default-roaring no
assert_equal bitmap-roaring [r object encoding bitmap:small]
assert_equal bitmap-roaring [r object encoding bitmap:large]
set cmd [rediscli [srv host] [srv port] [list -n $::dbnum --keystats]]
assert_equal 0 [catch {exec {*}$cmd 2>@1} result]
assert_match {*"bitmap:large" has 5 set bits*} $result
assert_match {*bitmap*7 set bits*3.50*} $result
}
test "bigkeys and keystats report a zero-cardinality native bitmap" {
r flushdb
r config set bitmap-default-roaring yes
r setbit bitmap:zero 0 0
r config set bitmap-default-roaring no
assert_equal bitmap-roaring [r object encoding bitmap:zero]
assert_equal 0 [r bitcount bitmap:zero]
set cmd [rediscli [srv host] [srv port] [list -n $::dbnum --bigkeys]]
assert_equal 0 [catch {exec {*}$cmd 2>@1} result]
assert_match {*Biggest bitmap found "bitmap:zero" has 0 set bits*} $result
assert_match {*1 bitmaps with 0 set bits*avg size 0.00*} $result
set cmd [rediscli [srv host] [srv port] [list -n $::dbnum --keystats]]
assert_equal 0 [catch {exec {*}$cmd 2>@1} result]
assert_match {*bitmap*"bitmap:zero" has 0 set bits*} $result
}
}
start_server {tags {"cli external:skip"}} {
test "bigkeys reports a zero-size key as the biggest key of its type" {
# type->biggest starts at 0, so a strict '<' comparison never fires when
+23
View File
@@ -53,6 +53,29 @@ start_server {tags {"repl external:skip debug_defrag:skip"}} {
}
}
test {Master can replicate BITOP NOT longer than replica proto-max-bulk-len} {
set limit 1048576
set master_old [lindex [r config get proto-max-bulk-len] 1]
set replica_old [lindex [r -1 config get proto-max-bulk-len] 1]
r config set proto-max-bulk-len [expr {$limit + 1}]
r -1 config set proto-max-bulk-len $limit
r del bitop:not:repl:src bitop:not:repl:out
r setbit bitop:not:repl:src [expr {($limit + 1) * 8 - 1}] 1
assert_equal [expr {$limit + 1}] [r bitop not bitop:not:repl:out bitop:not:repl:src]
wait_for_ofs_sync [srv 0 client] [srv -1 client]
assert_equal [expr {$limit + 1}] [r -1 strlen bitop:not:repl:out]
# GETBIT enforces the replica's local proto-max-bulk-len offset limit,
# so verify edge bytes with bounded GETRANGE reads instead.
assert_equal [binary format H* ff] [r -1 getrange bitop:not:repl:out 0 0]
assert_equal [binary format H* fe] [r -1 getrange bitop:not:repl:out $limit $limit]
r config set proto-max-bulk-len $master_old
r -1 config set proto-max-bulk-len $replica_old
r del bitop:not:repl:src bitop:not:repl:out
}
test {Slave is able to evict keys created in writable slaves} {
r -1 select 5
assert {[r -1 dbsize] == 0}
+7
View File
@@ -767,6 +767,10 @@ test {diskless loading short read} {
set has_vector_sets [server_has_command vadd]
# Make SETBIT create native Roaring bitmaps, and convert the
# dense string bitmaps below into them
r config set bitmap-default-roaring yes
for {set k 0} {$k < 3} {incr k} {
for {set i 0} {$i < 10} {incr i} {
r set "$k int_$i" [expr {int(rand()*10000)}]
@@ -782,6 +786,9 @@ test {diskless loading short read} {
r zadd "$k zset_large" [expr {rand()}] [string repeat A [expr {int(rand()*1000000)}]]
r lpush "$k list_small" [string repeat A [expr {int(rand()*10)}]]
r lpush "$k list_large" [string repeat A [expr {int(rand()*1000000)}]]
r setbit "$k bitmap_sparse" [expr {int(rand()*100000000)}] 1
r set "$k bitmap_large_$i" [string repeat A [expr {int(rand()*1000000)}]]
r setbit "$k bitmap_large_$i" 0 1
if {$has_vector_sets} {
r vadd "$k vector_set" VALUES 3 [expr {rand()}] [expr {rand()}] [expr {rand()}] [string repeat A [expr {int(rand()*1000)}]]
+322 -2
View File
@@ -52,6 +52,139 @@ static int KeySpace_NotificationLoaded(RedisModuleCtx *ctx, int type, const char
}
static long long callback_call_count = 0;
static long long string_callback_call_count = 0;
static long long bitmap_callback_call_count = 0;
#define BITMAP_TRANSITION_LOG_MAX 16
#define BITMAP_TRANSITION_LOG_ENTRY_MAX 128
static char bitmap_transition_log[BITMAP_TRANSITION_LOG_MAX][BITMAP_TRANSITION_LOG_ENTRY_MAX];
static int bitmap_transition_log_count = 0;
static long long bitmap_transition_unlink_count = 0;
static int IsBitmapTransitionKey(const RedisModuleString *key) {
static const char prefix[] = "bitmap:transition:";
size_t len;
const char *key_str = RedisModule_StringPtrLen(key, &len);
return len >= sizeof(prefix) - 1 &&
memcmp(key_str, prefix, sizeof(prefix) - 1) == 0;
}
static int IsBitmapTransitionReplayKey(const RedisModuleString *key) {
static const char replay_key[] = "bitmap:transition:replay";
size_t len;
const char *key_str = RedisModule_StringPtrLen(key, &len);
return len == sizeof(replay_key) - 1 &&
memcmp(key_str, replay_key, sizeof(replay_key) - 1) == 0;
}
static const char *KeyTypeName(int type) {
switch (type) {
case REDISMODULE_KEYTYPE_EMPTY: return "empty";
case REDISMODULE_KEYTYPE_STRING: return "string";
case REDISMODULE_KEYTYPE_BITMAP: return "bitmap";
default: return "other";
}
}
static int IsLiveMasterContext(RedisModuleCtx *ctx) {
int flags = RedisModule_GetContextFlags(ctx);
return (flags & REDISMODULE_CTX_FLAGS_MASTER) &&
!(flags & REDISMODULE_CTX_FLAGS_LOADING);
}
static int BitmapTransitionKeyIsBitmap(RedisModuleCtx *ctx,
RedisModuleString *key)
{
RedisModuleKey *redis_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ);
int is_bitmap = RedisModule_KeyType(redis_key) == REDISMODULE_KEYTYPE_BITMAP;
RedisModule_CloseKey(redis_key);
return is_bitmap;
}
static const char *NotificationClassName(int type) {
switch (type) {
case REDISMODULE_NOTIFY_GENERIC: return "generic";
case REDISMODULE_NOTIFY_NEW: return "new";
case REDISMODULE_NOTIFY_BITMAP: return "bitmap";
case REDISMODULE_NOTIFY_OVERWRITTEN: return "overwritten";
case REDISMODULE_NOTIFY_TYPE_CHANGED: return "type_changed";
default: return "other";
}
}
static void LogBitmapTransitionNotification(RedisModuleCtx *ctx, int type,
const char *event,
RedisModuleString *key)
{
int replay_only_event = type == REDISMODULE_NOTIFY_GENERIC ||
type == REDISMODULE_NOTIFY_NEW;
if (!IsBitmapTransitionKey(key) ||
(replay_only_event && !IsBitmapTransitionReplayKey(key)) ||
bitmap_transition_log_count == BITMAP_TRANSITION_LOG_MAX)
{
return;
}
RedisModuleKey *redis_key = RedisModule_OpenKey(ctx, key, REDISMODULE_READ);
int key_type = RedisModule_KeyType(redis_key);
snprintf(bitmap_transition_log[bitmap_transition_log_count],
BITMAP_TRANSITION_LOG_ENTRY_MAX, "%s %s %s", event,
NotificationClassName(type), KeyTypeName(key_type));
bitmap_transition_log_count++;
RedisModule_CloseKey(redis_key);
}
static void DeleteBitmapTransitionKey(RedisModuleCtx *ctx, RedisModuleString *key) {
RedisModuleCallReply *reply = RedisModule_Call(ctx, "DEL", "s!", key);
RedisModule_Assert(reply != NULL);
RedisModule_FreeCallReply(reply);
}
static void ReplaceBitmapTransitionKeyWithString(RedisModuleCtx *ctx,
RedisModuleString *key,
int propagate)
{
const unsigned char value[] = {0x20};
RedisModuleCallReply *reply = RedisModule_Call(
ctx, "SET", propagate ? "sb!" : "sb", key, value, sizeof(value));
RedisModule_Assert(reply != NULL);
RedisModule_FreeCallReply(reply);
}
static void ReplaceBitmapTransitionKeyWithList(RedisModuleCtx *ctx,
RedisModuleString *key)
{
DeleteBitmapTransitionKey(ctx, key);
RedisModuleCallReply *reply = RedisModule_Call(
ctx, "LPUSH", "sc!", key, "replacement");
RedisModule_Assert(reply != NULL);
RedisModule_FreeCallReply(reply);
}
static int BitmapTransitionBitIsSet(RedisModuleCtx *ctx, RedisModuleString *key,
long long offset)
{
RedisModuleCallReply *reply = RedisModule_Call(ctx, "GETBIT", "sl", key, offset);
RedisModule_Assert(reply != NULL);
RedisModule_Assert(RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_INTEGER);
int is_set = RedisModule_CallReplyInteger(reply) != 0;
RedisModule_FreeCallReply(reply);
return is_set;
}
static void BitmapTransitionKeyUnlink(RedisModuleCtx *ctx, RedisModuleEvent eid,
uint64_t subevent, void *data)
{
REDISMODULE_NOT_USED(ctx);
REDISMODULE_NOT_USED(eid);
REDISMODULE_NOT_USED(subevent);
RedisModuleKeyInfoV1 *info = data;
const RedisModuleString *key = RedisModule_GetKeyNameFromModuleKey(info->key);
if (IsBitmapTransitionKey(key))
bitmap_transition_unlink_count++;
}
static int KeySpace_NotificationGeneric(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
REDISMODULE_NOT_USED(type);
callback_call_count++;
@@ -89,13 +222,42 @@ static int KeySpace_NotificationGeneric(RedisModuleCtx *ctx, int type, const cha
return REDISMODULE_OK;
}
/* A keymiss or expired event on "bitmap:bitop:trigger" deletes and recreates
* "bitmap:bitop:victim", so a multi-key command that looks up the trigger
* after the victim loses the value it already looked up. Recreating the
* victim likely reuses the freed memory, so a dangling source is unlikely to
* still read as the original value. An event on "bitmap:bitop:trigger-grow"
* instead grows the victim in place and gives it a TTL. */
static void ReplaceBitopVictimOnTrigger(RedisModuleCtx *ctx, RedisModuleString *key) {
const char *name = RedisModule_StringPtrLen(key, NULL);
RedisModuleCallReply *reply;
if (!IsLiveMasterContext(ctx)) return;
if (!strcmp(name, "bitmap:bitop:trigger")) {
reply = RedisModule_Call(ctx, "DEL", "c!", "bitmap:bitop:victim");
RedisModule_Assert(reply != NULL);
RedisModule_FreeCallReply(reply);
reply = RedisModule_Call(ctx, "SETBIT", "ccc!", "bitmap:bitop:victim", "5000", "1");
RedisModule_Assert(reply != NULL);
RedisModule_FreeCallReply(reply);
} else if (!strcmp(name, "bitmap:bitop:trigger-grow")) {
reply = RedisModule_Call(ctx, "SETBIT", "ccc!", "bitmap:bitop:victim", "200000", "1");
RedisModule_Assert(reply != NULL);
RedisModule_FreeCallReply(reply);
reply = RedisModule_Call(ctx, "PEXPIRE", "cc!", "bitmap:bitop:victim", "100000");
RedisModule_Assert(reply != NULL);
RedisModule_FreeCallReply(reply);
}
}
static int KeySpace_NotificationExpired(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
REDISMODULE_NOT_USED(type);
REDISMODULE_NOT_USED(event);
REDISMODULE_NOT_USED(key);
RedisModuleCallReply* rep = RedisModule_Call(ctx, "INCR", "c!", "testkeyspace:expired");
RedisModule_FreeCallReply(rep);
ReplaceBitopVictimOnTrigger(ctx, key);
return REDISMODULE_OK;
}
@@ -107,7 +269,6 @@ static int KeySpace_NotificationExpired(RedisModuleCtx *ctx, int type, const cha
static int KeySpace_NotificationModuleKeyMiss(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
REDISMODULE_NOT_USED(type);
REDISMODULE_NOT_USED(event);
REDISMODULE_NOT_USED(key);
int flags = RedisModule_GetContextFlags(ctx);
if (!(flags & REDISMODULE_CTX_FLAGS_MASTER)) {
@@ -116,12 +277,14 @@ static int KeySpace_NotificationModuleKeyMiss(RedisModuleCtx *ctx, int type, con
RedisModuleCallReply* rep = RedisModule_Call(ctx, "incr", "!c", "missed");
RedisModule_FreeCallReply(rep);
ReplaceBitopVictimOnTrigger(ctx, key);
return REDISMODULE_OK;
}
static int KeySpace_NotificationModuleString(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
REDISMODULE_NOT_USED(type);
string_callback_call_count++;
const char *key_str = RedisModule_StringPtrLen(key, NULL);
/* Delete the key from within the notification callback, to verify the
@@ -148,6 +311,65 @@ static int KeySpace_NotificationModuleString(RedisModuleCtx *ctx, int type, cons
return REDISMODULE_OK;
}
static int KeySpace_NotificationModuleBitmap(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) {
bitmap_callback_call_count++;
LogBitmapTransitionNotification(ctx, type, event, key);
if (IsLiveMasterContext(ctx) &&
strncmp(RedisModule_StringPtrLen(key, NULL),
"bitmap:transition:delete-bitmap",
strlen("bitmap:transition:delete-bitmap")) == 0)
{
DeleteBitmapTransitionKey(ctx, key);
}
return REDISMODULE_OK;
}
static int KeySpace_NotificationBitmapTransition(RedisModuleCtx *ctx, int type,
const char *event,
RedisModuleString *key)
{
LogBitmapTransitionNotification(ctx, type, event, key);
if (type == REDISMODULE_NOTIFY_TYPE_CHANGED && IsLiveMasterContext(ctx) &&
strcmp(RedisModule_StringPtrLen(key, NULL),
"bitmap:transition:delete-type") == 0)
{
DeleteBitmapTransitionKey(ctx, key);
}
if (type == REDISMODULE_NOTIFY_TYPE_CHANGED && IsLiveMasterContext(ctx) &&
strncmp(RedisModule_StringPtrLen(key, NULL),
"bitmap:transition:delete-type-zero",
strlen("bitmap:transition:delete-type-zero")) == 0 &&
!BitmapTransitionBitIsSet(ctx, key, 0))
{
DeleteBitmapTransitionKey(ctx, key);
}
if (type == REDISMODULE_NOTIFY_TYPE_CHANGED && IsLiveMasterContext(ctx) &&
strncmp(RedisModule_StringPtrLen(key, NULL),
"bitmap:transition:replace-type-string",
strlen("bitmap:transition:replace-type-string")) == 0 &&
BitmapTransitionKeyIsBitmap(ctx, key))
{
ReplaceBitmapTransitionKeyWithString(ctx, key, 1);
}
if (type == REDISMODULE_NOTIFY_TYPE_CHANGED && IsLiveMasterContext(ctx) &&
strncmp(RedisModule_StringPtrLen(key, NULL),
"bitmap:transition:replace-type-list",
strlen("bitmap:transition:replace-type-list")) == 0 &&
BitmapTransitionKeyIsBitmap(ctx, key))
{
ReplaceBitmapTransitionKeyWithList(ctx, key);
}
if (type == REDISMODULE_NOTIFY_TYPE_CHANGED &&
strncmp(RedisModule_StringPtrLen(key, NULL),
"bitmap:transition:replace-replay-string",
strlen("bitmap:transition:replace-replay-string")) == 0 &&
BitmapTransitionKeyIsBitmap(ctx, key))
{
ReplaceBitmapTransitionKeyWithString(ctx, key, 0);
}
return REDISMODULE_OK;
}
static void KeySpace_PostNotificationStringFreePD(void *pd) {
RedisModule_FreeString(NULL, pd);
}
@@ -429,6 +651,8 @@ static RedisModuleNotificationFunc get_callback_for_event(int event_mask) {
// We have two callbacks for STRING events in your OnLoad,
// For simplicity, pick the first:
return KeySpace_NotificationModuleString;
case REDISMODULE_NOTIFY_BITMAP:
return KeySpace_NotificationModuleBitmap;
default:
return NULL;
}
@@ -441,6 +665,62 @@ int GetCallbackCountCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int a
return REDISMODULE_OK;
}
int GetStringCallbackCountCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
RedisModule_ReplyWithLongLong(ctx, string_callback_call_count);
return REDISMODULE_OK;
}
int GetBitmapCallbackCountCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
RedisModule_ReplyWithLongLong(ctx, bitmap_callback_call_count);
return REDISMODULE_OK;
}
static int ResetBitmapTransitionCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
if (argc != 1)
return RedisModule_WrongArity(ctx);
bitmap_transition_log_count = 0;
bitmap_transition_unlink_count = 0;
return RedisModule_ReplyWithSimpleString(ctx, "OK");
}
static int GetBitmapTransitionCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
if (argc != 1)
return RedisModule_WrongArity(ctx);
RedisModule_ReplyWithArray(ctx, bitmap_transition_log_count);
for (int i = 0; i < bitmap_transition_log_count; i++) {
RedisModule_ReplyWithStringBuffer(ctx, bitmap_transition_log[i],
strlen(bitmap_transition_log[i]));
}
return REDISMODULE_OK;
}
static int GetBitmapTransitionUnlinkCountCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
if (argc != 1)
return RedisModule_WrongArity(ctx);
return RedisModule_ReplyWithLongLong(ctx, bitmap_transition_unlink_count);
}
static int GetKeyTypeCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 2)
return RedisModule_WrongArity(ctx);
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
int type = RedisModule_KeyType(key);
RedisModule_ReplyWithCString(ctx, KeyTypeName(type));
RedisModule_CloseKey(key);
return REDISMODULE_OK;
}
static int CmdUnsub(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 2) {
return RedisModule_WrongArity(ctx);
@@ -507,6 +787,22 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return REDISMODULE_ERR;
}
if(RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_BITMAP, KeySpace_NotificationModuleBitmap) != REDISMODULE_OK){
return REDISMODULE_ERR;
}
if(RedisModule_SubscribeToKeyspaceEvents(ctx,
REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_NEW |
REDISMODULE_NOTIFY_OVERWRITTEN | REDISMODULE_NOTIFY_TYPE_CHANGED,
KeySpace_NotificationBitmapTransition) != REDISMODULE_OK){
return REDISMODULE_ERR;
}
if(RedisModule_SubscribeToServerEvent(ctx, RedisModuleEvent_Key,
BitmapTransitionKeyUnlink) != REDISMODULE_OK){
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx,"keyspace.notify", cmdNotify,"",0,0,0) == REDISMODULE_ERR){
return REDISMODULE_ERR;
}
@@ -557,6 +853,30 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx, "keyspace.string_callback_count", GetStringCallbackCountCommand, "", 0, 0, 0)== REDISMODULE_ERR){
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx, "keyspace.bitmap_callback_count", GetBitmapCallbackCountCommand, "", 0, 0, 0)== REDISMODULE_ERR){
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx, "keyspace.reset_bitmap_transition", ResetBitmapTransitionCommand, "", 0, 0, 0) == REDISMODULE_ERR){
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx, "keyspace.get_bitmap_transition", GetBitmapTransitionCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR){
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx, "keyspace.bitmap_transition_unlink_count", GetBitmapTransitionUnlinkCountCommand, "readonly", 0, 0, 0) == REDISMODULE_ERR){
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx, "keyspace.key_type", GetKeyTypeCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR){
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx, "keyspace.subscribe_subkeys", cmdSubscribeSubkeys, "", 0, 0, 0) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
+37
View File
@@ -178,6 +178,41 @@ int test_keyexists(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return RedisModule_ReplyWithBool(ctx, exists);
}
const char *test_keytype_name(int type) {
switch (type) {
case REDISMODULE_KEYTYPE_EMPTY: return "empty";
case REDISMODULE_KEYTYPE_STRING: return "string";
case REDISMODULE_KEYTYPE_LIST: return "list";
case REDISMODULE_KEYTYPE_HASH: return "hash";
case REDISMODULE_KEYTYPE_SET: return "set";
case REDISMODULE_KEYTYPE_ZSET: return "zset";
case REDISMODULE_KEYTYPE_MODULE: return "module";
case REDISMODULE_KEYTYPE_STREAM: return "stream";
case REDISMODULE_KEYTYPE_ARRAY: return "array";
case REDISMODULE_KEYTYPE_BITMAP: return "bitmap";
default: return "unknown";
}
}
int test_keyinfo(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc != 2) return RedisModule_WrongArity(ctx);
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
int type = RedisModule_KeyType(key);
size_t len = RedisModule_ValueLength(key);
size_t dma_len = 0;
char *dma = RedisModule_StringDMA(key, &dma_len, REDISMODULE_READ);
int truncate = RedisModule_StringTruncate(key, len);
RedisModule_ReplyWithArray(ctx, 4);
RedisModule_ReplyWithCString(ctx, test_keytype_name(type));
RedisModule_ReplyWithLongLong(ctx, (long long)len);
RedisModule_ReplyWithLongLong(ctx, dma != NULL);
RedisModule_ReplyWithLongLong(ctx, truncate == REDISMODULE_OK);
RedisModule_CloseKey(key);
return REDISMODULE_OK;
}
RedisModuleKey *open_key_or_reply(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode) {
RedisModuleKey *key = RedisModule_OpenKey(ctx, keyname, mode);
if (!key) {
@@ -587,6 +622,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"test.keyexists", test_keyexists,"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"test.keyinfo", test_keyinfo,"write",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"test.setlru", test_setlru,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"test.getlru", test_getlru,"",0,0,0) == REDISMODULE_ERR)
+83 -3
View File
@@ -342,6 +342,36 @@ proc randomKey {} {
}
}
# Creates an empty Roaring bitmap for a missing key, or re-encodes an existing
# string bitmap without changing its logical value or TTL. BITCONVERT is an
# internal replay primitive, so temporarily mark the test connection internal
# and always restore its ordinary-client state. Callers must pass a connection
# that is not already marked internal.
proc convert_string_bitmap_to_roaring {client key} {
set code [catch {
{*}$client debug mark-internal-client
{*}$client bitconvert $key
} result opts]
set cleanup_code [catch {
{*}$client debug mark-internal-client unmark
} cleanup_result cleanup_opts]
if {$code != 0} {
return -options $opts $result
}
if {$cleanup_code != 0} {
return -options $cleanup_opts $cleanup_result
}
return $result
}
proc empty_roaring_bitmap_dump_payload {} {
# RDB_TYPE_BITMAP, zero logical byte length, an eight-byte portable
# Roaring payload with zero high-32 buckets, RDB_VERSION 16, followed by
# an all-zero checksum. RESTORE accepts the zero checksum in test-built
# payloads.
return [binary format H* 210008000000000000000010000000000000000000]
}
proc findKeyWithType {r type} {
for {set j 0} {$j < 20} {incr j} {
set k [{*}$r randomkey]
@@ -384,8 +414,8 @@ proc createComplexDataset {r ops {opt {}}} {
# their own keys because Redis deletes a list/set/zset/hash once its last
# element is gone, which frees the name to be reused as another type, while
# an empty stream stays. The test code never deletes such a stream, on purpose:
# together with strings, streams are the only type that can exist with zero
# elements, and they give the dataset its zero-length coverage, like the
# together with strings and bitmaps, streams can exist with zero elements,
# and they give the dataset its zero-length coverage, like the
# size-0 bucket of the INFO keysizes histograms. The 20 keys for streams also
# mean each stream gets many operations, with "*" keeping its IDs increasing.
if {rand() < 0.2} {
@@ -439,6 +469,10 @@ proc createComplexDataset {r ops {opt {}}} {
# template-encoded hash. Short/long values naturally produce
# both TMPL_LP and TMPL_ARRAY encodings.
{*}$r himport set $k fs $v [randomValue] [randomValue]
} {
# Start with a native zero-length bitmap. Its later writes stay
# within 1 KiB so complex-data stress tests remain bounded.
{*}$r restore $k 0 [empty_roaring_bitmap_dump_payload]
} {
{*}$r del $k
}
@@ -494,6 +528,16 @@ proc createComplexDataset {r ops {opt {}}} {
{*}$r hexpire $k 1000 FIELDS 1 $f
}
}
{bitmap} {
randpath {
{*}$r setbit $k [randomInt 8192] [randomInt 2]
} {
{*}$r bitfield $k SET u8 [randomInt 8185] [randomInt 256]
} {
# Preserve some empty bitmaps for zero-length coverage.
{*}$r bitcount $k
}
}
}
}
}
@@ -572,6 +616,9 @@ proc csvdump r {
}
append o "\n"
}
bitmap {
append o [csvstring [{*}$r debug bitmap-raw $k]] "\n"
}
}
}
}
@@ -912,7 +959,8 @@ proc generate_fuzzy_traffic_on_key {key type duration} {
set stream_commands {XACK XADD XCLAIM XDEL XGROUP XINFO XLEN XPENDING XRANGE XREAD XREADGROUP XREVRANGE XTRIM XDELEX XACKDEL XNACK}
set vset_commands {VADD VREM}
set array_commands {ARSET ARGET ARDEL ARCOUNT ARMSET ARMGET ARGETRANGE ARDELRANGE ARINFO}
set commands [dict create string $string_commands hash $hash_commands zset $zset_commands list $list_commands set $set_commands stream $stream_commands vectorset $vset_commands array $array_commands]
set bitmap_commands {SETBIT GETBIT BITCOUNT BITPOS BITOP BITFIELD BITFIELD_RO}
set commands [dict create string $string_commands hash $hash_commands zset $zset_commands list $list_commands set $set_commands stream $stream_commands vectorset $vset_commands array $array_commands bitmap $bitmap_commands]
if 0 {
set gcra_commands {GCRA}
dict set commands gcra $gcra_commands
@@ -1265,6 +1313,38 @@ proc config_get_set {param value {options {}}} {
return $config
}
# Enable active defrag until it has walked some keys, then raise
# active-defrag-threshold-lower above any fragmentation so the running cycle
# is the last one. Returns how many cycles ran, counted from the verbose
# "Active defrag done" line each cycle ends with, or 0 if this build cannot
# enable active defrag. Every counted cycle ran to completion, so per-cycle
# defrag stats are exact multiples of the returned count.
proc run_complete_defrag_cycles {} {
set old_loglevel [config_get_set loglevel verbose]
set old_threshold [lindex [r config get active-defrag-threshold-lower] 1]
set done_lines [count_log_message 0 "Active defrag done"]
set cycles 0
catch {r config set activedefrag yes}
if {[r config get activedefrag] eq "activedefrag yes"} {
wait_for_condition 500 100 {
[s active_defrag_key_hits] + [s active_defrag_key_misses] > 0
} else {
fail "defrag did not start"
}
r config set active-defrag-threshold-lower 1000
wait_for_condition 500 100 {
[s active_defrag_running] eq 0
} else {
fail "defrag did not finish its last cycle"
}
set cycles [expr {[count_log_message 0 "Active defrag done"] - $done_lines}]
r config set activedefrag no
}
r config set active-defrag-threshold-lower $old_threshold
r config set loglevel $old_loglevel
return $cycles
}
proc delete_lines_with_pattern {filename tmpfilename pattern} {
set fh_in [open $filename r]
set fh_out [open $tmpfilename w]
+40
View File
@@ -228,6 +228,27 @@ start_server {tags {"bitops"}} {
r get dest{t}
} "\x55\xff\x00\xaa"
test {BITOP NOT allows string inputs longer than proto-max-bulk-len} {
# A source can exceed the current proto-max-bulk-len if it was created
# under a larger limit; BITOP no longer rejects it, matching the
# historical behavior (it only fails on a genuine allocation failure).
set limit 1048576
set oldval [config_get_set proto-max-bulk-len [expr {$limit + 1}]]
r del bitop:not:big{t} bitop:not:out{t}
r setbit bitop:not:big{t} [expr {($limit + 1) * 8 - 1}] 1
r config set proto-max-bulk-len $limit
assert_equal [expr {$limit + 1}] [r bitop not bitop:not:out{t} bitop:not:big{t}]
assert_equal [expr {$limit + 1}] [r strlen bitop:not:out{t}]
assert_equal 1 [r getbit bitop:not:out{t} 0]
# Restore the limit before reading the high bit (GETBIT caps its offset
# at proto-max-bulk-len too).
r config set proto-max-bulk-len $oldval
assert_equal 0 [r getbit bitop:not:out{t} [expr {($limit + 1) * 8 - 1}]]
r del bitop:not:big{t} bitop:not:out{t}
}
test {BITOP NOT with multiple source keys} {
r set s{t} "\xaa\x00\xff\x55"
assert_error "ERR BITOP NOT*" { r bitop not dest{t} s{t} s{t} }
@@ -692,7 +713,9 @@ start_server {tags {"bitops"}} {
set bytes [expr (1 << 29) + 1]
set bitpos [expr (1 << 32)]
set oldval [lindex [r config get proto-max-bulk-len] 1]
set oldroaring [lindex [r config get bitmap-default-roaring] 1]
r config set proto-max-bulk-len $bytes
r config set bitmap-default-roaring no
r setbit mykey $bitpos 1
assert_equal $bytes [r strlen mykey]
assert_equal 1 [r getbit mykey $bitpos]
@@ -710,7 +733,24 @@ start_server {tags {"bitops"}} {
assert_equal [expr $bitpos + 8] [r bitcount mykey]
assert_equal -1 [r bitpos mykey 0 0 [expr $bytes - 1]]
}
# A sparse Roaring source one byte beyond UINT32_MAX remains usable
# when proto-max-bulk-len permits it.
r del bitop:wide-source{t}
r config set bitmap-default-roaring yes
r setbit bitop:wide-source{t} 0 1
r setbit bitop:wide-source{t} $bitpos 1
r set bitop:wide:dest{t} sentinel
assert_equal $bytes [r bitop or bitop:wide:dest{t} bitop:wide-source{t}]
assert_equal string [r type mykey]
assert_equal $bytes [r strlen mykey]
assert_equal bitmap [r type bitop:wide:dest{t}]
assert_equal 1 [r getbit bitop:wide:dest{t} 0]
assert_equal 1 [r getbit bitop:wide:dest{t} $bitpos]
r config set bitmap-default-roaring $oldroaring
r config set proto-max-bulk-len $oldval
r del bitop:wide:dest{t} bitop:wide-source{t}
r del mykey
} {1} {large-memory}
File diff suppressed because it is too large. Load diff
@@ -3612,6 +3612,49 @@ start_cluster 3 3 {tags {external:skip cluster} overrides {cluster-node-timeout
wait_for_asm_done
}
test "ASM forwards config-selected BITOP representation" {
R 0 flushall
R 1 flushall
set source1 [slot_key 0 bitop:s1]
set source2 [slot_key 0 bitop:s2]
set dest [slot_key 0 bitop:out]
set old_source_default [lindex [R 0 config get bitmap-default-roaring] 1]
set old_dest_default [lindex [R 1 config get bitmap-default-roaring] 1]
R 0 config set bitmap-default-roaring yes
R 1 config set bitmap-default-roaring no
R 0 set $source1 [binary format H* f0]
R 0 set $source2 [binary format H* 0f]
# Keep the slot in the streaming phase while the live BITOP is issued.
set task_id [setup_slot_migration_with_delay 0 1 0 0 2 1000000]
assert_equal 1 [R 0 bitop or $dest $source1 $source2]
assert_equal bitmap [R 0 type $dest]
set source_digest [R 0 debug digest-value $source1 $source2 $dest]
wait_for_asm_done
R 0 config set rdb-key-save-delay 0
# BITROAROP carries the destination and sources through the import
# stream, preserving the source-selected representation despite the
# destination's opposite local default.
assert_equal bitmap [R 1 type $dest]
assert_equal [binary format H* ff] [R 1 debug bitmap-raw $dest]
assert_equal $source_digest [R 1 debug digest-value $source1 $source2 $dest]
wait_for_ofs_sync [Rn 1] [Rn 4]
R 4 readonly
assert_equal bitmap [R 4 type $dest]
assert_equal $source_digest [R 4 debug digest-value $source1 $source2 $dest]
R 0 config set bitmap-default-roaring $old_source_default
R 1 config set bitmap-default-roaring $old_dest_default
# Restore slot ownership for the following tests.
R 0 CLUSTER MIGRATION IMPORT 0 0
wait_for_asm_done
}
test "ASM stress: 4000 template keys, half streamed live during migration" {
# Create template keys on node 0 in slot 0. Each template is shared by 2 keys
proc asm_load_tmpl_keys {lo hi} {
+84
View File
@@ -0,0 +1,84 @@
source tests/support/cluster.tcl
start_cluster 3 0 {tags {external:skip cluster bitmap bitmap-roaring}} {
test "Roaring bitmap BITOP works with hash-slot-tagged keys" {
set slot [R 0 cluster keyslot "{bitop}foo"]
foreach key {"{bitop}bar" "{bitop}dest" "{bitop}not"} {
assert_equal $slot [R 0 cluster keyslot $key]
}
set port [srv 0 port]
set cluster [redis_cluster 127.0.0.1:$port]
array set node [$cluster masternode_for_slot $slot]
set owner $node(link)
assert_equal 0 [$owner setbit "{bitop}foo" 1 1]
assert_equal 0 [$owner setbit "{bitop}bar" 2 1]
assert_equal OK [convert_string_bitmap_to_roaring $owner "{bitop}foo"]
assert_equal OK [convert_string_bitmap_to_roaring $owner "{bitop}bar"]
assert_equal bitmap [$owner type "{bitop}foo"]
assert_equal bitmap-roaring [$owner object encoding "{bitop}foo"]
assert_equal 1 [$owner bitop or "{bitop}dest" "{bitop}foo" "{bitop}bar"]
assert_equal bitmap [$owner type "{bitop}dest"]
assert_equal bitmap-roaring [$owner object encoding "{bitop}dest"]
assert_equal 1 [$owner getbit "{bitop}dest" 1]
assert_equal 1 [$owner getbit "{bitop}dest" 2]
assert_equal 1 [$owner bitop not "{bitop}not" "{bitop}foo"]
assert_equal bitmap [$owner type "{bitop}not"]
assert_equal bitmap-roaring [$owner object encoding "{bitop}not"]
assert_equal 1 [$owner getbit "{bitop}not" 0]
assert_equal 0 [$owner getbit "{bitop}not" 1]
assert_equal 1 [$owner getbit "{bitop}foo" 1]
assert_equal PONG [$owner ping]
$cluster close
}
}
start_cluster 3 0 {tags {external:skip cluster bitmap bitmap-roaring}} {
test "MIGRATE moves a Roaring bitmap key between nodes" {
set key "{bitmig}bm"
set slot [R 0 cluster keyslot $key]
set port [srv 0 port]
set cluster [redis_cluster 127.0.0.1:$port]
array set node [$cluster masternode_for_slot $slot]
set owner $node(link)
set owner_id [$owner cluster myid]
# Pick any other master as the target.
set target -1
for {set i 0} {$i < 3} {incr i} {
if {[R $i cluster myid] ne $owner_id} {
set target $i
break
}
}
set target_id [R $target cluster myid]
set target_port [srv [expr {0 - $target}] port]
$owner config set bitmap-default-roaring yes
$owner del $key
$owner setbit $key 5 1
$owner setbit $key 100000 1
$owner config set bitmap-default-roaring no
assert_equal bitmap [$owner type $key]
assert_equal OK [R $target cluster setslot $slot importing $owner_id]
assert_equal OK [$owner cluster setslot $slot migrating $target_id]
assert_equal OK [$owner migrate 127.0.0.1 $target_port $key 0 5000]
assert_equal OK [R $target cluster setslot $slot node $target_id]
assert_equal OK [$owner cluster setslot $slot node $target_id]
assert_equal bitmap [R $target type $key]
assert_equal bitmap-roaring [R $target object encoding $key]
assert_equal 2 [R $target bitcount $key]
assert_equal 1 [R $target getbit $key 5]
assert_equal 1 [R $target getbit $key 100000]
$cluster close
}
}
+111 -5
View File
@@ -9,7 +9,7 @@
# cmd - A command that should be run before the verification.
# expOutput - This is a string that represents the expected output abbreviated.
# Instead of the output of "strings_len_exp_distrib" write "STR".
# Similarly for LIST, SET, ZSET, HASH and STREAM. Spaces and
# Similarly for LIST, SET, ZSET, HASH, STREAM and BITMAP. Spaces and
# newlines are ignored.
#
# Alternatively, you can set "__EVAL_DB_HIST__". The function
@@ -34,6 +34,7 @@ proc run_cmd_verify_hist {cmd expOutput {waitCond 0}} {
# Replace all placeholders with the actual values. Remove spaces & newlines.
set res [string map {
"BITMAP" "distrib_bitmaps_sizes"
"STREAM" "distrib_streams_items"
"STR" "distrib_strings_sizes"
"LIST" "distrib_lists_items"
@@ -118,6 +119,10 @@ proc eval_db_histogram {server dbid} {
set value [$server xlen $key]
set type "STREAM"
}
"bitmap" {
set value [string length [$server debug bitmap-raw $key]]
set type "BITMAP"
}
default {
continue ; # Skip unknown types
}
@@ -131,7 +136,7 @@ proc eval_db_histogram {server dbid} {
}
set result {}
foreach type {STR LIST SET ZSET HASH STREAM} {
foreach type {STR LIST SET ZSET HASH STREAM BITMAP} {
if {[array exists type_counts] && [array names type_counts $type,*] ne ""} {
set sorted_powers [lsort -integer [lmap item [array names type_counts $type,*] {
lindex [split $item ,] 1 ; # Extracts only the numeric part
@@ -515,7 +520,7 @@ proc test_all_keysizes { {replMode 0} } {
run_cmd_verify_hist {$server FLUSHALL} {}
createComplexDataset $server 1000 {useexpire usehexpire}
run_cmd_verify_hist {} {__EVAL_DB_HIST__ 0} 1
} {} {cluster:skip}
} {} {cluster:skip needs:debug}
start_server {tags {"cluster:skip" "external:skip" "needs:debug"}} {
test "KEYSIZES - Test DEBUG KEYSIZES-HIST-ASSERT command" {
@@ -726,6 +731,28 @@ proc test_all_keysizes { {replMode 0} } {
run_cmd_verify_hist {$server bitfield b3 set u8 65 255} {db0_STR:8=1}
run_cmd_verify_hist {$server bitfield b4 set u8 1000 255} {db0_STR:8=1,64=1}
} {} {cluster:skip}
test "KEYSIZES - Native bitmap byte lengths are tracked $suffixRepl" {
run_cmd_verify_hist {$server FLUSHALL} {}
set old_bitmap_default [lindex [$server config get bitmap-default-roaring] 1]
$server config set bitmap-default-roaring yes
# SETBIT walks logical byte lengths across histogram boundaries. A zero
# write beyond the end still extends a native bitmap's logical length.
run_cmd_verify_hist {$server SETBIT bitmap:hist 0 1} {db0_BITMAP:1=1}
run_cmd_verify_hist {$server SETBIT bitmap:hist 63 1} {db0_BITMAP:8=1}
run_cmd_verify_hist {$server SETBIT bitmap:hist 64 1} {db0_BITMAP:8=1}
run_cmd_verify_hist {$server SETBIT bitmap:hist 127 0} {db0_BITMAP:16=1}
# BITFIELD uses the same native mutation accounting, including writes
# of zero that only extend the logical length.
run_cmd_verify_hist {$server BITFIELD bitmap:hist SET u8 256 1} {db0_BITMAP:32=1}
run_cmd_verify_hist {$server BITFIELD bitmap:hist SET u8 1024 0} {db0_BITMAP:128=1}
# Async deletion removes the bitmap row on both primary and replica.
run_cmd_verify_hist {$server UNLINK bitmap:hist} {} 1
$server config set bitmap-default-roaring $old_bitmap_default
} {OK} {cluster:skip}
test "KEYSIZES - Test RESTORE $suffixRepl" {
run_cmd_verify_hist {$server FLUSHALL} {}
@@ -823,7 +850,7 @@ proc test_all_keysizes { {replMode 0} } {
} {} {cluster:skip needs:debug}
test "KEYSIZES - Untracked types stay out of the histogram $suffixRepl" {
# Only strings, the four collections and streams own a histogram row.
# Only strings, the four collections, streams and bitmaps own a histogram row.
# A key of any other type (here an array) must not be counted into some
# other type's row, neither when created nor when modified in place.
run_cmd_verify_hist {$server FLUSHALL} {}
@@ -881,7 +908,8 @@ start_server {} {
# The key memory histogram (distrib_*_sizes) requires key-memory-histograms or
# cluster-slot-stats-enabled to be set on startup (which enables memory_tracking).
#
# Note: Strings are not tracked to avoid confusion with distrib_strings_sizes.
# Note: String and bitmap allocation rows are not exposed because their logical
# byte histograms already use the conventional distrib_*_sizes field names.
################################################################################
# Query and Strip result of "info keysizes" from header, spaces, and newlines,
@@ -951,6 +979,80 @@ start_server {tags {external:skip needs:debug} overrides {key-memory-histograms
verify_keymem_empty r
}
if {[s arch_bits] == 64} {
test "KEYSIZES - Sparse native bitmap saturates into the final histogram bin" {
r SELECT 0
r FLUSHALL
set bitmap_len [expr {1 << 59}]
set bit_offset [expr {($bitmap_len - 1) * 8}]
set old_bulk_limit [config_get_set proto-max-bulk-len $bitmap_len]
set old_bitmap_default [config_get_set bitmap-default-roaring yes]
assert_equal OK [r DEBUG KEYSIZES-HIST-ASSERT 1]
# Writing zero at the end changes only the logical length, so this
# exercises the final histogram bin without a matching allocation.
# The length exceeds the final bin's range and saturates into it.
assert_equal 0 [r SETBIT bitmap:hist:top $bit_offset 0]
assert_equal bitmap [r TYPE bitmap:hist:top]
assert_match "*db0_distrib_bitmaps_sizes:256P=1*" [r INFO KEYSIZES]
assert_equal 1 [r DEL bitmap:hist:top]
assert_equal OK [r DEBUG KEYSIZES-HIST-ASSERT 0]
r CONFIG SET bitmap-default-roaring $old_bitmap_default
r CONFIG SET proto-max-bulk-len $old_bulk_limit
}
}
test "KEY-MEMORY-STATS - BITFIELD Roaring transitions keep histograms consistent" {
r SELECT 0
r FLUSHALL
assert_equal OK [r DEBUG KEYSIZES-HIST-ASSERT 1]
assert_equal OK [r DEBUG ALLOCSIZE-SLOTS-ASSERT 1]
assert_equal OK [r config set bitmap-default-roaring yes]
# Exercise both construction paths: BITFIELD can create a new native
# bitmap or convert an existing string in place.
assert_equal {0} [r bitfield bitmap_new SET u1 65536 1]
assert_equal bitmap [r type bitmap_new]
r set "bitmap:keymem:bf:convert" ""
assert_equal {0} [r bitfield "bitmap:keymem:bf:convert" SET u1 65536 1]
assert_equal bitmap [r type "bitmap:keymem:bf:convert"]
# COPY, RESTORE, and BITOP each install a bitmap through different
# keyspace/accounting paths.
assert_equal 1 [r copy bitmap_new "bitmap:keymem:copy"]
r restore "bitmap:keymem:restore" 0 [r dump bitmap_new]
r bitop or "bitmap:keymem:bitop" bitmap_new
assert_match "*db0_distrib_bitmaps_sizes:8K=5*" [r INFO KEYSIZES]
foreach key {
bitmap_new
bitmap:keymem:copy
bitmap:keymem:restore
bitmap:keymem:bitop
} {
# Growing every installed value validates both the logical-size
# histogram and per-slot allocation accounting update paths.
assert_equal 0 [r setbit $key 131072 1]
}
assert_match "*db0_distrib_bitmaps_sizes:8K=1,16K=4*" [r INFO KEYSIZES]
# RDB loading reconstructs both histogram rows through the generic
# key-install path. Allocation accounting is checked internally.
assert_equal OK [r DEBUG RELOAD]
assert_match "*db0_distrib_bitmaps_sizes:8K=1,16K=4*" [r INFO KEYSIZES]
assert_equal 1 [r DEL "bitmap:keymem:bf:convert"]
assert_match "*db0_distrib_bitmaps_sizes:16K=4*" [r INFO KEYSIZES]
assert_equal OK [r DEBUG ALLOCSIZE-SLOTS-ASSERT 0]
assert_equal OK [r DEBUG KEYSIZES-HIST-ASSERT 0]
assert_equal OK [r config set bitmap-default-roaring no]
r FLUSHALL
verify_keymem_empty r
}
test "KEY-MEMORY-STATS - Histogram bins should use power-of-2 labels" {
r FLUSHALL
r HSET "hash" f1 v1
@@ -1117,11 +1219,15 @@ start_server {tags {external:skip} overrides {key-memory-histograms no}} {
r SADD "set" x y z
r ZADD "zset" 1 a 2 b
r HSET "hash" f1 v1
set old_bitmap_default [config_get_set bitmap-default-roaring yes]
r SETBIT "bitmap" 63 1
r CONFIG SET bitmap-default-roaring $old_bitmap_default
set info [r info keysizes]
# Keysizes (sizes/items) should be present
assert {[string match "*distrib_strings_sizes*" $info]}
assert {[string match "*distrib_lists_items*" $info]}
assert {[string match "*distrib_bitmaps_sizes*" $info]}
# Key memory histogram should NOT be present (note: lists_sizes
# is only present when memory tracking is enabled, but lists_items always is)
set stripped [get_info_keymem_stripped r]
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff. Show more