region.go (185 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package txfile import ( "sort" ) // region values represent a continuous set of pages. type region struct { id PageID count uint32 } type regionList []region // PageIDs represent pages. The minimal page size is 512Bytes + // all contents in a file must be addressable by offset. This gives us // 9 bytes to store additional flags or value in the entry. const ( maxRegionEncSz = 8 + 4 entryBits = 9 // region entry header size in bits entryOverflow = (1 << 8) - 1 // overflow marker == all counter bits set entryOverflowMarker = uint64(entryOverflow) << (64 - entryBits) entryCounterMask = uint32(((1 << (entryBits - 1)) - 1)) entryMetaFlag = 1 << 63 // indicates the region holding pages used by the meta-area ) func (l regionList) Len() int { return len(l) } func (l *regionList) Add(reg region) { *l = append(*l, reg) } func (l regionList) Sort() { sort.Slice(l, func(i, j int) bool { return l[i].Before(l[j]) }) } func (l *regionList) MergeAdjacent() { if len(*l) <= 1 { return } tmp := (*l)[:1] i := 0 for _, r := range (*l)[1:] { if regionsMergable(tmp[i], r) { tmp[i] = mergeRegions(tmp[i], r) } else { tmp = append(tmp, r) i = i + 1 } } *l = tmp } func (l regionList) CountPagesUpTo(id PageID) (count uint) { for _, reg := range l { if reg.id >= id { break } start, end := reg.Range() if end > id { end = id } count += uint(end - start) } return } func (l regionList) CountPages() (count uint) { for _, reg := range l { count += uint(reg.count) } return } func (l regionList) EachPage(fn func(PageID)) { for _, reg := range l { reg.EachPage(fn) } } func (l regionList) EachRegion(fn func(region)) { for _, reg := range l { fn(reg) } } func (l regionList) PageIDs() (ids idList) { l.EachPage(ids.Add) return } func (r region) Start() PageID { return r.id } func (r region) End() PageID { return r.id + PageID(r.count) } func (r region) Range() (PageID, PageID) { return r.Start(), r.End() } func (r region) InRange(id PageID) bool { return r.Start() <= id && id < r.End() } func (r region) SplitAt(id PageID) region { start, end := r.Range() if id <= start || end < id { return region{} } if end > id { end = id } return region{id: start, count: uint32(end - start)} } func (r region) EachPage(fn func(PageID)) { for id, end := r.Range(); id != end; id++ { fn(id) } } func (r region) PageIDs() (ids idList) { r.EachPage(ids.Add) return } func (r region) Before(other region) bool { return r.id < other.id } func (r region) Precedes(other region) bool { return r.id+PageID(r.count) == other.id } func mergeRegions(a, b region) region { return region{id: a.id, count: a.count + b.count} } // mergeRegionLists merges 2 sorter regionLists into a new sorted region list. // Adjacent regions will be merged into a single region as well. func mergeRegionLists(a, b regionList) regionList { L := len(a) + len(b) if L == 0 { return nil } final := make(regionList, 0, L) for len(a) > 0 && len(b) > 0 { if a[0].Before(b[0]) { final, a = append(final, a[0]), a[1:] } else { final, b = append(final, b[0]), b[1:] } } // copy leftover elements final = append(final, a...) final = append(final, b...) final.MergeAdjacent() return final } // regionsMergable checks region a directly precedes regions b and // the region counter will not overflow. func regionsMergable(a, b region) bool { if !a.Before(b) { a, b = b, a } return a.Precedes(b) && (a.count+b.count) > a.count } // optimizeRegionList sorts and merges adjacent regions. func optimizeRegionList(reg *regionList) { initLen := reg.Len() reg.Sort() reg.MergeAdjacent() if l := reg.Len(); initLen > l { tmp := make(regionList, l, l) copy(tmp, *reg) *reg = tmp } } // (de-)serialization // ------------------ func regionEncodingSize(r region) int { if r.count < entryOverflow { return (&u64{}).Len() } return (&u64{}).Len() + (&u32{}).Len() } func encodeRegion(buf []byte, isMeta bool, reg region) int { flag := uint64(0) if isMeta { flag = entryMetaFlag } payload := buf entry := castU64(payload) payload = payload[entry.Len():] if reg.count < entryOverflow { count := uint64(reg.count) << (64 - entryBits) entry.Set(flag | count | uint64(reg.id)) } else { count := castU32(payload) payload = payload[count.Len():] entry.Set(flag | entryOverflowMarker | uint64(reg.id)) count.Set(reg.count) } return len(buf) - len(payload) } func decodeRegion(buf []byte) (bool, region, int) { payload := buf entry := castU64(payload) value := entry.Get() payload = payload[entry.Len():] id := PageID((value << entryBits) >> entryBits) isMeta := (entryMetaFlag & value) == entryMetaFlag count := uint32(value>>(64-entryBits)) & entryCounterMask switch count { case 0: count = 1 case entryOverflow: extra := castU32(payload) count, payload = extra.Get(), payload[extra.Len():] } return isMeta, region{id: id, count: count}, len(buf) - len(payload) }