github.com/apache/arrow/go/v14@v14.0.1/internal/bitutils/bit_block_counter.go (about) 1 // Licensed to the Apache Software Foundation (ASF) under one 2 // or more contributor license agreements. See the NOTICE file 3 // distributed with this work for additional information 4 // regarding copyright ownership. The ASF licenses this file 5 // to you under the Apache License, Version 2.0 (the 6 // "License"); you may not use this file except in compliance 7 // with the License. You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 package bitutils 18 19 import ( 20 "math" 21 "math/bits" 22 "unsafe" 23 24 "github.com/apache/arrow/go/v14/arrow/bitutil" 25 "github.com/apache/arrow/go/v14/internal/utils" 26 ) 27 28 func loadWord(byt []byte) uint64 { 29 return utils.ToLEUint64(*(*uint64)(unsafe.Pointer(&byt[0]))) 30 } 31 32 func shiftWord(current, next uint64, shift int64) uint64 { 33 if shift == 0 { 34 return current 35 } 36 return (current >> shift) | (next << (64 - shift)) 37 } 38 39 // BitBlockCount is returned by the various bit block counter utilities 40 // in order to return a length of bits and the population count of that 41 // slice of bits. 42 type BitBlockCount struct { 43 Len int16 44 Popcnt int16 45 } 46 47 // NoneSet returns true if ALL the bits were 0 in this set, ie: Popcnt == 0 48 func (b BitBlockCount) NoneSet() bool { 49 return b.Popcnt == 0 50 } 51 52 // AllSet returns true if ALL the bits were 1 in this set, ie: Popcnt == Len 53 func (b BitBlockCount) AllSet() bool { 54 return b.Len == b.Popcnt 55 } 56 57 // BitBlockCounter is a utility for grabbing chunks of a bitmap at a time and efficiently 58 // counting the number of bits which are 1. 59 type BitBlockCounter struct { 60 bitmap []byte 61 bitsRemaining int64 62 bitOffset int8 63 } 64 65 const ( 66 wordBits int64 = 64 67 fourWordsBits int64 = wordBits * 4 68 ) 69 70 // NewBitBlockCounter returns a BitBlockCounter for the passed bitmap starting at startOffset 71 // of length nbits. 72 func NewBitBlockCounter(bitmap []byte, startOffset, nbits int64) *BitBlockCounter { 73 return &BitBlockCounter{ 74 bitmap: bitmap[startOffset/8:], 75 bitsRemaining: nbits, 76 bitOffset: int8(startOffset % 8), 77 } 78 } 79 80 // getBlockSlow is for returning a block of the requested size when there aren't 81 // enough bits remaining to do a full word computation. 82 func (b *BitBlockCounter) getBlockSlow(blockSize int64) BitBlockCount { 83 runlen := int16(utils.Min(b.bitsRemaining, blockSize)) 84 popcnt := int16(bitutil.CountSetBits(b.bitmap, int(b.bitOffset), int(runlen))) 85 b.bitsRemaining -= int64(runlen) 86 b.bitmap = b.bitmap[runlen/8:] 87 return BitBlockCount{runlen, popcnt} 88 } 89 90 // NextFourWords returns the next run of available bits, usually 256. The 91 // returned pair contains the size of run and the number of true values. 92 // The last block will have a length less than 256 if the bitmap length 93 // is not a multiple of 256, and will return 0-length blocks in subsequent 94 // invocations. 95 func (b *BitBlockCounter) NextFourWords() BitBlockCount { 96 if b.bitsRemaining == 0 { 97 return BitBlockCount{0, 0} 98 } 99 100 totalPopcnt := 0 101 if b.bitOffset == 0 { 102 // if we're aligned at 0 bitoffset, then we can easily just jump from 103 // word to word nice and easy. 104 if b.bitsRemaining < fourWordsBits { 105 return b.getBlockSlow(fourWordsBits) 106 } 107 totalPopcnt += bits.OnesCount64(loadWord(b.bitmap)) 108 totalPopcnt += bits.OnesCount64(loadWord(b.bitmap[8:])) 109 totalPopcnt += bits.OnesCount64(loadWord(b.bitmap[16:])) 110 totalPopcnt += bits.OnesCount64(loadWord(b.bitmap[24:])) 111 } else { 112 // When the offset is > 0, we need there to be a word beyond the last 113 // aligned word in the bitmap for the bit shifting logic. 114 if b.bitsRemaining < 5*fourWordsBits-int64(b.bitOffset) { 115 return b.getBlockSlow(fourWordsBits) 116 } 117 118 current := loadWord(b.bitmap) 119 next := loadWord(b.bitmap[8:]) 120 totalPopcnt += bits.OnesCount64(shiftWord(current, next, int64(b.bitOffset))) 121 122 current = next 123 next = loadWord(b.bitmap[16:]) 124 totalPopcnt += bits.OnesCount64(shiftWord(current, next, int64(b.bitOffset))) 125 126 current = next 127 next = loadWord(b.bitmap[24:]) 128 totalPopcnt += bits.OnesCount64(shiftWord(current, next, int64(b.bitOffset))) 129 130 current = next 131 next = loadWord(b.bitmap[32:]) 132 totalPopcnt += bits.OnesCount64(shiftWord(current, next, int64(b.bitOffset))) 133 } 134 b.bitmap = b.bitmap[bitutil.BytesForBits(fourWordsBits):] 135 b.bitsRemaining -= fourWordsBits 136 return BitBlockCount{256, int16(totalPopcnt)} 137 } 138 139 // NextWord returns the next run of available bits, usually 64. The returned 140 // pair contains the size of run and the number of true values. The last 141 // block will have a length less than 64 if the bitmap length is not a 142 // multiple of 64, and will return 0-length blocks in subsequent 143 // invocations. 144 func (b *BitBlockCounter) NextWord() BitBlockCount { 145 if b.bitsRemaining == 0 { 146 return BitBlockCount{0, 0} 147 } 148 popcnt := 0 149 if b.bitOffset == 0 { 150 if b.bitsRemaining < wordBits { 151 return b.getBlockSlow(wordBits) 152 } 153 popcnt = bits.OnesCount64(loadWord(b.bitmap)) 154 } else { 155 // When the offset is > 0, we need there to be a word beyond the last 156 // aligned word in the bitmap for the bit shifting logic. 157 if b.bitsRemaining < (2*wordBits - int64(b.bitOffset)) { 158 return b.getBlockSlow(wordBits) 159 } 160 popcnt = bits.OnesCount64(shiftWord(loadWord(b.bitmap), loadWord(b.bitmap[8:]), int64(b.bitOffset))) 161 } 162 b.bitmap = b.bitmap[wordBits/8:] 163 b.bitsRemaining -= wordBits 164 return BitBlockCount{64, int16(popcnt)} 165 } 166 167 // OptionalBitBlockCounter is a useful counter to iterate through a possibly 168 // non-existent validity bitmap to allow us to write one code path for both 169 // the with-nulls and no-nulls cases without giving up a lot of performance. 170 type OptionalBitBlockCounter struct { 171 hasBitmap bool 172 pos int64 173 len int64 174 counter *BitBlockCounter 175 } 176 177 // NewOptionalBitBlockCounter constructs and returns a new bit block counter that 178 // can properly handle the case when a bitmap is null, if it is guaranteed that the 179 // the bitmap is not nil, then prefer NewBitBlockCounter here. 180 func NewOptionalBitBlockCounter(bitmap []byte, offset, length int64) *OptionalBitBlockCounter { 181 var counter *BitBlockCounter 182 if bitmap != nil { 183 counter = NewBitBlockCounter(bitmap, offset, length) 184 } 185 return &OptionalBitBlockCounter{ 186 hasBitmap: bitmap != nil, 187 pos: 0, 188 len: length, 189 counter: counter, 190 } 191 } 192 193 // NextBlock returns block count for next word when the bitmap is available otherwise 194 // return a block with length up to INT16_MAX when there is no validity 195 // bitmap (so all the referenced values are not null). 196 func (obc *OptionalBitBlockCounter) NextBlock() BitBlockCount { 197 const maxBlockSize = math.MaxInt16 198 if obc.hasBitmap { 199 block := obc.counter.NextWord() 200 obc.pos += int64(block.Len) 201 return block 202 } 203 204 blockSize := int16(utils.Min(maxBlockSize, obc.len-obc.pos)) 205 obc.pos += int64(blockSize) 206 // all values are non-null 207 return BitBlockCount{blockSize, blockSize} 208 } 209 210 // NextWord is like NextBlock, but returns a word-sized block even when there is no 211 // validity bitmap 212 func (obc *OptionalBitBlockCounter) NextWord() BitBlockCount { 213 const wordsize = 64 214 if obc.hasBitmap { 215 block := obc.counter.NextWord() 216 obc.pos += int64(block.Len) 217 return block 218 } 219 blockSize := int16(utils.Min(wordsize, obc.len-obc.pos)) 220 obc.pos += int64(blockSize) 221 // all values are non-null 222 return BitBlockCount{blockSize, blockSize} 223 } 224 225 // VisitBitBlocks is a utility for easily iterating through the blocks of bits in a bitmap, 226 // calling the appropriate visitValid/visitInvalid function as we iterate through the bits. 227 // visitValid is called with the bitoffset of the valid bit. Don't use this inside a tight 228 // loop when performance is needed and instead prefer manually constructing these loops 229 // in that scenario. 230 func VisitBitBlocks(bitmap []byte, offset, length int64, visitValid func(pos int64), visitInvalid func()) { 231 counter := NewOptionalBitBlockCounter(bitmap, offset, length) 232 pos := int64(0) 233 for pos < length { 234 block := counter.NextBlock() 235 if block.AllSet() { 236 for i := 0; i < int(block.Len); i, pos = i+1, pos+1 { 237 visitValid(pos) 238 } 239 } else if block.NoneSet() { 240 for i := 0; i < int(block.Len); i, pos = i+1, pos+1 { 241 visitInvalid() 242 } 243 } else { 244 for i := 0; i < int(block.Len); i, pos = i+1, pos+1 { 245 if bitutil.BitIsSet(bitmap, int(offset+pos)) { 246 visitValid(pos) 247 } else { 248 visitInvalid() 249 } 250 } 251 } 252 } 253 } 254 255 // VisitBitBlocks is a utility for easily iterating through the blocks of bits in a bitmap, 256 // calling the appropriate visitValid/visitInvalid function as we iterate through the bits. 257 // visitValid is called with the bitoffset of the valid bit. Don't use this inside a tight 258 // loop when performance is needed and instead prefer manually constructing these loops 259 // in that scenario. 260 func VisitBitBlocksShort(bitmap []byte, offset, length int64, visitValid func(pos int64) error, visitInvalid func() error) error { 261 counter := NewOptionalBitBlockCounter(bitmap, offset, length) 262 pos := int64(0) 263 for pos < length { 264 block := counter.NextBlock() 265 if block.AllSet() { 266 for i := 0; i < int(block.Len); i, pos = i+1, pos+1 { 267 if err := visitValid(pos); err != nil { 268 return err 269 } 270 } 271 } else if block.NoneSet() { 272 for i := 0; i < int(block.Len); i, pos = i+1, pos+1 { 273 if err := visitInvalid(); err != nil { 274 return err 275 } 276 } 277 } else { 278 for i := 0; i < int(block.Len); i, pos = i+1, pos+1 { 279 if bitutil.BitIsSet(bitmap, int(offset+pos)) { 280 if err := visitValid(pos); err != nil { 281 return err 282 } 283 } else { 284 if err := visitInvalid(); err != nil { 285 return err 286 } 287 } 288 } 289 } 290 } 291 return nil 292 } 293 294 func VisitTwoBitBlocks(leftBitmap, rightBitmap []byte, leftOffset, rightOffset int64, len int64, visitValid func(pos int64), visitNull func()) { 295 if leftBitmap == nil || rightBitmap == nil { 296 // at most one is present 297 if leftBitmap == nil { 298 VisitBitBlocks(rightBitmap, rightOffset, len, visitValid, visitNull) 299 } else { 300 VisitBitBlocks(leftBitmap, leftOffset, len, visitValid, visitNull) 301 } 302 return 303 } 304 305 bitCounter := NewBinaryBitBlockCounter(leftBitmap, rightBitmap, leftOffset, rightOffset, len) 306 var pos int64 307 for pos < len { 308 block := bitCounter.NextAndWord() 309 if block.AllSet() { 310 for i := 0; i < int(block.Len); i, pos = i+1, pos+1 { 311 visitValid(pos) 312 } 313 } else if block.NoneSet() { 314 for i := 0; i < int(block.Len); i, pos = i+1, pos+1 { 315 visitNull() 316 } 317 } else { 318 for i := 0; i < int(block.Len); i, pos = i+1, pos+1 { 319 if bitutil.BitIsSet(leftBitmap, int(leftOffset+pos)) && bitutil.BitIsSet(rightBitmap, int(rightOffset+pos)) { 320 visitValid(pos) 321 } else { 322 visitNull() 323 } 324 } 325 } 326 } 327 } 328 329 type bitOp struct { 330 bit func(bool, bool) bool 331 word func(uint64, uint64) uint64 332 } 333 334 var ( 335 bitBlockAnd = bitOp{ 336 bit: func(a, b bool) bool { return a && b }, 337 word: func(a, b uint64) uint64 { return a & b }, 338 } 339 bitBlockAndNot = bitOp{ 340 bit: func(a, b bool) bool { return a && !b }, 341 word: func(a, b uint64) uint64 { return a &^ b }, 342 } 343 bitBlockOr = bitOp{ 344 bit: func(a, b bool) bool { return a || b }, 345 word: func(a, b uint64) uint64 { return a | b }, 346 } 347 bitBlockOrNot = bitOp{ 348 bit: func(a, b bool) bool { return a || !b }, 349 word: func(a, b uint64) uint64 { return a | ^b }, 350 } 351 ) 352 353 // BinaryBitBlockCounter computes popcounts on the result of bitwise 354 // operations between two bitmaps, 64 bits at a time. A 64-bit word 355 // is loaded from each bitmap, then the popcount is computed on 356 // e.g. the bitwise-and of the two words 357 type BinaryBitBlockCounter struct { 358 left []byte 359 right []byte 360 bitsRemaining int64 361 leftOffset, rightOffset int64 362 363 bitsRequiredForWords int64 364 } 365 366 // NewBinaryBitBlockCounter constructs a binary bit block counter for 367 // computing the popcounts on the results of operations between 368 // the passed in bitmaps, with their respective offsets. 369 func NewBinaryBitBlockCounter(left, right []byte, leftOffset, rightOffset int64, length int64) *BinaryBitBlockCounter { 370 ret := &BinaryBitBlockCounter{ 371 left: left[leftOffset/8:], 372 right: right[rightOffset/8:], 373 leftOffset: leftOffset % 8, 374 rightOffset: rightOffset % 8, 375 bitsRemaining: length, 376 } 377 378 leftBitsReq := int64(64) 379 if ret.leftOffset != 0 { 380 leftBitsReq = 64 + (64 - ret.leftOffset) 381 } 382 rightBitsReq := int64(64) 383 if ret.rightOffset != 0 { 384 rightBitsReq = 64 + (64 - ret.rightOffset) 385 } 386 387 if leftBitsReq > rightBitsReq { 388 ret.bitsRequiredForWords = leftBitsReq 389 } else { 390 ret.bitsRequiredForWords = rightBitsReq 391 } 392 393 return ret 394 } 395 396 // NextAndWord returns the popcount of the bitwise-and of the next run 397 // of available bits, up to 64. The returned pair contains the size of 398 // the run and the number of true values. the last block will have a 399 // length less than 64 if the bitmap length is not a multiple of 64, 400 // and will return 0-length blocks in subsequent invocations 401 func (b *BinaryBitBlockCounter) NextAndWord() BitBlockCount { return b.nextWord(bitBlockAnd) } 402 403 // NextAndNotWord is like NextAndWord but performs x &^ y on each run 404 func (b *BinaryBitBlockCounter) NextAndNotWord() BitBlockCount { return b.nextWord(bitBlockAndNot) } 405 406 // NextOrWord is like NextAndWord but performs x | y on each run 407 func (b *BinaryBitBlockCounter) NextOrWord() BitBlockCount { return b.nextWord(bitBlockOr) } 408 409 // NextOrWord is like NextAndWord but performs x | ^y on each run 410 func (b *BinaryBitBlockCounter) NextOrNotWord() BitBlockCount { return b.nextWord(bitBlockOrNot) } 411 412 func (b *BinaryBitBlockCounter) nextWord(op bitOp) BitBlockCount { 413 if b.bitsRemaining == 0 { 414 return BitBlockCount{} 415 } 416 417 // when offset is >0, we need there to be a word beyond the last 418 // aligned word in the bitmap for the bit shifting logic 419 if b.bitsRemaining < b.bitsRequiredForWords { 420 runLength := int16(b.bitsRemaining) 421 if runLength > int16(wordBits) { 422 runLength = int16(wordBits) 423 } 424 425 var popcount int16 426 for i := int16(0); i < runLength; i++ { 427 if op.bit(bitutil.BitIsSet(b.left, int(b.leftOffset)+int(i)), 428 bitutil.BitIsSet(b.right, int(b.rightOffset)+int(i))) { 429 popcount++ 430 } 431 } 432 // this code path should trigger _at most_ 2 times. in the "two times" 433 // case, the first time the run length will be a multiple of 8. 434 b.left = b.left[runLength/8:] 435 b.right = b.right[runLength/8:] 436 b.bitsRemaining -= int64(runLength) 437 return BitBlockCount{Len: runLength, Popcnt: popcount} 438 } 439 440 var popcount int 441 if b.leftOffset == 0 && b.rightOffset == 0 { 442 popcount = bits.OnesCount64(op.word(loadWord(b.left), loadWord(b.right))) 443 } else { 444 leftWord := shiftWord(loadWord(b.left), loadWord(b.left[8:]), b.leftOffset) 445 rightWord := shiftWord(loadWord(b.right), loadWord(b.right[8:]), b.rightOffset) 446 popcount = bits.OnesCount64(op.word(leftWord, rightWord)) 447 } 448 b.left = b.left[wordBits/8:] 449 b.right = b.right[wordBits/8:] 450 b.bitsRemaining -= wordBits 451 return BitBlockCount{Len: int16(wordBits), Popcnt: int16(popcount)} 452 }