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:
parent
bb483baedd
commit
9d3109888e
3 changed files with 198 additions and 12 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue