mirror of
https://github.com/redis/redis.git
synced 2026-10-28 08:25:04 +08:00
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:
1 parent
fcc9b536dd
commit
f3b9b68ec6
106 files changed
+45104
-344
No files matched your search
@@ -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
|
||||
|
||||
Vendored
+14
-1
@@ -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
|
||||
|
||||
Vendored
+31
@@ -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`).
|
||||
Vendored
+15
@@ -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
|
||||
Vendored
+236
@@ -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.
|
||||
Vendored
+73
@@ -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
|
||||
Vendored
+1327
File diff suppressed because it is too large.
Load diff
Vendored
+13
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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_ */
|
||||
+2708
File diff suppressed because it is too large.
Load diff
@@ -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_ */
|
||||
@@ -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
|
||||
@@ -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_ */
|
||||
@@ -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_ */
|
||||
@@ -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_ */
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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_ */
|
||||
+1387
File diff suppressed because it is too large.
Load diff
+897
@@ -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 */
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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_ */
|
||||
Vendored
+2168
File diff suppressed because it is too large.
Load diff
Vendored
+2399
File diff suppressed because it is too large.
Load diff
Vendored
+491
@@ -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
|
||||
Vendored
+1150
File diff suppressed because it is too large.
Load diff
Vendored
+586
@@ -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
|
||||
+1318
File diff suppressed because it is too large.
Load diff
+1142
File diff suppressed because it is too large.
Load diff
+354
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Vendored
+1163
File diff suppressed because it is too large.
Load diff
Vendored
+347
@@ -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
|
||||
Vendored
+52
@@ -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
|
||||
*/
|
||||
Vendored
+64
@@ -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); }
|
||||
Vendored
+3579
File diff suppressed because it is too large.
Load diff
Vendored
+3025
File diff suppressed because it is too large.
Load diff
Vendored
+819
@@ -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
@@ -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
|
||||
Vendored
+17
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
+1877
File diff suppressed because it is too large.
Load diff
+107
@@ -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
@@ -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]++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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). */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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} {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)}]]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -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} {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in new issue
Block a user