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  }