From 2b817ec61d0af3d1564cacdfd766fde7d1dbad62 Mon Sep 17 00:00:00 2001 From: Sam Li Date: Thu, 12 Mar 2026 16:07:12 -0700 Subject: [PATCH 1/8] remove the use of a specialized AVX2 function sperr::any_ge_pow2(). It doesn't show much benefit because the vast majority arrays to evaluate are quite small. --- include/sperr_helper.h | 5 ----- src/SPECK2D_INT_ENC.cpp | 15 --------------- src/SPECK3D_INT_ENC.cpp | 4 ---- src/sperr_helper.cpp | 32 -------------------------------- 4 files changed, 56 deletions(-) diff --git a/include/sperr_helper.h b/include/sperr_helper.h index 1d602472..c9385f0c 100644 --- a/include/sperr_helper.h +++ b/include/sperr_helper.h @@ -184,11 +184,6 @@ auto chunk_volume(dims_type vol_dim, dims_type chunk_dim) -> std::vector auto calc_mean_var(const T*, size_t len, size_t omp_nthreads = 0) -> std::array; -#ifdef __AVX2__ -template -auto any_ge_pow2(const T* buf, size_t len, T threshold) -> bool; -#endif - }; // namespace sperr #endif diff --git a/src/SPECK2D_INT_ENC.cpp b/src/SPECK2D_INT_ENC.cpp index 83f6c741..bc2dee36 100644 --- a/src/SPECK2D_INT_ENC.cpp +++ b/src/SPECK2D_INT_ENC.cpp @@ -66,13 +66,8 @@ auto sperr::SPECK2D_INT_ENC::m_decide_S_significance(const Set2D& set) const for (auto y = set.start_y; y < (set.start_y + set.length_y); y++) { auto first = m_coeff_buf.data() + y * m_dims[0] + set.start_x; -#ifdef __AVX2__ - if (sperr::any_ge_pow2(first, set.length_x, m_threshold)) - return true; -#else if (std::any_of(first, first + set.length_x, [th = m_threshold](auto v) { return v >= th; })) return true; -#endif } return false; } @@ -86,26 +81,16 @@ auto sperr::SPECK2D_INT_ENC::m_decide_I_significance() const -> bool assert(m_I.length_x == m_dims[0]); auto first = m_coeff_buf.data() + size_t{m_I.start_y} * size_t{m_I.length_x}; auto len = m_coeff_buf.size() - size_t{m_I.start_y} * size_t{m_I.length_x}; -#ifdef __AVX2__ - if (sperr::any_ge_pow2(first, len, m_threshold)) - return true; -#else if (std::any_of(first, first + len, [thld = m_threshold](auto v) { return v >= thld; })) return true; -#endif // Second, test the rectangle that's directly to the right of the missing top-left corner. // len = m_dims[0] - m_I.start_x; for (auto y = 0u; y < m_I.start_y; y++) { first = m_coeff_buf.data() + y * m_dims[0] + m_I.start_x; -#ifdef __AVX2__ - if (sperr::any_ge_pow2(first, len, m_threshold)) - return true; -#else if (std::any_of(first, first + len, [thld = m_threshold](auto v) { return v >= thld; })) return true; -#endif } return false; } diff --git a/src/SPECK3D_INT_ENC.cpp b/src/SPECK3D_INT_ENC.cpp index 85a4dfdb..11c9e83b 100644 --- a/src/SPECK3D_INT_ENC.cpp +++ b/src/SPECK3D_INT_ENC.cpp @@ -167,12 +167,8 @@ void sperr::SPECK3D_INT_ENC::m_process_S(size_t idx1, size_t idx2, size_t& co // If need to output, it means the current set has unknown significance. if (output) { auto first = m_morton_buf.data() + set.get_morton(); -#ifdef __AVX2__ - is_sig = sperr::any_ge_pow2(first, set.num_elem(), m_threshold); -#else is_sig = std::any_of(first, first + set.num_elem(), [thld = m_threshold](auto v) { return v >= thld; }); -#endif m_bit_buffer.wbit(is_sig); } diff --git a/src/sperr_helper.cpp b/src/sperr_helper.cpp index 173631cd..a2543834 100644 --- a/src/sperr_helper.cpp +++ b/src/sperr_helper.cpp @@ -641,35 +641,3 @@ auto sperr::calc_mean_var(const T* arr, size_t len, size_t omp_nthreads) -> std: } template auto sperr::calc_mean_var(const float*, size_t, size_t) -> std::array; template auto sperr::calc_mean_var(const double*, size_t, size_t) -> std::array; - -#ifdef __AVX2__ -template -auto sperr::any_ge_pow2(const T* buf, size_t len, T thld) -> bool -{ - assert((thld > 0) && (thld & (thld - 1)) == 0); - - const size_t simd_width = 32 / sizeof(T); - T mask_val = ~(thld - 1); - __m256i mask_vec; - if constexpr (sizeof(T) == 8) - mask_vec = _mm256_set1_epi64x(mask_val); - else if constexpr (sizeof(T) == 4) - mask_vec = _mm256_set1_epi32(mask_val); - else if constexpr (sizeof(T) == 2) - mask_vec = _mm256_set1_epi16(mask_val); - else - mask_vec = _mm256_set1_epi8(mask_val); - - size_t i = 0; - for (; i + simd_width <= len; i += simd_width) { - auto data_vec = _mm256_loadu_si256(reinterpret_cast(buf + i)); - if (!_mm256_testz_si256(data_vec, mask_vec)) - return true; - } - return std::any_of(buf + i, buf + len, [thld](auto v) { return v >= thld; }); -} -template auto sperr::any_ge_pow2(const uint8_t*, size_t, uint8_t) -> bool; -template auto sperr::any_ge_pow2(const uint16_t*, size_t, uint16_t) -> bool; -template auto sperr::any_ge_pow2(const uint32_t*, size_t, uint32_t) -> bool; -template auto sperr::any_ge_pow2(const uint64_t*, size_t, uint64_t) -> bool; -#endif From bd6306945113c03d0591d025947b3f23106883a4 Mon Sep 17 00:00:00 2001 From: Sam Li Date: Fri, 13 Mar 2026 21:18:36 -0700 Subject: [PATCH 2/8] also remove the unit test for sperr::any_ge_pow2() --- test_scripts/sperr_helper_unit_test.cpp | 35 ------------------------- 1 file changed, 35 deletions(-) diff --git a/test_scripts/sperr_helper_unit_test.cpp b/test_scripts/sperr_helper_unit_test.cpp index 9b18574d..ef273be0 100644 --- a/test_scripts/sperr_helper_unit_test.cpp +++ b/test_scripts/sperr_helper_unit_test.cpp @@ -293,39 +293,4 @@ TEST(sperr_helper, read_sections) EXPECT_EQ(buf, buf2); } -#ifdef __AVX2__ -TEST(sperr_helper, any_ge_pow2) -{ - std::vector vec8(100, 0); - EXPECT_FALSE(sperr::any_ge_pow2(vec8.data(), vec8.size(), (uint8_t)1)); - vec8[50] = 1; - EXPECT_TRUE(sperr::any_ge_pow2(vec8.data(), vec8.size(), (uint8_t)1)); - vec8[50] = 0; - - // Power of 2 threshold - EXPECT_FALSE(sperr::any_ge_pow2(vec8.data(), vec8.size(), (uint8_t)4)); - vec8[99] = 3; - EXPECT_FALSE(sperr::any_ge_pow2(vec8.data(), vec8.size(), (uint8_t)4)); - vec8[99] = 4; - EXPECT_TRUE(sperr::any_ge_pow2(vec8.data(), vec8.size(), (uint8_t)4)); - vec8[99] = 5; - EXPECT_TRUE(sperr::any_ge_pow2(vec8.data(), vec8.size(), (uint8_t)4)); - - // Larger types - std::vector vec64(50, 0); - uint64_t thld = 64; - EXPECT_FALSE(sperr::any_ge_pow2(vec64.data(), vec64.size(), thld)); - vec64[25] = 100; - EXPECT_TRUE(sperr::any_ge_pow2(vec64.data(), vec64.size(), thld)); - - thld = 1024; // Power of 2 - vec64.assign(50, 0); - EXPECT_FALSE(sperr::any_ge_pow2(vec64.data(), vec64.size(), thld)); - vec64[0] = 1023; - EXPECT_FALSE(sperr::any_ge_pow2(vec64.data(), vec64.size(), thld)); - vec64[0] = 1024; - EXPECT_TRUE(sperr::any_ge_pow2(vec64.data(), vec64.size(), thld)); -} -#endif - } // namespace From b908d825a72fd57899f4c32be4013f139bc9f6fb Mon Sep 17 00:00:00 2001 From: Sam Li Date: Fri, 13 Mar 2026 21:22:43 -0700 Subject: [PATCH 3/8] remove the use of std::remainder(), big performance win --- src/SPECK_FLT.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/SPECK_FLT.cpp b/src/SPECK_FLT.cpp index c9ae961d..01c9367a 100644 --- a/src/SPECK_FLT.cpp +++ b/src/SPECK_FLT.cpp @@ -243,10 +243,11 @@ auto sperr::SPECK_FLT::m_estimate_mse_midtread(double q) const -> double const size_t num_strides = len / stride_size; auto tmp_buf = vecd_type(num_strides + 1); + const auto rcp_q = 1.0 / q; for (size_t i = 0; i < num_strides; i++) { const auto beg = m_vals_d.cbegin() + i * stride_size; - tmp_buf[i] = std::accumulate(beg, beg + stride_size, 0.0, [q](auto init, auto v) { - auto diff = std::remainder(v, q); + tmp_buf[i] = std::accumulate(beg, beg + stride_size, 0.0, [q, rcp_q](auto init, auto v) { + auto diff = std::fma(-q, std::rint(v * rcp_q), v); return init + diff * diff; }); } @@ -254,8 +255,8 @@ auto sperr::SPECK_FLT::m_estimate_mse_midtread(double q) const -> double // Let's also process the last stride. tmp_buf[num_strides] = 0.0; tmp_buf[num_strides] = std::accumulate(m_vals_d.cbegin() + num_strides * stride_size, - m_vals_d.cend(), 0.0, [q](auto init, auto v) { - auto diff = std::remainder(v, q); + m_vals_d.cend(), 0.0, [q, rcp_q](auto init, auto v) { + auto diff = std::fma(-q, std::rint(v * rcp_q), v); return init + diff * diff; }); const auto total_sum = std::accumulate(tmp_buf.cbegin(), tmp_buf.cend(), 0.0); From aad6640a2c17b1caa58e34a1e0e461e3a2bb739f Mon Sep 17 00:00:00 2001 From: Sam Li Date: Sat, 14 Mar 2026 15:24:40 -0700 Subject: [PATCH 4/8] misc --- include/SPECK_INT.h | 4 ++-- src/SPECK3D_INT.cpp | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/include/SPECK_INT.h b/include/SPECK_INT.h index 1abd2a8a..45c801f8 100644 --- a/include/SPECK_INT.h +++ b/include/SPECK_INT.h @@ -73,11 +73,11 @@ class SPECK_INT { void m_refinement_pass_decode(); // Data members - uint8_t m_num_bitplanes = 0; - uint_type m_threshold = 0; uint64_t m_total_bits = 0; // The number of bits of a complete SPECK stream. uint64_t m_avail_bits = 0; // Decoding only. `m_avail_bits` <= `m_total_bits` size_t m_budget = std::numeric_limits::max(); + uint_type m_threshold = 0; + uint8_t m_num_bitplanes = 0; dims_type m_dims = {0, 0, 0}; vecui_type m_coeff_buf; diff --git a/src/SPECK3D_INT.cpp b/src/SPECK3D_INT.cpp index 763db790..e683ba24 100644 --- a/src/SPECK3D_INT.cpp +++ b/src/SPECK3D_INT.cpp @@ -330,6 +330,7 @@ auto sperr::SPECK3D_INT::m_partition_S_XY(Set3D set, uint16_t lev) const -> std::tuple, uint16_t> { // This partition scheme is only used during initialization; no need to calculate morton offset. + // The correct morton offset will be calculated during initialization. const auto split_x = std::array{set.length_x - set.length_x / 2, set.length_x / 2}; const auto split_y = std::array{set.length_y - set.length_y / 2, set.length_y / 2}; @@ -392,6 +393,7 @@ auto sperr::SPECK3D_INT::m_partition_S_Z(Set3D set, uint16_t lev) const -> std::tuple, uint16_t> { // This partition scheme is only used during initialization; no need to calculate morton offset. + // The correct morton offset will be calculated during initialization. const auto split_z = std::array{set.length_z - set.length_z / 2, set.length_z / 2}; if (split_z[1] != 0) From 7320bd2f7bea9ad39fef23497951280af2f75d9a Mon Sep 17 00:00:00 2001 From: Sam Li Date: Sat, 14 Mar 2026 19:44:33 -0700 Subject: [PATCH 5/8] keep MSB in m_morton_buf so that each value only takes 1 byte --- include/SPECK3D_INT_ENC.h | 11 +++++- include/SPECK_INT.h | 1 + src/SPECK3D_INT_ENC.cpp | 77 ++++++++++++++++++++++++++------------- src/SPECK_INT.cpp | 1 + 4 files changed, 63 insertions(+), 27 deletions(-) diff --git a/include/SPECK3D_INT_ENC.h b/include/SPECK3D_INT_ENC.h index 2ccb25e2..331e4768 100644 --- a/include/SPECK3D_INT_ENC.h +++ b/include/SPECK3D_INT_ENC.h @@ -35,10 +35,19 @@ class SPECK3D_INT_ENC final : public SPECK3D_INT { void m_process_P(size_t idx, size_t morton, size_t& counter, bool output) final; void m_process_P_lite(size_t idx) final; void m_additional_initialization() final; + void m_bitplane_init() final; // Data structures and functions for morton data layout. - vecui_type m_morton_buf; + // `m_morton_buf` stores the MSB bit position of each coefficient (via m_msb_position()), + // rather than the full coefficient value. This shrinks the buffer from sizeof(T) to 1 byte + // per element, reducing cache pressure in the significance-testing hot path (m_process_S). + std::vector m_morton_buf; + // `m_morton_threshold` is the MSB position of `m_threshold`, updated each bitplane via + // m_bitplane_init(). Significance tests compare m_morton_buf entries against this value. + int8_t m_morton_threshold = -1; void m_deposit_set(Set3D); + // Returns the bit position of the most significant bit (0-based), or -1 for zero. + auto m_msb_position(uint_type v) const -> int8_t; }; }; // namespace sperr diff --git a/include/SPECK_INT.h b/include/SPECK_INT.h index 45c801f8..592709aa 100644 --- a/include/SPECK_INT.h +++ b/include/SPECK_INT.h @@ -69,6 +69,7 @@ class SPECK_INT { virtual void m_clean_LIS() = 0; virtual void m_sorting_pass() = 0; virtual void m_initialize_lists() = 0; + virtual void m_bitplane_init() {} void m_refinement_pass_encode(); void m_refinement_pass_decode(); diff --git a/src/SPECK3D_INT_ENC.cpp b/src/SPECK3D_INT_ENC.cpp index 11c9e83b..2c8235e8 100644 --- a/src/SPECK3D_INT_ENC.cpp +++ b/src/SPECK3D_INT_ENC.cpp @@ -5,6 +5,10 @@ #include // std::memcpy() #include +#if __cplusplus >= 202002L +#include +#endif + template void sperr::SPECK3D_INT_ENC::m_deposit_set(Set3D set) { @@ -13,7 +17,7 @@ void sperr::SPECK3D_INT_ENC::m_deposit_set(Set3D set) return; case 1: { auto id = set.start_z * m_dims[0] * m_dims[1] + set.start_y * m_dims[0] + set.start_x; - m_morton_buf[set.get_morton()] = m_coeff_buf[id]; + m_morton_buf[set.get_morton()] = m_msb_position(m_coeff_buf[id]); return; } case 2: { @@ -22,7 +26,7 @@ void sperr::SPECK3D_INT_ENC::m_deposit_set(Set3D set) // Deposit the 1st element. auto id = set.start_z * m_dims[0] * m_dims[1] + set.start_y * m_dims[0] + set.start_x; auto morton_id = set.get_morton(); - m_morton_buf[morton_id] = m_coeff_buf[id]; + m_morton_buf[morton_id] = m_msb_position(m_coeff_buf[id]); // Deposit the 2nd element. if (set.length_x == 2) @@ -31,7 +35,7 @@ void sperr::SPECK3D_INT_ENC::m_deposit_set(Set3D set) id += m_dims[0]; else id += m_dims[0] * m_dims[1]; - m_morton_buf[++morton_id] = m_coeff_buf[id]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[id]); return; } @@ -41,51 +45,51 @@ void sperr::SPECK3D_INT_ENC::m_deposit_set(Set3D set) if (set.length_x == 2 && set.length_y == 2) { // Element (0, 0, 0) - m_morton_buf[morton_id] = m_coeff_buf[id]; + m_morton_buf[morton_id] = m_msb_position(m_coeff_buf[id]); // Element (1, 0, 0) - m_morton_buf[++morton_id] = m_coeff_buf[id + 1]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[id + 1]); // Element (0, 1, 0) auto id2 = id + m_dims[0]; - m_morton_buf[++morton_id] = m_coeff_buf[id2]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[id2]); // Element (1, 1, 0) - m_morton_buf[++morton_id] = m_coeff_buf[++id2]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[++id2]); return; } else if (set.length_x == 2 && set.length_z == 2) { // Element (0, 0, 0) - m_morton_buf[morton_id] = m_coeff_buf[id]; + m_morton_buf[morton_id] = m_msb_position(m_coeff_buf[id]); // Element (1, 0, 0) - m_morton_buf[++morton_id] = m_coeff_buf[id + 1]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[id + 1]); // Element (0, 0, 1) auto id2 = id + m_dims[0] * m_dims[1]; - m_morton_buf[++morton_id] = m_coeff_buf[id2]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[id2]); // Element (1, 0, 1) - m_morton_buf[++morton_id] = m_coeff_buf[++id2]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[++id2]); return; } else if (set.length_y == 2 && set.length_z == 2) { // Element (0, 0, 0) - m_morton_buf[morton_id] = m_coeff_buf[id]; + m_morton_buf[morton_id] = m_msb_position(m_coeff_buf[id]); // Element (0, 1, 0) auto id2 = id + m_dims[0]; - m_morton_buf[++morton_id] = m_coeff_buf[id2]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[id2]); // Element (0, 0, 1) id2 = id + m_dims[0] * m_dims[1]; - m_morton_buf[++morton_id] = m_coeff_buf[id2]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[id2]); // Element (0, 1, 1) id2 += m_dims[0]; - m_morton_buf[++morton_id] = m_coeff_buf[id2]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[id2]); return; } @@ -97,31 +101,31 @@ void sperr::SPECK3D_INT_ENC::m_deposit_set(Set3D set) // Element (0, 0, 0) const auto id = set.start_z * m_dims[0] * m_dims[1] + set.start_y * m_dims[0] + set.start_x; auto morton_id = set.get_morton(); - m_morton_buf[morton_id] = m_coeff_buf[id]; + m_morton_buf[morton_id] = m_msb_position(m_coeff_buf[id]); // Element (1, 0, 0) - m_morton_buf[++morton_id] = m_coeff_buf[id + 1]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[id + 1]); // Element (0, 1, 0) auto id2 = id + m_dims[0]; - m_morton_buf[++morton_id] = m_coeff_buf[id2]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[id2]); // Element (1, 1, 0) - m_morton_buf[++morton_id] = m_coeff_buf[++id2]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[++id2]); // Element (0, 0, 1) id2 = id + m_dims[0] * m_dims[1]; - m_morton_buf[++morton_id] = m_coeff_buf[id2]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[id2]); // Element (1, 0, 1) - m_morton_buf[++morton_id] = m_coeff_buf[++id2]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[++id2]); // Element (0, 1, 1) id2 = id + m_dims[0] * (m_dims[1] + 1); - m_morton_buf[++morton_id] = m_coeff_buf[id2]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[id2]); // Element (1, 1, 1) - m_morton_buf[++morton_id] = m_coeff_buf[++id2]; + m_morton_buf[++morton_id] = m_msb_position(m_coeff_buf[++id2]); return; } @@ -168,7 +172,7 @@ void sperr::SPECK3D_INT_ENC::m_process_S(size_t idx1, size_t idx2, size_t& co if (output) { auto first = m_morton_buf.data() + set.get_morton(); is_sig = std::any_of(first, first + set.num_elem(), - [thld = m_threshold](auto v) { return v >= thld; }); + [thld = m_morton_threshold](auto v) { return v >= thld; }); m_bit_buffer.wbit(is_sig); } @@ -185,8 +189,8 @@ void sperr::SPECK3D_INT_ENC::m_process_P(size_t idx, size_t morton, size_t& c bool is_sig = true; if (output) { - assert(m_coeff_buf[idx] == m_morton_buf[morton]); - is_sig = (m_morton_buf[morton] >= m_threshold); + assert(m_msb_position(m_coeff_buf[idx]) == m_morton_buf[morton]); + is_sig = (m_morton_buf[morton] >= m_morton_threshold); m_bit_buffer.wbit(is_sig); } @@ -217,6 +221,27 @@ void sperr::SPECK3D_INT_ENC::m_process_P_lite(size_t idx) } } +template +auto sperr::SPECK3D_INT_ENC::m_msb_position(uint_type v) const -> int8_t +{ +#if __cplusplus >= 202002L + return static_cast(sizeof(uint_type) * 8 - 1 - std::countl_zero(v)); +#else + int8_t pos = -1; + while (v) { + v >>= 1; + pos++; + } + return pos; +#endif +} + +template +void sperr::SPECK3D_INT_ENC::m_bitplane_init() +{ + m_morton_threshold = m_msb_position(m_threshold); +} + template class sperr::SPECK3D_INT_ENC; template class sperr::SPECK3D_INT_ENC; template class sperr::SPECK3D_INT_ENC; diff --git a/src/SPECK_INT.cpp b/src/SPECK_INT.cpp index fa04a877..02443416 100644 --- a/src/SPECK_INT.cpp +++ b/src/SPECK_INT.cpp @@ -144,6 +144,7 @@ void sperr::SPECK_INT::encode() // Marching over bitplanes. for (uint8_t bitplane = 0; bitplane < m_num_bitplanes; bitplane++) { + m_bitplane_init(); m_sorting_pass(); if (m_bit_buffer.wtell() >= m_budget) // Happens only when fixed-rate compression. break; From 649a8201de6fc960968ef57561ac5f62b673bc2e Mon Sep 17 00:00:00 2001 From: Sam Li Date: Sat, 14 Mar 2026 21:28:06 -0700 Subject: [PATCH 6/8] misc --- src/SPECK3D_INT_ENC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPECK3D_INT_ENC.cpp b/src/SPECK3D_INT_ENC.cpp index 2c8235e8..80df3975 100644 --- a/src/SPECK3D_INT_ENC.cpp +++ b/src/SPECK3D_INT_ENC.cpp @@ -170,7 +170,7 @@ void sperr::SPECK3D_INT_ENC::m_process_S(size_t idx1, size_t idx2, size_t& co // If need to output, it means the current set has unknown significance. if (output) { - auto first = m_morton_buf.data() + set.get_morton(); + auto first = m_morton_buf.begin() + set.get_morton(); is_sig = std::any_of(first, first + set.num_elem(), [thld = m_morton_threshold](auto v) { return v >= thld; }); m_bit_buffer.wbit(is_sig); From 6ece2b1e2aa8557725e89a13752481aa4d667276 Mon Sep 17 00:00:00 2001 From: Sam Li Date: Sun, 15 Mar 2026 09:41:08 -0700 Subject: [PATCH 7/8] another net improvement by batching all refinements together in 3D encoding step --- include/SPECK3D_INT_ENC.h | 1 + include/SPECK_INT.h | 1 + src/SPECK3D_INT_ENC.cpp | 15 +++++++++------ src/SPECK_INT.cpp | 5 ++++- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/include/SPECK3D_INT_ENC.h b/include/SPECK3D_INT_ENC.h index 331e4768..c078147b 100644 --- a/include/SPECK3D_INT_ENC.h +++ b/include/SPECK3D_INT_ENC.h @@ -36,6 +36,7 @@ class SPECK3D_INT_ENC final : public SPECK3D_INT { void m_process_P_lite(size_t idx) final; void m_additional_initialization() final; void m_bitplane_init() final; + void m_refinement_extra() final; // Data structures and functions for morton data layout. // `m_morton_buf` stores the MSB bit position of each coefficient (via m_msb_position()), diff --git a/include/SPECK_INT.h b/include/SPECK_INT.h index 592709aa..4b055d25 100644 --- a/include/SPECK_INT.h +++ b/include/SPECK_INT.h @@ -70,6 +70,7 @@ class SPECK_INT { virtual void m_sorting_pass() = 0; virtual void m_initialize_lists() = 0; virtual void m_bitplane_init() {} + virtual void m_refinement_extra() {} void m_refinement_pass_encode(); void m_refinement_pass_decode(); diff --git a/src/SPECK3D_INT_ENC.cpp b/src/SPECK3D_INT_ENC.cpp index 80df3975..748f4d27 100644 --- a/src/SPECK3D_INT_ENC.cpp +++ b/src/SPECK3D_INT_ENC.cpp @@ -196,9 +196,6 @@ void sperr::SPECK3D_INT_ENC::m_process_P(size_t idx, size_t morton, size_t& c if (is_sig) { counter++; // Let's increment the counter first! - assert(m_coeff_buf[idx] >= m_threshold); - m_coeff_buf[idx] -= m_threshold; - m_bit_buffer.wbit(m_sign_array.rbit(idx)); m_LSP_new.push_back(idx); m_LIP_mask.wfalse(idx); @@ -212,9 +209,6 @@ void sperr::SPECK3D_INT_ENC::m_process_P_lite(size_t idx) m_bit_buffer.wbit(is_sig); if (is_sig) { - assert(m_coeff_buf[idx] >= m_threshold); - m_coeff_buf[idx] -= m_threshold; - m_bit_buffer.wbit(m_sign_array.rbit(idx)); m_LSP_new.push_back(idx); m_LIP_mask.wfalse(idx); @@ -242,6 +236,15 @@ void sperr::SPECK3D_INT_ENC::m_bitplane_init() m_morton_threshold = m_msb_position(m_threshold); } +template +void sperr::SPECK3D_INT_ENC::m_refinement_extra() +{ + for (auto idx : m_LSP_new) { + assert(m_coeff_buf[idx] >= m_threshold); + m_coeff_buf[idx] -= m_threshold; + } +} + template class sperr::SPECK3D_INT_ENC; template class sperr::SPECK3D_INT_ENC; template class sperr::SPECK3D_INT_ENC; diff --git a/src/SPECK_INT.cpp b/src/SPECK_INT.cpp index 02443416..82d2718a 100644 --- a/src/SPECK_INT.cpp +++ b/src/SPECK_INT.cpp @@ -346,8 +346,11 @@ void sperr::SPECK_INT::m_refinement_pass_encode() } } - // Second, mark newly found significant pixels in `m_LSP_mask`. + // Second, deal with newly found significant pixels in `m_LSP_mask`. + // For 1D and 2D cases, it simply means marking those locations. + // For 3D cases, `m_refinement_extra()` actually refines `m_coeff_buf` values. // + m_refinement_extra(); for (auto idx : m_LSP_new) m_LSP_mask.wtrue(idx); m_LSP_new.clear(); From f67b472b3197022d3662e9e3a557ee408c4716bd Mon Sep 17 00:00:00 2001 From: Sam Li Date: Sun, 15 Mar 2026 10:29:10 -0700 Subject: [PATCH 8/8] change the definition of Set3D, and store the morton index as a plain uint64_t. It provides performance gain actually! --- include/SPECK3D_INT.h | 21 +-------------------- src/SPECK3D_INT.cpp | 22 +++++++++++----------- src/SPECK3D_INT_ENC.cpp | 12 ++++++------ 3 files changed, 18 insertions(+), 37 deletions(-) diff --git a/include/SPECK3D_INT.h b/include/SPECK3D_INT.h index 881f98d5..0aed3824 100644 --- a/include/SPECK3D_INT.h +++ b/include/SPECK3D_INT.h @@ -3,21 +3,13 @@ #include "SPECK_INT.h" -#include // std::memcpy() #include namespace sperr { class Set3D { - private: - // The first 6 bytes of the morton offset in uint64_t. Because each set dimension is - // stored using 16-bit integers, these 48 bits are big enough too! - std::array m_morton = {0, 0, 0, 0, 0, 0}; - public: - // - // Publicly accessible public data members. - // + uint64_t morton_idx = 0; uint16_t start_x = 0; uint16_t start_y = 0; uint16_t start_z = 0; @@ -25,17 +17,6 @@ class Set3D { uint16_t length_y = 0; uint16_t length_z = 0; - public: - // - // Member functions (intended to be inline) - // - auto get_morton() const -> uint64_t - { - auto tmp = uint64_t{0}; - std::memcpy(&tmp, m_morton.data(), sizeof(m_morton)); - return tmp; - } - void set_morton(uint64_t val) { std::memcpy(m_morton.data(), &val, sizeof(m_morton)); } void make_empty() { length_x = 0; } auto num_elem() const -> size_t { return (size_t{length_x} * length_y * length_z); } }; diff --git a/src/SPECK3D_INT.cpp b/src/SPECK3D_INT.cpp index e683ba24..0d899649 100644 --- a/src/SPECK3D_INT.cpp +++ b/src/SPECK3D_INT.cpp @@ -148,7 +148,7 @@ void sperr::SPECK3D_INT::m_code_S(size_t idx1, size_t idx2) // Element (0, 0, 0) const auto id = set.start_z * m_dims[0] * m_dims[1] + set.start_y * m_dims[0] + set.start_x; - auto mort = set.get_morton(); + auto mort = set.morton_idx; m_LIP_mask.wtrue(id); m_process_P(id, mort, sig_counter, need_decide); @@ -200,7 +200,7 @@ void sperr::SPECK3D_INT::m_code_S(size_t idx1, size_t idx2) if (it->num_elem() == 1) { auto idx = it->start_z * m_dims[0] * m_dims[1] + it->start_y * m_dims[0] + it->start_x; m_LIP_mask.wtrue(idx); - m_process_P(idx, it->get_morton(), sig_counter, need_decide); + m_process_P(idx, it->morton_idx, sig_counter, need_decide); } else { m_LIS[next_lev].emplace_back(*it); @@ -230,14 +230,14 @@ auto sperr::SPECK3D_INT::m_partition_S_XYZ(Set3D set, uint16_t lev) const auto subsets = std::tuple, uint16_t>(); std::get<1>(subsets) = lev; - auto morton_offset = set.get_morton(); + auto morton_offset = set.morton_idx; // // The actual figuring out where it starts/ends part... // // subset (0, 0, 0) auto& sub0 = std::get<0>(subsets)[0]; - sub0.set_morton(morton_offset); + sub0.morton_idx = morton_offset; sub0.start_x = set.start_x; sub0.start_y = set.start_y; sub0.start_z = set.start_z; @@ -248,7 +248,7 @@ auto sperr::SPECK3D_INT::m_partition_S_XYZ(Set3D set, uint16_t lev) const // subset (1, 0, 0) auto& sub1 = std::get<0>(subsets)[1]; morton_offset += sub0.num_elem(); - sub1.set_morton(morton_offset); + sub1.morton_idx = morton_offset; sub1.start_x = set.start_x + split_x[0]; sub1.start_y = set.start_y; sub1.start_z = set.start_z; @@ -259,7 +259,7 @@ auto sperr::SPECK3D_INT::m_partition_S_XYZ(Set3D set, uint16_t lev) const // subset (0, 1, 0) auto& sub2 = std::get<0>(subsets)[2]; morton_offset += sub1.num_elem(); - sub2.set_morton(morton_offset); + sub2.morton_idx = morton_offset; sub2.start_x = set.start_x; sub2.start_y = set.start_y + split_y[0]; sub2.start_z = set.start_z; @@ -270,7 +270,7 @@ auto sperr::SPECK3D_INT::m_partition_S_XYZ(Set3D set, uint16_t lev) const // subset (1, 1, 0) auto& sub3 = std::get<0>(subsets)[3]; morton_offset += sub2.num_elem(); - sub3.set_morton(morton_offset); + sub3.morton_idx = morton_offset; sub3.start_x = set.start_x + split_x[0]; sub3.start_y = set.start_y + split_y[0]; sub3.start_z = set.start_z; @@ -281,7 +281,7 @@ auto sperr::SPECK3D_INT::m_partition_S_XYZ(Set3D set, uint16_t lev) const // subset (0, 0, 1) auto& sub4 = std::get<0>(subsets)[4]; morton_offset += sub3.num_elem(); - sub4.set_morton(morton_offset); + sub4.morton_idx = morton_offset; sub4.start_x = set.start_x; sub4.start_y = set.start_y; sub4.start_z = set.start_z + split_z[0]; @@ -292,7 +292,7 @@ auto sperr::SPECK3D_INT::m_partition_S_XYZ(Set3D set, uint16_t lev) const // subset (1, 0, 1) auto& sub5 = std::get<0>(subsets)[5]; morton_offset += sub4.num_elem(); - sub5.set_morton(morton_offset); + sub5.morton_idx = morton_offset; sub5.start_x = set.start_x + split_x[0]; sub5.start_y = set.start_y; sub5.start_z = set.start_z + split_z[0]; @@ -303,7 +303,7 @@ auto sperr::SPECK3D_INT::m_partition_S_XYZ(Set3D set, uint16_t lev) const // subset (0, 1, 1) auto& sub6 = std::get<0>(subsets)[6]; morton_offset += sub5.num_elem(); - sub6.set_morton(morton_offset); + sub6.morton_idx = morton_offset; sub6.start_x = set.start_x; sub6.start_y = set.start_y + split_y[0]; sub6.start_z = set.start_z + split_z[0]; @@ -314,7 +314,7 @@ auto sperr::SPECK3D_INT::m_partition_S_XYZ(Set3D set, uint16_t lev) const // subset (1, 1, 1) auto& sub7 = std::get<0>(subsets)[7]; morton_offset += sub6.num_elem(); - sub7.set_morton(morton_offset); + sub7.morton_idx = morton_offset; sub7.start_x = set.start_x + split_x[0]; sub7.start_y = set.start_y + split_y[0]; sub7.start_z = set.start_z + split_z[0]; diff --git a/src/SPECK3D_INT_ENC.cpp b/src/SPECK3D_INT_ENC.cpp index 748f4d27..b698b4a3 100644 --- a/src/SPECK3D_INT_ENC.cpp +++ b/src/SPECK3D_INT_ENC.cpp @@ -17,7 +17,7 @@ void sperr::SPECK3D_INT_ENC::m_deposit_set(Set3D set) return; case 1: { auto id = set.start_z * m_dims[0] * m_dims[1] + set.start_y * m_dims[0] + set.start_x; - m_morton_buf[set.get_morton()] = m_msb_position(m_coeff_buf[id]); + m_morton_buf[set.morton_idx] = m_msb_position(m_coeff_buf[id]); return; } case 2: { @@ -25,7 +25,7 @@ void sperr::SPECK3D_INT_ENC::m_deposit_set(Set3D set) // // Deposit the 1st element. auto id = set.start_z * m_dims[0] * m_dims[1] + set.start_y * m_dims[0] + set.start_x; - auto morton_id = set.get_morton(); + auto morton_id = set.morton_idx; m_morton_buf[morton_id] = m_msb_position(m_coeff_buf[id]); // Deposit the 2nd element. @@ -41,7 +41,7 @@ void sperr::SPECK3D_INT_ENC::m_deposit_set(Set3D set) } case 4: { const auto id = set.start_z * m_dims[0] * m_dims[1] + set.start_y * m_dims[0] + set.start_x; - auto morton_id = set.get_morton(); + auto morton_id = set.morton_idx; if (set.length_x == 2 && set.length_y == 2) { // Element (0, 0, 0) @@ -100,7 +100,7 @@ void sperr::SPECK3D_INT_ENC::m_deposit_set(Set3D set) if (set.length_x == 2 && set.length_y == 2) { // Element (0, 0, 0) const auto id = set.start_z * m_dims[0] * m_dims[1] + set.start_y * m_dims[0] + set.start_x; - auto morton_id = set.get_morton(); + auto morton_id = set.morton_idx; m_morton_buf[morton_id] = m_msb_position(m_coeff_buf[id]); // Element (1, 0, 0) @@ -155,7 +155,7 @@ void sperr::SPECK3D_INT_ENC::m_additional_initialization() auto idx1 = m_LIS.size() - tmp; for (size_t idx2 = 0; idx2 < m_LIS[idx1].size(); idx2++) { auto& set = m_LIS[idx1][idx2]; - set.set_morton(morton_offset); + set.morton_idx = morton_offset; m_deposit_set(set); morton_offset += set.num_elem(); } @@ -170,7 +170,7 @@ void sperr::SPECK3D_INT_ENC::m_process_S(size_t idx1, size_t idx2, size_t& co // If need to output, it means the current set has unknown significance. if (output) { - auto first = m_morton_buf.begin() + set.get_morton(); + auto first = m_morton_buf.begin() + set.morton_idx; is_sig = std::any_of(first, first + set.num_elem(), [thld = m_morton_threshold](auto v) { return v >= thld; }); m_bit_buffer.wbit(is_sig);