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 fa7b5da5 perf(arrow/bitutil): add SetBitSwap/ClearBitSwap, use in 
builder.SetNull (#1090)
fa7b5da5 is described below

commit fa7b5da54984284029102367eda44f085fd7d953
Author: Tom Frank <[email protected]>
AuthorDate: Thu Aug 6 19:07:47 2026 +0300

    perf(arrow/bitutil): add SetBitSwap/ClearBitSwap, use in builder.SetNull 
(#1090)
    
    ### What changes are included in this PR?
    
    Adds `bitutil.SetBitSwap` and `bitutil.ClearBitSwap`, which modify a bit
    and
    return its previous value in a single read-modify-write, and uses
    `ClearBitSwap` in `builder.SetNull` to replace the previous
    `BitIsSet` + `ClearBit` sequence (two byte loads and two `Bytes()`
    calls).
    
    This makes `SetNull` ~32% faster (1.048ns → 0.716ns, benchstat n=10,
    Apple M4 Pro).
    
    ### Are these changes tested?
    
    Yes. New unit tests `TestSetBitSwap`/`TestClearBitSwap` cover the added
    functions, and the existing `TestBuilder_SetNull` (including
    idempotency)
    covers the `SetNull` change.
    
    ### Are there any user-facing changes?
    
    Two new exported functions in `bitutil`: `SetBitSwap` and
    `ClearBitSwap`.
    No behavior changes to existing APIs.
---
 arrow/array/builder.go        |  3 +--
 arrow/bitutil/bitutil.go      | 18 ++++++++++++++++++
 arrow/bitutil/bitutil_test.go | 26 ++++++++++++++++++++++++++
 3 files changed, 45 insertions(+), 2 deletions(-)

diff --git a/arrow/array/builder.go b/arrow/array/builder.go
index 517feec5..2e0500af 100644
--- a/arrow/array/builder.go
+++ b/arrow/array/builder.go
@@ -133,10 +133,9 @@ func (b *builder) SetNull(i int) {
        if i < 0 || i >= b.length {
                panic("arrow/array: index out of range")
        }
-       if bitutil.BitIsSet(b.nullBitmap.Bytes(), i) {
+       if bitutil.ClearBitSwap(b.nullBitmap.Bytes(), i) {
                b.nulls++
        }
-       bitutil.ClearBit(b.nullBitmap.Bytes(), i)
 }
 
 func (b *builder) init(capacity int) {
diff --git a/arrow/bitutil/bitutil.go b/arrow/bitutil/bitutil.go
index 47af2b2e..720bc51c 100644
--- a/arrow/bitutil/bitutil.go
+++ b/arrow/bitutil/bitutil.go
@@ -58,6 +58,24 @@ func SetBit(buf []byte, i int) { buf[uint(i)/8] |= 
BitMask[byte(i)%8] }
 // ClearBit sets the bit at index i in buf to 0.
 func ClearBit(buf []byte, i int) { buf[uint(i)/8] &= FlippedBitMask[byte(i)%8] 
}
 
+// SetBitSwap sets the bit at index i in buf to 1 and returns whether it was 
previously set.
+func SetBitSwap(buf []byte, i int) bool {
+       p := &buf[uint(i)/8]
+       mask := BitMask[byte(i)%8]
+       old := *p
+       *p = old | mask
+       return old&mask != 0
+}
+
+// ClearBitSwap sets the bit at index i in buf to 0 and returns whether it was 
previously set.
+func ClearBitSwap(buf []byte, i int) bool {
+       p := &buf[uint(i)/8]
+       mask := BitMask[byte(i)%8]
+       old := *p
+       *p = old &^ mask
+       return old&mask != 0
+}
+
 // SetBitTo sets the bit at index i in buf to val.
 func SetBitTo(buf []byte, i int, val bool) {
        if val {
diff --git a/arrow/bitutil/bitutil_test.go b/arrow/bitutil/bitutil_test.go
index 650984af..fcfd35e0 100644
--- a/arrow/bitutil/bitutil_test.go
+++ b/arrow/bitutil/bitutil_test.go
@@ -118,6 +118,32 @@ func TestSetBit(t *testing.T) {
        assert.Equal(t, []byte{0xa1, 0xc2}, buf)
 }
 
+func TestSetBitSwap(t *testing.T) {
+       buf := make([]byte, 2)
+       buf[0] = 0xa1
+       buf[1] = 0xc2
+       exp := []bool{true, false, false, false, false, true, false, true, 
false, true, false, false, false, false, true, true}
+       var got []bool
+       for i := 0; i < 0x10; i++ {
+               got = append(got, bitutil.SetBitSwap(buf, i))
+       }
+       assert.Equal(t, exp, got)
+       assert.Equal(t, []byte{0xff, 0xff}, buf)
+}
+
+func TestClearBitSwap(t *testing.T) {
+       buf := make([]byte, 2)
+       buf[0] = 0xa1
+       buf[1] = 0xc2
+       exp := []bool{true, false, false, false, false, true, false, true, 
false, true, false, false, false, false, true, true}
+       var got []bool
+       for i := 0; i < 0x10; i++ {
+               got = append(got, bitutil.ClearBitSwap(buf, i))
+       }
+       assert.Equal(t, exp, got)
+       assert.Equal(t, []byte{0x00, 0x00}, buf)
+}
+
 func TestSetBitTo(t *testing.T) {
        buf := make([]byte, 2)
        for i, v := range []bool{true, false, false, false, false, true, false, 
true, false, true, false, false, false, false, true, true} {

Reply via email to