github.com/grailbio/base@v0.0.11/diagnostic/memsize/interval_set.go (about) 1 package memsize 2 3 import ( 4 "sort" 5 ) 6 7 // interval represents a range of integers from start (inclusive) to start+length (not inclusive) 8 type interval struct { 9 start uintptr 10 length int64 11 } 12 13 // intervalSet is a collection of intervals 14 // new intervals can be added and the total covered size computed. 15 // with some frequency, intervals will be compacted to save memory. 16 type intervalSet struct { 17 data []interval 18 nextCompact int 19 } 20 21 func (r *intervalSet) add(interval interval) { 22 if interval.length == 0 { 23 return 24 } 25 r.data = append(r.data, interval) 26 if len(r.data) >= r.nextCompact { 27 r.compact() 28 } 29 } 30 31 // compact sorts the intervals and merges adjacent intervals if they overlap. 32 func (r *intervalSet) compact() { 33 defer r.setNextCompact() 34 if len(r.data) < 2 { 35 return 36 } 37 sort.Slice(r.data, func(i, j int) bool { 38 return r.data[i].start < r.data[j].start 39 }) 40 basePtr := 0 41 aheadPtr := 1 42 for aheadPtr < len(r.data) { 43 if overlaps(r.data[basePtr], r.data[aheadPtr]) { 44 r.data[basePtr].length = max(r.data[basePtr].length, int64(r.data[aheadPtr].start-r.data[basePtr].start)+r.data[aheadPtr].length) 45 aheadPtr++ 46 } else { 47 basePtr++ 48 r.data[basePtr] = r.data[aheadPtr] 49 aheadPtr++ 50 } 51 } 52 r.data = r.data[0 : basePtr+1] 53 54 // if the data will fit into a much smaller backing array, then copy to a smaller backing array to save memory. 55 if len(r.data) < cap(r.data)/4 && len(r.data) > 100 { 56 dataCopy := append([]interval{}, r.data...) // copy r.data to smaller array 57 r.data = dataCopy 58 } 59 } 60 61 // setNextCompact sets the size that r.data must reach before the next compacting 62 func (r *intervalSet) setNextCompact() { 63 r.nextCompact = int(float64(len(r.data)) * 1.2) // increase current length by at least 20% 64 if r.nextCompact < cap(r.data) { // do not compact before reaching capacity of data 65 r.nextCompact = cap(r.data) 66 } 67 if r.nextCompact < 10 { // do not compact before reaching 10 elements. 68 r.nextCompact = 10 69 } 70 } 71 72 // tests for overlaps between two intervals. 73 // precondition: x.start <= y.start 74 func overlaps(x, y interval) bool { 75 return x.start+uintptr(x.length) >= y.start 76 } 77 78 // totalCovered returns the total number of integers covered by the intervalSet 79 func (r *intervalSet) totalCovered() int { 80 if len(r.data) == 0 { 81 return 0 82 } 83 sort.Slice(r.data, func(i, j int) bool { 84 return r.data[i].start < r.data[j].start 85 }) 86 total := 0 87 curInterval := interval{start: r.data[0].start, length: 0} // zero width interval for initialization 88 for _, val := range r.data { 89 if overlaps(curInterval, val) { // extend the current interval 90 curInterval.length = max(curInterval.length, int64(val.start-curInterval.start)+val.length) 91 } else { // start a new interval 92 total += int(curInterval.length) 93 curInterval = val 94 } 95 } 96 total += int(curInterval.length) 97 return total 98 } 99 100 func max(i, j int64) int64 { 101 if i > j { 102 return i 103 } 104 return j 105 }