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} {