This is an automated email from the ASF dual-hosted git repository.

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 3c410074 perf(internal/bitutils): use four-word fast path for 
unaligned bitmaps (#1398)
3c410074 is described below

commit 3c4100746627e225eabe773a5cedfd2360cb4e5a
Author: Minh Vu <[email protected]>
AuthorDate: Thu Oct 22 19:52:51 2026 +0200

    perf(internal/bitutils): use four-word fast path for unaligned bitmaps 
(#1398)
    
    ## What
    Use the four-word fast path for unaligned bitmap counts.
    
    ## Why
    The existing threshold is higher than the five backing words needed by
    the shifted reads.
    
    ## Implementation
    - Lower the threshold to the minimum safe range.
    - Add boundary coverage and a package benchmark.
    
    ## Benchmark
    Go 1.26.3, Apple M1 Pro (`darwin/arm64`), median of five runs; 0 B/op
    and 0 allocs/op.
    
    | Metric | Before | After | Change |
    | --- | ---: | ---: | ---: |
    | 1,024 bits, offset 1 | 281.1 ns/op | 95.64 ns/op | -66.0% |
    | 4,096 bits, offset 1 | 415.8 ns/op | 215.3 ns/op | -48.2% |
    
    ---------
    
    Co-authored-by: Matt Topol <[email protected]>
---
 internal/bitutils/bit_block_counter.go             |  3 +-
 .../bitutils/bit_block_counter_benchmark_test.go   | 54 +++++++++++++++
 internal/bitutils/bit_block_counter_guard_test.go  | 78 ++++++++++++++++++++++
 internal/bitutils/bit_block_counter_test.go        | 32 +++++++++
 4 files changed, 166 insertions(+), 1 deletion(-)

diff --git a/internal/bitutils/bit_block_counter.go 
b/internal/bitutils/bit_block_counter.go
index 43b2fbb2..53d25c1b 100644
--- a/internal/bitutils/bit_block_counter.go
+++ b/internal/bitutils/bit_block_counter.go
@@ -111,7 +111,8 @@ func (b *BitBlockCounter) NextFourWords() BitBlockCount {
        } else {
                // When the offset is > 0, we need there to be a word beyond 
the last
                // aligned word in the bitmap for the bit shifting logic.
-               if b.bitsRemaining < 5*fourWordsBits-int64(b.bitOffset) {
+               // Reading four shifted 64-bit words needs five backing words.
+               if b.bitsRemaining < fourWordsBits+wordBits-int64(b.bitOffset) {
                        return b.getBlockSlow(fourWordsBits)
                }
 
diff --git a/internal/bitutils/bit_block_counter_benchmark_test.go 
b/internal/bitutils/bit_block_counter_benchmark_test.go
new file mode 100644
index 00000000..28344071
--- /dev/null
+++ b/internal/bitutils/bit_block_counter_benchmark_test.go
@@ -0,0 +1,54 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+package bitutils_test
+
+import (
+       "fmt"
+       "math/rand/v2"
+       "testing"
+
+       "github.com/apache/arrow-go/v18/arrow/bitutil"
+       "github.com/apache/arrow-go/v18/internal/bitutils"
+)
+
+var fourWordsBenchmarkSink int64
+
+func BenchmarkFourWordsUnaligned(b *testing.B) {
+       for _, length := range []int64{512, 1024, 4096} {
+               for _, offset := range []int64{1, 3, 7} {
+                       bitmap := make([]byte, 
bitutil.BytesForBits(offset+length))
+                       rng := &rand.ChaCha8{}
+                       _, _ = rng.Read(bitmap)
+
+                       b.Run(fmt.Sprintf("%dbits/offset%d", length, offset), 
func(b *testing.B) {
+                               b.ReportAllocs()
+                               b.SetBytes(int64(bitutil.BytesForBits(length)))
+
+                               var totalPopcnt int64
+                               for b.Loop() {
+                                       counter := 
bitutils.NewBitBlockCounter(bitmap, offset, length)
+                                       for pos := int64(0); pos < length; {
+                                               block := counter.NextFourWords()
+                                               pos += int64(block.Len)
+                                               totalPopcnt += 
int64(block.Popcnt)
+                                       }
+                               }
+                               fourWordsBenchmarkSink = totalPopcnt
+                       })
+               }
+       }
+}
diff --git a/internal/bitutils/bit_block_counter_guard_test.go 
b/internal/bitutils/bit_block_counter_guard_test.go
new file mode 100644
index 00000000..67ea4a6a
--- /dev/null
+++ b/internal/bitutils/bit_block_counter_guard_test.go
@@ -0,0 +1,78 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+//go:build linux || darwin
+
+package bitutils_test
+
+import (
+       "fmt"
+       "math/rand/v2"
+       "syscall"
+       "testing"
+
+       "github.com/apache/arrow-go/v18/arrow/bitutil"
+       "github.com/apache/arrow-go/v18/internal/bitutils"
+)
+
+func TestFourWordsUnalignedGuardPage(t *testing.T) {
+       pageSize := syscall.Getpagesize()
+       for offset := int64(1); offset < 8; offset++ {
+               for _, length := range []int64{
+                       255, 256, 257,
+                       320 - offset - 1, 320 - offset, 320 - offset + 1,
+                       512, 1024, 1280 - offset - 1, 1280 - offset,
+               } {
+                       t.Run(fmt.Sprintf("offset%d/length%d", offset, length), 
func(t *testing.T) {
+                               mapped, err := syscall.Mmap(-1, 0, 2*pageSize, 
syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_PRIVATE|syscall.MAP_ANON)
+                               if err != nil {
+                                       t.Fatalf("mmap: %v", err)
+                               }
+                               t.Cleanup(func() {
+                                       if err := syscall.Munmap(mapped); err 
!= nil {
+                                               t.Errorf("munmap: %v", err)
+                                       }
+                               })
+                               if err := syscall.Mprotect(mapped[pageSize:], 
syscall.PROT_NONE); err != nil {
+                                       t.Fatalf("mprotect: %v", err)
+                               }
+
+                               // An unsafe read beyond the last required byte 
reaches the protected page.
+                               numBytes := int(bitutil.BytesForBits(offset + 
length))
+                               bitmap := mapped[pageSize-numBytes : pageSize]
+                               rng := &rand.ChaCha8{}
+                               _, _ = rng.Read(bitmap)
+
+                               counter := bitutils.NewBitBlockCounter(bitmap, 
offset, length)
+                               for pos := int64(0); pos < length; {
+                                       block := counter.NextFourWords()
+                                       wantLen := min(int64(256), length-pos)
+                                       if int64(block.Len) != wantLen {
+                                               t.Fatalf("offset=%d length=%d 
pos=%d: block length=%d, want %d", offset, length, pos, block.Len, wantLen)
+                                       }
+                                       wantPopcnt := 
bitutil.CountSetBits(bitmap, int(offset+pos), int(block.Len))
+                                       if int(block.Popcnt) != wantPopcnt {
+                                               t.Fatalf("offset=%d length=%d 
pos=%d: popcount=%d, want %d", offset, length, pos, block.Popcnt, wantPopcnt)
+                                       }
+                                       pos += int64(block.Len)
+                               }
+                               if got := counter.NextFourWords().Len; got != 0 
{
+                                       t.Fatalf("offset=%d length=%d: final 
block length=%d, want 0", offset, length, got)
+                               }
+                       })
+               }
+       }
+}
diff --git a/internal/bitutils/bit_block_counter_test.go 
b/internal/bitutils/bit_block_counter_test.go
index 8ebbbe88..5b9f364e 100644
--- a/internal/bitutils/bit_block_counter_test.go
+++ b/internal/bitutils/bit_block_counter_test.go
@@ -199,3 +199,35 @@ func TestFourWordsRandomData(t *testing.T) {
                checkWithOffset(offsetI)
        }
 }
+
+func TestFourWordsUnalignedBoundaries(t *testing.T) {
+       for offset := int64(1); offset < 8; offset++ {
+               for _, length := range []int64{
+                       255, 256, 257,
+                       320 - offset - 1, 320 - offset, 320 - offset + 1,
+                       512, 1024, 1280 - offset - 1, 1280 - offset,
+               } {
+                       // Use an exact-length bitmap for functional boundary 
coverage.
+                       // The guard-page test separately detects unsafe 
overreads.
+                       bitmap := make([]byte, 
bitutil.BytesForBits(offset+length))
+                       rng := &rand.ChaCha8{}
+                       _, _ = rng.Read(bitmap)
+
+                       counter := bitutils.NewBitBlockCounter(bitmap, offset, 
length)
+                       for pos := int64(0); pos < length; {
+                               block := counter.NextFourWords()
+                               if block.Len <= 0 {
+                                       t.Fatalf("no progress at offset=%d 
length=%d pos=%d", offset, length, pos)
+                               }
+                               assert.EqualValues(t, min(int64(256), 
length-pos), block.Len,
+                                       "offset=%d length=%d pos=%d", offset, 
length, pos)
+                               assert.EqualValues(t,
+                                       bitutil.CountSetBits(bitmap, 
int(offset+pos), int(block.Len)),
+                                       block.Popcnt, "offset=%d length=%d 
pos=%d", offset, length, pos)
+                               pos += int64(block.Len)
+                       }
+
+                       assert.Zero(t, counter.NextFourWords().Len)
+               }
+       }
+}

Reply via email to