Enhance performance of rz_bv_copy_nbits for large to small and small to large bv copies (#5551)

* Implement optimized functions for small to large and large to small

* Add benchmark instructions in testREADME.md

* Use the term unaligned consistently instead of nonaligned

* Slightly improve bitvector test error message

* Remove unnecessary annotations

* Edit readme

* Add explicit fallthrough comments

* Return statement for rz_bv_toggle_all()

* Extend tests
This commit is contained in:
Anton Angelov 2025-11-29 18:48:36 +02:00 committed by GitHub
parent bb483baedd
commit 9d3109888e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 198 additions and 12 deletions

View file

@ -240,10 +240,118 @@ static ut32 rz_bv_copy_nbits_large_aligned(const RzBitVector *src, ut32 src_star
return nbit;
}
/**
* \brief Optimized version of rz_bv_copy_nbits() for copying bit range from a large bitvector to a small one
*/
static ut32 rz_bv_copy_nbits_large_to_small(const RzBitVector *src, ut32 src_start_pos, RzBitVector *dst, ut32 dst_start_pos, ut32 nbit) {
ut64 buffer = 0;
ut8 start_bits = RZ_MIN((BV_ELEM_SIZE - src_start_pos) % BV_ELEM_SIZE, nbit);
ut32 byte_index = (src_start_pos + start_bits) / BV_ELEM_SIZE;
switch ((nbit - start_bits + 7) / BV_ELEM_SIZE) {
case 8:
buffer |= ((ut64)src->bits.large_a[byte_index + 7]) << (BV_ELEM_SIZE * 7);
// fallthrough
case 7:
buffer |= ((ut64)src->bits.large_a[byte_index + 6]) << (BV_ELEM_SIZE * 6);
// fallthrough
case 6:
buffer |= ((ut64)src->bits.large_a[byte_index + 5]) << (BV_ELEM_SIZE * 5);
// fallthrough
case 5:
buffer |= ((ut64)src->bits.large_a[byte_index + 4]) << (BV_ELEM_SIZE * 4);
// fallthrough
case 4:
buffer |= ((ut64)src->bits.large_a[byte_index + 3]) << (BV_ELEM_SIZE * 3);
// fallthrough
case 3:
buffer |= ((ut64)src->bits.large_a[byte_index + 2]) << (BV_ELEM_SIZE * 2);
// fallthrough
case 2:
buffer |= ((ut64)src->bits.large_a[byte_index + 1]) << (BV_ELEM_SIZE);
// fallthrough
case 1:
buffer |= ((ut64)src->bits.large_a[byte_index]);
// fallthrough
case 0:
break;
default:
rz_warn_if_reached();
return 0;
}
if (start_bits > 0) {
// Handle start bits
buffer = rz_bits_copy_ut64(src->bits.large_a[src_start_pos / BV_ELEM_SIZE], (src_start_pos % BV_ELEM_SIZE), buffer << start_bits, 0, start_bits);
}
dst->bits.small_u = rz_bits_copy_ut64(buffer, 0, dst->bits.small_u, dst_start_pos, nbit);
return nbit;
}
/**
* \brief Optimized version of rz_bv_copy_nbits() for copying bit range from a small bitvector to a large one
*/
static ut32 rz_bv_copy_nbits_small_to_large(const RzBitVector *src, ut32 src_start_pos, RzBitVector *dst, ut32 dst_start_pos, ut32 nbit) {
ut64 byte_index = dst_start_pos / BV_ELEM_SIZE;
ut8 start_bits = RZ_MIN((BV_ELEM_SIZE - dst_start_pos) % BV_ELEM_SIZE, nbit);
ut8 trailing_bits = RZ_MIN((dst_start_pos + nbit) % BV_ELEM_SIZE, nbit - start_bits);
ut8 middle_bits = nbit - start_bits - trailing_bits;
ut64 buffer = src->bits.small_u >> src_start_pos;
// Handle unaligned start bits
if (start_bits > 0) {
dst->bits.large_a[byte_index] = rz_bits_copy_ut8(buffer, 0, dst->bits.large_a[byte_index], dst_start_pos % BV_ELEM_SIZE, start_bits);
byte_index++;
buffer >>= start_bits;
}
// Handle unaligned trailing bits
if (trailing_bits > 0) {
ut64 trailing_byte_index = (dst_start_pos + nbit) / BV_ELEM_SIZE;
dst->bits.large_a[trailing_byte_index] = rz_bits_copy_ut8(buffer >> middle_bits, 0, dst->bits.large_a[trailing_byte_index], 0, trailing_bits);
}
// Handle middle bytes
switch (middle_bits / BV_ELEM_SIZE) {
case 8:
dst->bits.large_a[byte_index + 7] = (buffer >> BV_ELEM_SIZE * 7) & UT8_MAX;
// fallthrough
case 7:
dst->bits.large_a[byte_index + 6] = (buffer >> BV_ELEM_SIZE * 6) & UT8_MAX;
// fallthrough
case 6:
dst->bits.large_a[byte_index + 5] = (buffer >> BV_ELEM_SIZE * 5) & UT8_MAX;
// fallthrough
case 5:
dst->bits.large_a[byte_index + 4] = (buffer >> BV_ELEM_SIZE * 4) & UT8_MAX;
// fallthrough
case 4:
dst->bits.large_a[byte_index + 3] = (buffer >> BV_ELEM_SIZE * 3) & UT8_MAX;
// fallthrough
case 3:
dst->bits.large_a[byte_index + 2] = (buffer >> BV_ELEM_SIZE * 2) & UT8_MAX;
// fallthrough
case 2:
dst->bits.large_a[byte_index + 1] = (buffer >> BV_ELEM_SIZE) & UT8_MAX;
// fallthrough
case 1:
dst->bits.large_a[byte_index] = buffer & UT8_MAX;
// fallthrough
case 0:
break;
default:
rz_warn_if_reached();
return 0;
}
return nbit;
}
/**
* \brief Optimized version of rz_bv_copy_nbits() for large bitvectors (more than 64 bits) with unaligned bit positions
*/
static ut32 rz_bv_copy_nbits_large_nonaligned(const RzBitVector *src, ut32 src_start_pos, RzBitVector *dst, ut32 dst_start_pos, ut32 nbit) {
static ut32 rz_bv_copy_nbits_large_unaligned(const RzBitVector *src, ut32 src_start_pos, RzBitVector *dst, ut32 dst_start_pos, ut32 nbit) {
// Sanity check performed by caller
ut64 bits_remaining = nbit;
@ -308,26 +416,25 @@ RZ_API ut32 rz_bv_copy_nbits(RZ_NONNULL const RzBitVector *src, ut32 src_start_p
}
if (src->bits.large_a != dst->bits.large_a) {
return rz_bv_copy_nbits_large_nonaligned(src, src_start_pos, dst, dst_start_pos, nbit);
return rz_bv_copy_nbits_large_unaligned(src, src_start_pos, dst, dst_start_pos, nbit);
}
// Use a temporary bitvector for same-vector copies
RzBitVector *temp = rz_bv_new(rz_bv_len(dst));
rz_bv_copy(dst, temp);
ut32 bits_copied = rz_bv_copy_nbits_large_nonaligned(src, src_start_pos, temp, dst_start_pos, nbit);
ut32 bits_copied = rz_bv_copy_nbits_large_unaligned(src, src_start_pos, temp, dst_start_pos, nbit);
rz_bv_copy(temp, dst);
rz_bv_free(temp);
return bits_copied;
}
// Only one of the bitvectors is large
// TODO: add specialized functions for large to small and small to large bit copy
for (ut32 i = 0; i < nbit; ++i) {
bool c = rz_bv_get(src, src_start_pos + i);
rz_bv_set(dst, dst_start_pos + i, c);
if (src->len > 64) {
// Large to small copy
return rz_bv_copy_nbits_large_to_small(src, src_start_pos, dst, dst_start_pos, nbit);
}
return nbit;
// Small to large
return rz_bv_copy_nbits_small_to_large(src, src_start_pos, dst, dst_start_pos, nbit);
}
/**
@ -514,6 +621,7 @@ RZ_API bool rz_bv_toggle_all(RZ_NONNULL RzBitVector *bv) {
rz_return_val_if_fail(bv, false);
if (bv->len <= 64) {
bv->bits.small_u = ~(bv->bits.small_u);
return true;
}
rz_return_val_if_fail(bv->bits.large_a, false);

View file

@ -9,6 +9,7 @@ Rizin uses both regression and unit tests.
* unit/: Unit tests (written in C, using minunit).
* fuzz/: Fuzzing helper scripts
* bins/: Sample binaries (fetched from the [external repository](https://github.com/rizinorg/rizin-testbins))
* bench/: Benchmarks
# Requirements
@ -46,6 +47,17 @@ to build Rizin).
You can run one specific testcase category (e.g. the whole `test_bin.c` file) using `meson test -C build bin`.
If you are using `meson test`, you should consider using the `--print-errorlogs` flag.
## Benchmarks
In order to be able to run the benchmarks, the `-Denable_benchmarks=true` switch needs to be specified
when setting up the build directory (e.g. `meson setup build -Denable_benchmarks=true`).
Afterwards use `ninja -C build test --benchmark` (or `meson test -C build --benchmark`)
to run the benchmarks from the top directory (replace `build` with the name of the directory
you used to build Rizin).
Running a specific set of benchmarks (e.g. `bitvector`) can be done with `ninja -C build test bitvector --benchmark`.
# Failure Levels
A test can have one of the following results:

View file

@ -1201,12 +1201,12 @@ static const char *test_rz_bv_copy_nbits_against_ref(const RzBitVector *src, ut3
rz_bv_copy(dst, dst_copy_ref);
if (rz_bv_copy_nbits(src_copy, src_pos, dst_copy, dst_pos, nbit) != nbit) {
error = "rz_bv_copy_nbits() incorrect return";
error = "rz_bv_copy_nbits() incorrect number of bits copied";
goto finally;
}
if (rz_bv_copy_nbits_ref(src_copy, src_pos, dst_copy_ref, dst_pos, nbit) != nbit) {
error = "rz_bv_copy_nbits_ref() incorrect result";
error = "rz_bv_copy_nbits_ref() incorrect number of bits copied";
goto finally;
}
@ -1221,7 +1221,7 @@ static const char *test_rz_bv_copy_nbits_against_ref(const RzBitVector *src, ut3
rz_bv_toggle_all(dst_copy);
if (rz_bv_copy_nbits(src_copy, src_pos, dst_copy, dst_pos, nbit) != nbit) {
error = "rz_bv_copy_nbits() incorrect result";
error = "rz_bv_copy_nbits() incorrect number of bits copied";
goto finally;
}
@ -1338,6 +1338,70 @@ bool test_rz_bv_copy_nbits_large_unaligned(void) {
mu_end;
}
bool test_rz_bv_copy_nbits_large_to_small(void) {
RzBitVector *a = rz_bv_new_from_ut64(128, 0x67452301);
RzBitVector *b = rz_bv_new_from_ut64(64, 0x0);
const char *error;
/// copy aligned
error = test_rz_bv_copy_nbits_against_ref(a, 8, b, 8, 16);
mu_assert_null(error, error);
/// copy unaligned
error = test_rz_bv_copy_nbits_against_ref(a, 1, b, 0, 31);
mu_assert_null(error, error);
/// copy 1 unaligned bit
error = test_rz_bv_copy_nbits_against_ref(a, 3, b, 5, 1);
mu_assert_null(error, error);
/// copy unaligned with dst start_bits > 0
error = test_rz_bv_copy_nbits_against_ref(a, 0, b, 1, 31);
mu_assert_null(error, error);
/// copy different bit sizes from 8 to 64 bits
for (ut8 size = 8; size <= 64; size += 8) {
error = test_rz_bv_copy_nbits_against_ref(a, size - 1, b, 0, size);
mu_assert_null(error, error);
}
rz_bv_free(a);
rz_bv_free(b);
mu_end;
}
bool test_rz_bv_copy_nbits_small_to_large(void) {
RzBitVector *a = rz_bv_new_from_ut64(64, 0x67452301);
RzBitVector *b = rz_bv_new_from_ut64(128, 0x0);
const char *error;
/// copy aligned
error = test_rz_bv_copy_nbits_against_ref(a, 8, b, 8, 16);
mu_assert_null(error, error);
/// copy unaligned
error = test_rz_bv_copy_nbits_against_ref(a, 1, b, 0, 31);
mu_assert_null(error, error);
/// copy 1 unaligned bit
error = test_rz_bv_copy_nbits_against_ref(a, 3, b, 5, 1);
mu_assert_null(error, error);
/// copy unaligned with dst start_bits > 0
error = test_rz_bv_copy_nbits_against_ref(a, 0, b, 1, 31);
mu_assert_null(error, error);
/// copy different bit sizes from 8 to 64 bits
for (ut8 size = 8; size <= 64; size += 8) {
error = test_rz_bv_copy_nbits_against_ref(a, 0, b, size - 1, size);
mu_assert_null(error, error);
}
rz_bv_free(a);
rz_bv_free(b);
mu_end;
}
bool test_rz_bv_extra_operations(void) {
// arithmetic rshift
RzBitVector *bv1 = rz_bv_new_from_ut64(32, 73 * 16);
@ -1458,6 +1522,8 @@ bool all_tests() {
mu_run_test(test_rz_bv_copy_nbits_small);
mu_run_test(test_rz_bv_copy_nbits_large_aligned);
mu_run_test(test_rz_bv_copy_nbits_large_unaligned);
mu_run_test(test_rz_bv_copy_nbits_small_to_large);
mu_run_test(test_rz_bv_copy_nbits_large_to_small);
mu_run_test(test_rz_bv_extra_operations);
return tests_passed != tests_run;