github.com/apache/arrow/go/v10@v10.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/v10/arrow/bitutil" 25 "github.com/apache/arrow/go/v10/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 type bitOp struct { 295 bit func(bool, bool) bool 296 word func(uint64, uint64) uint64 297 } 298 299 var ( 300 bitBlockAnd = bitOp{ 301 bit: func(a, b bool) bool { return a && b }, 302 word: func(a, b uint64) uint64 { return a & b }, 303 } 304 bitBlockAndNot = bitOp{ 305 bit: func(a, b bool) bool { return a && !b }, 306 word: func(a, b uint64) uint64 { return a &^ b }, 307 } 308 bitBlockOr = bitOp{ 309 bit: func(a, b bool) bool { return a || b }, 310 word: func(a, b uint64) uint64 { return a | b }, 311 } 312 bitBlockOrNot = bitOp{ 313 bit: func(a, b bool) bool { return a || !b }, 314 word: func(a, b uint64) uint64 { return a | ^b }, 315 } 316 ) 317 318 // BinaryBitBlockCounter computes popcounts on the result of bitwise 319 // operations between two bitmaps, 64 bits at a time. A 64-bit word 320 // is loaded from each bitmap, then the popcount is computed on 321 // e.g. the bitwise-and of the two words 322 type BinaryBitBlockCounter struct { 323 left []byte 324 right []byte 325 bitsRemaining int64 326 leftOffset, rightOffset int64 327 328 bitsRequiredForWords int64 329 } 330 331 // NewBinaryBitBlockCounter constructs a binary bit block counter for 332 // computing the popcounts on the results of operations between 333 // the passed in bitmaps, with their respective offsets. 334 func NewBinaryBitBlockCounter(left, right []byte, leftOffset, rightOffset int64, length int64) *BinaryBitBlockCounter { 335 ret := &BinaryBitBlockCounter{ 336 left: left[leftOffset/8:], 337 right: right[rightOffset/8:], 338 leftOffset: leftOffset % 8, 339 rightOffset: rightOffset % 8, 340 bitsRemaining: length, 341 } 342 343 leftBitsReq := int64(64) 344 if ret.leftOffset != 0 { 345 leftBitsReq = 64 + (64 - ret.leftOffset) 346 } 347 rightBitsReq := int64(64) 348 if ret.rightOffset != 0 { 349 rightBitsReq = 64 + (64 - ret.rightOffset) 350 } 351 352 if leftBitsReq > rightBitsReq { 353 ret.bitsRequiredForWords = leftBitsReq 354 } else { 355 ret.bitsRequiredForWords = rightBitsReq 356 } 357 358 return ret 359 } 360 361 // NextAndWord returns the popcount of the bitwise-and of the next run 362 // of available bits, up to 64. The returned pair contains the size of 363 // the run and the number of true values. the last block will have a 364 // length less than 64 if the bitmap length is not a multiple of 64, 365 // and will return 0-length blocks in subsequent invocations 366 func (b *BinaryBitBlockCounter) NextAndWord() BitBlockCount { return b.nextWord(bitBlockAnd) } 367 368 // NextAndNotWord is like NextAndWord but performs x &^ y on each run 369 func (b *BinaryBitBlockCounter) NextAndNotWord() BitBlockCount { return b.nextWord(bitBlockAndNot) } 370 371 // NextOrWord is like NextAndWord but performs x | y on each run 372 func (b *BinaryBitBlockCounter) NextOrWord() BitBlockCount { return b.nextWord(bitBlockOr) } 373 374 // NextOrWord is like NextAndWord but performs x | ^y on each run 375 func (b *BinaryBitBlockCounter) NextOrNotWord() BitBlockCount { return b.nextWord(bitBlockOrNot) } 376 377 func (b *BinaryBitBlockCounter) nextWord(op bitOp) BitBlockCount { 378 if b.bitsRemaining == 0 { 379 return BitBlockCount{} 380 } 381 382 // when offset is >0, we need there to be a word beyond the last 383 // aligned word in the bitmap for the bit shifting logic 384 if b.bitsRemaining < b.bitsRequiredForWords { 385 runLength := int16(b.bitsRemaining) 386 if runLength > int16(wordBits) { 387 runLength = int16(wordBits) 388 } 389 390 var popcount int16 391 for i := int16(0); i < runLength; i++ { 392 if op.bit(bitutil.BitIsSet(b.left, int(b.leftOffset)+int(i)), 393 bitutil.BitIsSet(b.right, int(b.rightOffset)+int(i))) { 394 popcount++ 395 } 396 } 397 // this code path should trigger _at most_ 2 times. in the "two times" 398 // case, the first time the run length will be a multiple of 8. 399 b.left = b.left[runLength/8:] 400 b.right = b.right[runLength/8:] 401 b.bitsRemaining -= int64(runLength) 402 return BitBlockCount{Len: runLength, Popcnt: popcount} 403 } 404 405 var popcount int 406 if b.leftOffset == 0 && b.rightOffset == 0 { 407 popcount = bits.OnesCount64(op.word(loadWord(b.left), loadWord(b.right))) 408 } else { 409 leftWord := shiftWord(loadWord(b.left), loadWord(b.left[8:]), b.leftOffset) 410 rightWord := shiftWord(loadWord(b.right), loadWord(b.right[8:]), b.rightOffset) 411 popcount = bits.OnesCount64(op.word(leftWord, rightWord)) 412 } 413 b.left = b.left[wordBits/8:] 414 b.right = b.right[wordBits/8:] 415 b.bitsRemaining -= wordBits 416 return BitBlockCount{Len: int16(wordBits), Popcnt: int16(popcount)} 417 }