github.com/outcaste-io/sroar@v0.0.0-20221229172112-1fb64f14314c/container.go (about) 1 /* 2 * Copyright 2021 Dgraph Labs, Inc. and Contributors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package sroar 18 19 import ( 20 "encoding/hex" 21 "fmt" 22 "math" 23 "math/bits" 24 "os" 25 "strings" 26 ) 27 28 // container uses extra 4 []uint16 in the front as header. 29 // container[0] is used for storing the size of the container, expressed in Uint16. 30 // The container size cannot exceed the vicinity of 8KB. At 8KB, we switch from packed arrays to 31 // bitmaps. We can fit the entire uint16 worth of bitmaps in 8KB (2^16 / 8 = 8 32 // KB). 33 34 const ( 35 typeArray uint16 = 0x00 36 typeBitmap uint16 = 0x01 37 38 // Container header. 39 indexSize int = 0 40 indexType int = 1 41 indexCardinality int = 2 42 // Index 2 and 3 is used for cardinality. We need 2 uint16s to store cardinality because 43 // 2^16 will not fit in uint16. 44 startIdx uint16 = 4 45 46 minContainerSize = 64 // In Uint16. 47 // Bitmap container can contain 2^16 integers. Each integer would use one bit to represent. 48 // Given that our data is represented in []uint16s, that'd mean the size of container to store 49 // it would be divided by 16. 50 // 4 for header and 4096 for storing bitmap container. In Uint16. 51 maxContainerSize = 4 + (1<<16)/16 52 ) 53 54 func dataAt(data []uint16, i int) uint16 { return data[int(startIdx)+i] } 55 56 func incrCardinality(data []uint16) { 57 cur := getCardinality(data) 58 if cur+1 > math.MaxUint16 { 59 data[indexCardinality+1] = 1 60 } else { 61 data[indexCardinality]++ 62 } 63 } 64 65 var invalidCardinality int = math.MaxUint16 + 10 66 var maxCardinality int = math.MaxUint16 + 1 67 68 func getCardinality(data []uint16) int { 69 // This sum has to be done using two ints to avoid overflow. 70 return int(data[indexCardinality]) + int(data[indexCardinality+1]) 71 } 72 73 func setCardinality(data []uint16, c int) { 74 if c > math.MaxUint16 { 75 data[indexCardinality] = math.MaxUint16 76 data[indexCardinality+1] = uint16(c - math.MaxUint16) 77 } else { 78 data[indexCardinality] = uint16(c) 79 data[indexCardinality+1] = 0 80 } 81 } 82 83 func zeroOutContainer(c []uint16) { 84 switch c[indexType] { 85 case typeArray: 86 array(c).zeroOut() 87 case typeBitmap: 88 bitmap(c).zeroOut() 89 } 90 } 91 92 func removeRangeContainer(c []uint16, lo, hi uint16) { 93 switch c[indexType] { 94 case typeArray: 95 array(c).removeRange(lo, hi) 96 case typeBitmap: 97 bitmap(c).removeRange(lo, hi) 98 } 99 } 100 101 func calculateAndSetCardinality(data []uint16) { 102 if data[indexType] != typeBitmap { 103 panic("Non-bitmap containers should always have cardinality set correctly") 104 } 105 b := bitmap(data) 106 card := b.cardinality() 107 setCardinality(b, card) 108 } 109 110 type array []uint16 111 112 // find returns the index of the first element >= x. 113 // The index is based on data portion of the container, ignoring startIdx. 114 // If the element > than all elements present, then N is returned where N = cardinality of the 115 // container. 116 func (c array) find(x uint16) int { 117 N := getCardinality(c) 118 for i := int(startIdx); i < int(startIdx)+N; i++ { 119 if len(c) <= int(i) { 120 panic(fmt.Sprintf("find: %d len(c) %d <= i %d\n", x, len(c), i)) 121 } 122 if c[i] >= x { 123 return int(i - int(startIdx)) 124 } 125 } 126 return N 127 } 128 129 func (c array) rank(x uint16) int { 130 N := getCardinality(c) 131 idx := c.find(x) 132 if idx == N { 133 return -1 134 } 135 return idx 136 } 137 138 func (c array) has(x uint16) bool { 139 N := getCardinality(c) 140 idx := c.find(x) 141 if idx == N { 142 return false 143 } 144 return c[int(startIdx)+idx] == x 145 } 146 147 func (c array) add(x uint16) bool { 148 idx := c.find(x) 149 N := getCardinality(c) 150 offset := int(startIdx) + idx 151 152 if int(idx) < N { 153 if c[offset] == x { 154 return false 155 } 156 // The entry at offset is the first entry, which is greater than x. Move it to the right. 157 copy(c[offset+1:], c[offset:]) 158 } 159 if offset >= len(c) { 160 fmt.Printf("offset: %d len(c): %d idx: %d N: %d isFull: %v\n", offset, len(c), idx, N, c.isFull()) 161 fmt.Printf("array:\n%s\n", hex.Dump(toByteSlice(c))) 162 for ei, ee := range c { 163 fmt.Printf("[%d] %d\n", ei, ee) 164 } 165 fmt.Printf("Trying to add x: %d\n", x) 166 os.Exit(1) 167 } 168 c[offset] = x 169 incrCardinality(c) 170 return true 171 } 172 173 func (c array) remove(x uint16) bool { 174 idx := c.find(x) 175 N := getCardinality(c) 176 offset := int(startIdx) + idx 177 178 if int(idx) < N { 179 if c[offset] != x { 180 return false 181 } 182 copy(c[offset:], c[offset+1:]) 183 setCardinality(c, N-1) 184 return true 185 } 186 return false 187 } 188 189 func (c array) removeRange(lo, hi uint16) { 190 if hi < lo { 191 panic(fmt.Sprintf("args must satisfy lo <= hi, got lo: %d, hi: %d\n", lo, hi)) 192 } 193 loIdx := c.find(lo) 194 hiIdx := c.find(hi) 195 196 st := int(startIdx) 197 loVal := c[st+loIdx] 198 N := getCardinality(c) 199 200 // remove range doesn't intersect with any element in the array. 201 if hi < loVal || loIdx == N { 202 return 203 } 204 if hiIdx == N { 205 if loIdx > 0 { 206 c = c[:int(startIdx)+loIdx-1] 207 } else { 208 c = c[:int(startIdx)] 209 } 210 setCardinality(c, loIdx) 211 return 212 } 213 if c[st+hiIdx] == hi { 214 hiIdx++ 215 } 216 copy(c[st+loIdx:], c[st+hiIdx:]) 217 setCardinality(c, N-hiIdx+loIdx) 218 } 219 220 func (c array) zeroOut() { 221 setCardinality(c, 0) 222 } 223 224 // TODO: Figure out how memory allocation would work in these situations. Perhaps use allocator here? 225 func (c array) andArray(other array) []uint16 { 226 min := min(getCardinality(c), getCardinality(other)) 227 228 setc := c.all() 229 seto := other.all() 230 231 out := make([]uint16, int(startIdx)+min+1) 232 num := uint16(intersection2by2(setc, seto, out[startIdx:])) 233 234 // Truncate out to how many values were found. 235 out = out[:startIdx+num+1] 236 out[indexType] = typeArray 237 out[indexSize] = uint16(len(out)) 238 setCardinality(out, int(num)) 239 return out 240 } 241 242 // TODO: We can do this operation in-place on the src array. 243 func (c array) andNotArray(other array, buf []uint16) []uint16 { 244 max := getCardinality(c) 245 out := make([]uint16, int(startIdx)+max+1) 246 247 andRes := array(c.andArray(other)).all() 248 srcVals := array(c).all() 249 num := uint16(difference(srcVals, andRes, out[startIdx:])) 250 251 // Truncate out to how many values were found. 252 out = out[:startIdx+num+1] 253 out[indexType] = typeArray 254 out[indexSize] = uint16(len(out)) 255 setCardinality(out, int(num)) 256 return out 257 } 258 259 func (c array) orArray(other array, buf []uint16, runMode int) []uint16 { 260 // We ignore runInline for this call. 261 262 max := getCardinality(c) + getCardinality(other) 263 if max > 4096 { 264 // Use bitmap container. 265 out := bitmap(c.toBitmapContainer(buf)) 266 // For now, just keep it as a bitmap. No need to change if the 267 // cardinality is smaller than 4096. 268 out.orArray(other, nil, runMode|runInline) 269 // Return out because out is pointing to buf. This would allow the 270 // receiver to copy out. 271 return out 272 } 273 274 // The output would be of typeArray. 275 out := buf[:int(startIdx)+max] 276 num := union2by2(c.all(), other.all(), out[startIdx:]) 277 out[indexType] = typeArray 278 out[indexSize] = uint16(len(out)) 279 setCardinality(out, num) 280 return out 281 } 282 283 var tmp = make([]uint16, 8192) 284 285 func (c array) andBitmap(other bitmap) []uint16 { 286 out := make([]uint16, int(startIdx)+getCardinality(c)+2) // some extra space. 287 out[indexType] = typeArray 288 289 pos := startIdx 290 for _, x := range c.all() { 291 out[pos] = x 292 pos += other.bitValue(x) 293 } 294 295 // Ensure we have at least one empty slot at the end. 296 res := out[:pos+1] 297 res[indexSize] = uint16(len(res)) 298 setCardinality(res, int(pos-startIdx)) 299 return res 300 } 301 302 // TODO: Write an optmized version of this function. 303 func (c array) andNotBitmap(other bitmap, buf []uint16) []uint16 { 304 assert(len(buf) == maxContainerSize) 305 res := array(buf) 306 Memclr(res) 307 res[indexSize] = 4 308 for _, e := range c.all() { 309 if !other.has(e) { 310 res.add(e) 311 } 312 } 313 return res 314 } 315 316 func (c array) isFull() bool { 317 N := getCardinality(c) 318 return int(startIdx)+N >= len(c) 319 } 320 321 func (c array) all() []uint16 { 322 N := getCardinality(c) 323 return c[startIdx : int(startIdx)+N] 324 } 325 326 func (c array) minimum() uint16 { 327 N := getCardinality(c) 328 if N == 0 { 329 return 0 330 } 331 return c[startIdx] 332 } 333 334 func (c array) maximum() uint16 { 335 N := getCardinality(c) 336 if N == 0 { 337 return 0 338 } 339 return c[int(startIdx)+N-1] 340 } 341 342 func (c array) toBitmapContainer(buf []uint16) []uint16 { 343 if len(buf) == 0 { 344 buf = make([]uint16, maxContainerSize) 345 } else { 346 assert(len(buf) == maxContainerSize) 347 assert(len(buf) == copy(buf, empty)) 348 } 349 350 b := bitmap(buf) 351 b[indexSize] = maxContainerSize 352 b[indexType] = typeBitmap 353 setCardinality(b, getCardinality(c)) 354 355 data := b[startIdx:] 356 for _, x := range c.all() { 357 idx := x >> 4 358 pos := x & 0xF 359 data[idx] |= bitmapMask[pos] 360 } 361 return b 362 } 363 364 func (c array) String() string { 365 var b strings.Builder 366 b.WriteString(fmt.Sprintf("Size: %d\n", c[0])) 367 for i, val := range c[startIdx:] { 368 b.WriteString(fmt.Sprintf("%d: %d\n", i, val)) 369 } 370 return b.String() 371 } 372 373 type bitmap []uint16 374 375 var bitmapMask []uint16 376 377 func init() { 378 bitmapMask = make([]uint16, 16) 379 for i := 0; i < 16; i++ { 380 bitmapMask[i] = 1 << (15 - i) 381 } 382 } 383 384 func (b bitmap) add(x uint16) bool { 385 idx := x >> 4 386 pos := x & 0xF 387 388 if has := b[startIdx+idx] & bitmapMask[pos]; has > 0 { 389 return false 390 } 391 392 b[startIdx+idx] |= bitmapMask[pos] 393 incrCardinality(b) 394 return true 395 } 396 397 func (b bitmap) remove(x uint16) bool { 398 idx := x >> 4 399 pos := x & 0xF 400 401 c := getCardinality(b) 402 if has := b[startIdx+idx] & bitmapMask[pos]; has > 0 { 403 b[startIdx+idx] ^= bitmapMask[pos] 404 setCardinality(b, c-1) 405 return true 406 } 407 return false 408 } 409 410 func (b bitmap) removeRange(lo, hi uint16) { 411 loIdx := lo >> 4 412 loPos := lo & 0xF 413 414 hiIdx := hi >> 4 415 hiPos := hi & 0xF 416 417 N := getCardinality(b) 418 var removed int 419 for i := loIdx + 1; i < hiIdx; i++ { 420 removed += bits.OnesCount16(b[startIdx+i]) 421 b[startIdx+i] = 0 422 } 423 424 if loIdx == hiIdx { 425 for p := loPos; p <= hiPos; p++ { 426 if b[startIdx+loIdx]&bitmapMask[p] > 0 { 427 removed++ 428 } 429 b[startIdx+loIdx] &= ^bitmapMask[p] 430 } 431 setCardinality(b, N-removed) 432 return 433 } 434 for p := loPos; p < 1<<4; p++ { 435 if b[startIdx+loIdx]&bitmapMask[p] > 0 { 436 removed++ 437 } 438 b[startIdx+loIdx] &= ^bitmapMask[p] 439 } 440 for p := uint16(0); p <= hiPos; p++ { 441 if b[startIdx+hiIdx]&bitmapMask[p] > 0 { 442 removed++ 443 } 444 b[startIdx+hiIdx] &= ^bitmapMask[p] 445 } 446 setCardinality(b, N-removed) 447 } 448 449 func (b bitmap) has(x uint16) bool { 450 idx := x >> 4 451 pos := x & 0xF 452 has := b[startIdx+idx] & bitmapMask[pos] 453 return has > 0 454 } 455 456 func (b bitmap) rank(x uint16) int { 457 idx := x >> 4 458 pos := x & 0xF 459 if b[startIdx+idx]&bitmapMask[pos] == 0 { 460 return -1 461 } 462 463 var rank int 464 for i := 0; i < int(idx); i++ { 465 rank += bits.OnesCount16(b[int(startIdx)+i]) 466 } 467 for p := uint16(0); p <= pos; p++ { 468 if b[startIdx+idx]&bitmapMask[p] > 0 { 469 rank++ 470 } 471 } 472 return rank - 1 473 } 474 475 // TODO: This can perhaps be using SIMD instructions. 476 func (b bitmap) andBitmap(other bitmap) []uint16 { 477 out := make([]uint16, maxContainerSize) 478 out[indexSize] = maxContainerSize 479 out[indexType] = typeBitmap 480 var num int 481 for i := int(startIdx); i < len(b); i++ { 482 out[i] = b[i] & other[i] 483 num += bits.OnesCount16(out[i]) 484 } 485 setCardinality(out, num) 486 return out 487 } 488 489 func (b bitmap) orBitmap(other bitmap, buf []uint16, runMode int) []uint16 { 490 if runMode&runInline > 0 { 491 buf = b 492 } else { 493 copy(buf, b) // Copy over first. 494 } 495 buf[indexSize] = maxContainerSize 496 buf[indexType] = typeBitmap 497 498 if num := getCardinality(b); num == maxCardinality { 499 // do nothing. bitmap is already full. 500 501 } else if runMode&runLazy > 0 || num == invalidCardinality { 502 data := buf[startIdx:] 503 for i, v := range other[startIdx:] { 504 data[i] |= v 505 } 506 setCardinality(buf, invalidCardinality) 507 508 } else { 509 var num int 510 data := buf[startIdx:] 511 for i, v := range other[startIdx:] { 512 data[i] |= v 513 // We are going to iterate over the entire container. So, we can 514 // just recount the cardinality, starting from num=0. 515 num += bits.OnesCount16(data[i]) 516 } 517 setCardinality(buf, num) 518 } 519 if runMode&runInline > 0 { 520 return nil 521 } 522 return buf 523 } 524 525 func (b bitmap) andNotBitmap(other bitmap) []uint16 { 526 var num int 527 data := b[startIdx:] 528 for i, v := range other[startIdx:] { 529 data[i] = data[i] ^ (data[i] & v) 530 num += bits.OnesCount16(data[i]) 531 } 532 setCardinality(b, num) 533 return b 534 } 535 536 func (b bitmap) andNotArray(other array) []uint16 { 537 for _, e := range other.all() { 538 b.remove(e) 539 } 540 return b 541 } 542 543 func (b bitmap) orArray(other array, buf []uint16, runMode int) []uint16 { 544 if runMode&runInline > 0 { 545 buf = b 546 } else { 547 copy(buf, b) 548 } 549 550 if num := getCardinality(b); num == maxCardinality { 551 // do nothing. This bitmap is already full. 552 553 } else if runMode&runLazy > 0 || num == invalidCardinality { 554 // Avoid calculating the cardinality to speed up operations. 555 for _, x := range other.all() { 556 idx := x / 16 557 pos := x % 16 558 559 buf[startIdx+idx] |= bitmapMask[pos] 560 } 561 setCardinality(buf, invalidCardinality) 562 563 } else { 564 num := getCardinality(buf) 565 for _, x := range other.all() { 566 idx := x / 16 567 pos := x % 16 568 569 val := &buf[4+idx] 570 before := bits.OnesCount16(*val) 571 *val |= bitmapMask[pos] 572 after := bits.OnesCount16(*val) 573 num += after - before 574 } 575 setCardinality(buf, num) 576 } 577 578 if runMode&runInline > 0 { 579 return nil 580 } 581 return buf 582 } 583 584 func (b bitmap) all() []uint16 { 585 var res []uint16 586 data := b[startIdx:] 587 for idx := uint16(0); idx < uint16(len(data)); idx++ { 588 x := data[idx] 589 // TODO: This could potentially be optimized. 590 for pos := uint16(0); pos < 16; pos++ { 591 if x&bitmapMask[pos] > 0 { 592 res = append(res, (idx<<4)|pos) 593 } 594 } 595 } 596 return res 597 } 598 599 //TODO: It can be optimized. 600 func (b bitmap) selectAt(idx int) uint16 { 601 data := b[startIdx:] 602 n := uint16(len(data)) 603 for i := uint16(0); i < n; i++ { 604 x := data[i] 605 c := bits.OnesCount16(x) 606 if idx < c { 607 for pos := uint16(0); pos < 16; pos++ { 608 if idx == 0 && x&bitmapMask[pos] > 0 { 609 return i*16 + pos 610 } 611 if x&bitmapMask[pos] > 0 { 612 idx-- 613 } 614 } 615 616 } 617 idx -= c 618 } 619 panic("should not reach here") 620 } 621 622 // bitValue returns a 0 or a 1 depending upon whether x is present in the bitmap, where 1 means 623 // present and 0 means absent. 624 func (b bitmap) bitValue(x uint16) uint16 { 625 idx := x >> 4 626 return (b[4+idx] >> (15 - (x & 0xF))) & 1 627 } 628 629 func (b bitmap) isFull() bool { 630 return false 631 } 632 633 func (b bitmap) minimum() uint16 { 634 N := getCardinality(b) 635 if N == 0 { 636 return 0 637 } 638 for i, x := range b[startIdx:] { 639 lz := bits.LeadingZeros16(x) 640 if lz == 16 { 641 continue 642 } 643 return uint16(16*i + lz) 644 } 645 panic("We shouldn't reach here") 646 } 647 648 func (b bitmap) maximum() uint16 { 649 N := getCardinality(b) 650 if N == 0 { 651 return 0 652 } 653 for i := len(b) - 1; i >= int(startIdx); i-- { 654 x := b[i] 655 tz := bits.TrailingZeros16(x) 656 if tz == 16 { 657 continue 658 } 659 return uint16(16*i + 15 - tz) 660 } 661 panic("We shouldn't reach here") 662 } 663 664 func (b bitmap) cardinality() int { 665 var num int 666 for _, x := range b[startIdx:] { 667 num += bits.OnesCount16(x) 668 } 669 return num 670 } 671 672 var zeroContainer = make([]uint16, maxContainerSize) 673 674 func (b bitmap) zeroOut() { 675 setCardinality(b, 0) 676 copy(b[startIdx:], zeroContainer[startIdx:]) 677 } 678 679 var ( 680 runInline = 0x01 681 runLazy = 0x02 682 ) 683 684 func containerOr(ac, bc, buf []uint16, runMode int) []uint16 { 685 at := ac[indexType] 686 bt := bc[indexType] 687 688 if at == typeArray && bt == typeArray { 689 left := array(ac) 690 right := array(bc) 691 // We can't always inline this function. If the right container has 692 // enough entries, trying to do a union with the left container inplace 693 // could end up overwriting the left container entries. So, we use a 694 // buffer to hold all output, and then copy it over to left. 695 // 696 // TODO: If right doesn't have a lot of entries, we could just iterate 697 // over left and merge the entries from right inplace. Would be faster 698 // than copying over all entries into buffer. Worth trying that approach. 699 return left.orArray(right, buf, runMode) 700 } 701 if at == typeArray && bt == typeBitmap { 702 left := array(ac) 703 right := bitmap(bc) 704 // Don't run inline for this call. 705 return right.orArray(left, buf, runMode&^runInline) 706 } 707 708 // These two following cases can be fully inlined. 709 if at == typeBitmap && bt == typeArray { 710 left := bitmap(ac) 711 right := array(bc) 712 return left.orArray(right, buf, runMode) 713 } 714 if at == typeBitmap && bt == typeBitmap { 715 left := bitmap(ac) 716 right := bitmap(bc) 717 return left.orBitmap(right, buf, runMode) 718 } 719 panic("containerAnd: We should not reach here") 720 } 721 722 func containerAnd(ac, bc []uint16) []uint16 { 723 at := ac[indexType] 724 bt := bc[indexType] 725 726 if at == typeArray && bt == typeArray { 727 left := array(ac) 728 right := array(bc) 729 return left.andArray(right) 730 } 731 if at == typeArray && bt == typeBitmap { 732 left := array(ac) 733 right := bitmap(bc) 734 return left.andBitmap(right) 735 } 736 if at == typeBitmap && bt == typeArray { 737 left := bitmap(ac) 738 right := array(bc) 739 out := right.andBitmap(left) 740 return out 741 } 742 if at == typeBitmap && bt == typeBitmap { 743 left := bitmap(ac) 744 right := bitmap(bc) 745 return left.andBitmap(right) 746 } 747 panic("containerAnd: We should not reach here") 748 } 749 750 // TODO: Optimize this function. 751 func containerAndNot(ac, bc, buf []uint16) []uint16 { 752 at := ac[indexType] 753 bt := bc[indexType] 754 755 if at == typeArray && bt == typeArray { 756 left := array(ac) 757 right := array(bc) 758 return left.andNotArray(right, buf) 759 } 760 if at == typeArray && bt == typeBitmap { 761 left := array(ac) 762 right := bitmap(bc) 763 return left.andNotBitmap(right, buf) 764 } 765 if at == typeBitmap && bt == typeArray { 766 left := bitmap(ac) 767 right := array(bc) 768 out := left.andNotArray(right) 769 return out 770 } 771 if at == typeBitmap && bt == typeBitmap { 772 left := bitmap(ac) 773 right := bitmap(bc) 774 return left.andNotBitmap(right) 775 } 776 panic("containerAndNot: We should not reach here") 777 }