This is an automated email from the ASF dual-hosted git repository.
Jefffrey pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 3abbc04e42 Allow cross-crate inlining for `BitSliceIterator` (#10588)
3abbc04e42 is described below
commit 3abbc04e429d8d13f3dae1e0086f23142ad8cec9
Author: Connor Tsui <[email protected]>
AuthorDate: Tue Aug 18 02:28:44 2026 -0400
Allow cross-crate inlining for `BitSliceIterator` (#10588)
## Which issue does this PR close?
- Closes #10587.
## Rationale for this change
Downstream crates built without LTO cannot inline the `BitSliceIterator`
hot path. This change also makes `BitSliceIterator::next` consistent
with the annotated `next` implementations on `BitIterator` and
`BitIndexIterator`.
[Vortex](https://github.com/vortex-data/vortex) discovered this gap
while consuming `arrow-buffer` 58.4.0 from a separate crate.
## What changes are included in this PR?
Adds `#[inline]` to `BitSliceIterator::{new, advance_to_set_bit, next}`
and `UnalignedBitChunk::iter`.
## Are these changes tested?
Yes.
<details>
<summary>Instruction-count measurements</summary>
[Vortex's](https://github.com/vortex-data/vortex) original
`arrow-buffer` 58.4.0 experiment measured 13,729 marginal instructions
per iteration unmodified and 8,565 with fat LTO and one codegen unit.
Annotating only `BitSliceIterator::next` reduced the count to 13,151,
while annotating the broader pre-bisection set reduced it to 10,026. The
bisection on current `main` narrows that set to the four methods in this
PR.
A local reproduction on current `main` measured the proposed change with
a separate consumer crate:
| Methods marked `#[inline]` | Marginal instructions per iteration |
| ---------------------------- | -----------------------------------: |
| None | 15,028 |
| Four methods in this change | 7,584 |
Removing any one of the four attributes gives up part of the
improvement:
| Method without `#[inline]` | Marginal instructions per iteration |
| -------------------------------------------- |
-----------------------------------: |
| `BitSliceIterator::new` | 8,898 |
| `BitSliceIterator::advance_to_set_bit` | 14,015 |
| `BitSliceIterator::next` | 15,006 |
| `UnalignedBitChunk::iter` | 8,448 |
`UnalignedBitChunk::new` was also tested because it constructs this
path. Adding it to the four-method set increases the result from 7,584
to 8,331 instructions, or 9.8%, so this PR does not annotate it.
The consumer iterates contiguous true runs over a 16,384-bit buffer at
1% density. It uses `opt-level = 3`, 16 codegen units, and no LTO.
Callgrind counts from runs of 1,000 and 2,000 iterations were
differenced to remove startup and setup instructions.
These are instruction counts, not wall-clock measurements.
</details>
On aarch64 macOS, the optimized `arrow-buffer` rlib changes from
1,799,592 to 1,808,184 bytes, an increase of 8,592 bytes or 0.48%. The
median release compile time across five forced builds with warm
dependencies remains 1.19 seconds.
## Are there any user-facing changes?
No.
Signed-off-by: Connor Tsui <[email protected]>
---
arrow-buffer/src/util/bit_chunk_iterator.rs | 1 +
arrow-buffer/src/util/bit_iterator.rs | 3 +++
2 files changed, 4 insertions(+)
diff --git a/arrow-buffer/src/util/bit_chunk_iterator.rs
b/arrow-buffer/src/util/bit_chunk_iterator.rs
index 2c4e684917..460349e7a6 100644
--- a/arrow-buffer/src/util/bit_chunk_iterator.rs
+++ b/arrow-buffer/src/util/bit_chunk_iterator.rs
@@ -157,6 +157,7 @@ impl<'a> UnalignedBitChunk<'a> {
}
/// Returns an iterator over the chunks
+ #[inline]
pub fn iter(&self) -> UnalignedBitChunkIterator<'a> {
self.prefix
.into_iter()
diff --git a/arrow-buffer/src/util/bit_iterator.rs
b/arrow-buffer/src/util/bit_iterator.rs
index c2e65de0c8..6286e8f42b 100644
--- a/arrow-buffer/src/util/bit_iterator.rs
+++ b/arrow-buffer/src/util/bit_iterator.rs
@@ -195,6 +195,7 @@ pub struct BitSliceIterator<'a> {
impl<'a> BitSliceIterator<'a> {
/// Create a new [`BitSliceIterator`] from the provided `buffer`,
/// and `offset` and `len` in bits
+ #[inline]
pub fn new(buffer: &'a [u8], offset: usize, len: usize) -> Self {
let chunk = UnalignedBitChunk::new(buffer, offset, len);
let mut iter = chunk.iter();
@@ -215,6 +216,7 @@ impl<'a> BitSliceIterator<'a> {
///
/// Where `chunk_offset` is the bit offset to the current `u64` chunk
/// and `bit_offset` is the offset of the first `1` bit in that chunk
+ #[inline]
fn advance_to_set_bit(&mut self) -> Option<(i64, u32)> {
loop {
if self.current_chunk != 0 {
@@ -232,6 +234,7 @@ impl<'a> BitSliceIterator<'a> {
impl Iterator for BitSliceIterator<'_> {
type Item = (usize, usize);
+ #[inline]
fn next(&mut self) -> Option<Self::Item> {
// Used as termination condition
if self.len == 0 {