Skip to content
This repository has been archived by the owner on Feb 27, 2023. It is now read-only.

Commit

Permalink
Merge pull request #1285 from antsystem/feat/add-bitmap
Browse files Browse the repository at this point in the history
pkg: add bitmap in pkg
  • Loading branch information
lowzj committed Apr 22, 2020
2 parents 4c2af2b + 9814193 commit 7aa7efc
Show file tree
Hide file tree
Showing 5 changed files with 597 additions and 1 deletion.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ require (
github.com/magiconair/properties v1.8.1 // indirect
github.com/mailru/easyjson v0.0.0-20170902151237-2a92e673c9a6 // indirect
github.com/mitchellh/mapstructure v1.1.2
github.com/openacid/low v0.1.10
github.com/pborman/uuid v0.0.0-20180122190007-c65b2f87fee3
github.com/pkg/errors v0.8.0
github.com/prashantv/gostub v1.0.0
Expand All @@ -38,7 +39,7 @@ require (
github.com/spf13/cobra v0.0.0-20181021141114-fe5e611709b0
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.4.0
github.com/stretchr/testify v1.2.2
github.com/stretchr/testify v1.3.0
github.com/valyala/fasthttp v1.3.0
github.com/willf/bitset v0.0.0-20190228212526-18bd95f470f9
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect
Expand Down
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ github.com/asaskevich/govalidator v0.0.0-20170903095215-73945b6115bf/go.mod h1:l
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
Expand All @@ -25,6 +26,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.7 h1:DVS0EPFHUiaJSaX2EKlaf65HUmk9PXhOl/Xa3Go242Q=
github.com/cpuguy83/go-md2man v1.0.7/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
Expand Down Expand Up @@ -115,6 +117,10 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
github.com/openacid/low v0.1.10 h1:rKpmB5CHtKoPq9tFiqUvRk8vtWaPympL2D2dNfw3PvI=
github.com/openacid/low v0.1.10/go.mod h1:QCkCiLykPRXaaZV76EsiRePPqQlqraEaV5WdGQh4qKk=
github.com/openacid/must v0.1.3/go.mod h1:luPiXCuJlEo3UUFQngVQokV0MPGryeYvtCbQPs3U1+I=
github.com/pborman/uuid v0.0.0-20180122190007-c65b2f87fee3 h1:9J0mOv1rXIBlRjQCiAGyx9C3dZZh5uIa3HU0oTV8v1E=
github.com/pborman/uuid v0.0.0-20180122190007-c65b2f87fee3/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
Expand Down Expand Up @@ -159,9 +165,12 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
Expand Down
287 changes: 287 additions & 0 deletions pkg/bitmap/bitmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
/*
* Copyright The Dragonfly Authors.
*
* Licensed 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 bitmap

import (
"encoding/binary"
"fmt"
"math"
"sync"

lbm "github.com/openacid/low/bitmap"
)

const (
// sizeOf64BitsLimit limits the max size of array of bitmap.
// It is limited by "github.com/openacid/low/bitmap".
sizeOf64BitsLimit = (math.MaxInt32 >> 6) + 1
)

// BitMap is a struct which provides the Get or Set of bits map.
type BitMap struct {
lock sync.RWMutex
bm []uint64
maxBitIndex uint32
}

// NewBitMap generates a BitMap.
func NewBitMap(sizeOf64Bits uint32, allSetBit bool) (*BitMap, error) {
if sizeOf64Bits > sizeOf64BitsLimit {
return nil, fmt.Errorf("sizeOf64Bits %d should be range[0, %d]", sizeOf64Bits, sizeOf64BitsLimit)
}

bm := make([]uint64, sizeOf64Bits)
if allSetBit {
for i := 0; i < int(sizeOf64Bits); i++ {
bm[i] = math.MaxUint64
}
}

return &BitMap{
bm: bm,
maxBitIndex: (sizeOf64Bits << 6) - 1,
}, nil
}

// RestoreBitMap generate the BitMap by input bytes.
func RestoreBitMap(data []byte) (*BitMap, error) {
if uint64(len(data)) > (sizeOf64BitsLimit << 3) {
return nil, fmt.Errorf("sizeOf64Bits %d should be range[0, %d]", uint64(len(data)), sizeOf64BitsLimit<<3)
}

bm := DecodeToUintArray(data)

return &BitMap{
bm: bm,
maxBitIndex: uint32(len(bm)*64 - 1),
}, nil
}

// Get gets the bits in range [start, end]. if set is true, return the set bits.
// else return the unset bits.
func (b *BitMap) Get(start uint32, end uint32, setBit bool) ([]*BitsRange, error) {
b.lock.RLock()
defer b.lock.RUnlock()

return b.getWithoutLock(start, end, setBit)
}

// Set sets or cleans the bits in range [start, end]. if setBit is true, set bits. else clean bits.
func (b *BitMap) Set(start uint32, end uint32, setBit bool) error {
b.lock.Lock()
defer b.lock.Unlock()

return b.setWithoutLock(start, end, setBit)
}

func (b *BitMap) getWithoutLock(start uint32, end uint32, setBit bool) ([]*BitsRange, error) {
if start > end {
return nil, fmt.Errorf("start %d should not bigger than %d", start, end)
}

if end > b.maxBitIndex {
return nil, fmt.Errorf("end %d should not bigger than %d", end, b.maxBitIndex)
}

rs := []*BitsRange{}
var lastRange *BitsRange

second64MinIndex := ((start >> 6) + 1) << 6
first64MaxIndex := end
if first64MaxIndex >= second64MinIndex {
first64MaxIndex = second64MinIndex - 1
}

last64MinIndex := (end >> 6) << 6

appendArr, last := b.mergeAndFetchRangeOfUint64(b.bm[start>>6], (start>>6)<<6, setBit, start, first64MaxIndex, lastRange)
rs = append(rs, appendArr...)
lastRange = last

for i := second64MinIndex; i < last64MinIndex; i += 64 {
appendArr, last := b.mergeAndFetchRangeOfUint64(b.bm[i>>6], (i>>6)<<6, setBit, i, i+63, lastRange)
rs = append(rs, appendArr...)
lastRange = last
}

// get range of last uint64
if last64MinIndex >= second64MinIndex {
appendArr, last := b.mergeAndFetchRangeOfUint64(b.bm[end>>6], (end>>6)<<6, setBit, last64MinIndex, end, lastRange)
rs = append(rs, appendArr...)
lastRange = last
}

if lastRange != nil {
rs = append(rs, lastRange)
}

return rs, nil
}

// mergeAndFetchRangeOfUint64 will get range of x, and merge prv range if possible.
// return the array of ranges and last range which may merged by next uint64.
func (b *BitMap) mergeAndFetchRangeOfUint64(x uint64, base uint32, setBit bool, limitStart uint32, limitEnd uint32, prv *BitsRange) (appendArr []*BitsRange, last *BitsRange) {
var (
start, end uint32
startIndex, endIndex uint32
out bool
)

appendArr = []*BitsRange{}

if !setBit {
x = uint64(^x)
}

for {
if x == 0 || out {
break
}

// start is the value of the trailing zeros of x
start = uint32(Ctz64(x))
// remove the trailing zeros of x and counts again
end = uint32(Ctz64(uint64(^(x >> start)))) + start

x = (x >> end) << end

startIndex = start + base
endIndex = end + base - 1

if endIndex < limitStart {
continue
}

if startIndex > limitEnd {
continue
}

if startIndex < limitStart {
startIndex = limitStart
}

if endIndex > limitEnd {
out = true
endIndex = limitEnd
}

if prv != nil {
if prv.EndIndex+1 == startIndex {
prv.EndIndex = endIndex
continue
}
appendArr = append(appendArr, prv)
}

prv = &BitsRange{
StartIndex: startIndex,
EndIndex: endIndex,
}
}

return appendArr, prv
}

func (b *BitMap) setWithoutLock(start uint32, end uint32, setBit bool) error {
if start > end {
return fmt.Errorf("start %d should not bigger than %d", start, end)
}

if end > b.maxBitIndex {
return fmt.Errorf("end %d should not bigger than %d", end, b.maxBitIndex)
}

second64MinIndex := ((start >> 6) + 1) << 6
first64MaxIndex := end
if first64MaxIndex >= second64MinIndex {
first64MaxIndex = second64MinIndex - 1
}

for i := start; i <= first64MaxIndex; i++ {
if setBit {
b.bm[i>>6] = b.bm[i>>6] | lbm.Bit[i&63]
} else {
b.bm[i>>6] = b.bm[i>>6] & (^lbm.Bit[i&63])
}
}

last64MinIndex := (end >> 6) << 6
if last64MinIndex < first64MaxIndex {
last64MinIndex = first64MaxIndex + 1
}

for i := second64MinIndex; i < last64MinIndex; i += 64 {
if setBit {
b.bm[i>>6] = math.MaxUint64
} else {
b.bm[i>>6] = 0
}
}

for i := last64MinIndex; i <= end; i++ {
if setBit {
b.bm[i>>6] = b.bm[i>>6] | lbm.Bit[i&63]
} else {
b.bm[i>>6] = b.bm[i>>6] & (^lbm.Bit[i&63])
}
}

return nil
}

func (b *BitMap) Encode() []byte {
b.lock.RLock()
defer b.lock.RUnlock()

return EncodeUintArray(b.bm)
}

// BitsRange shows the range of bitmap.
type BitsRange struct {
StartIndex uint32
EndIndex uint32
}

// EncodeUintArray encodes []uint64 to bytes.
func EncodeUintArray(input []uint64) []byte {
arrLen := len(input)
data := make([]byte, arrLen*8)

bytesIndex := 0
for i := 0; i < arrLen; i++ {
binary.LittleEndian.PutUint64(data[bytesIndex:bytesIndex+8], input[i])
bytesIndex += 8
}

return data[:bytesIndex]
}

// DecodeToUintArray decodes input bytes to []uint64.
func DecodeToUintArray(data []byte) []uint64 {
var (
bytesIndex int
)

arrLen := len(data) / 8
out := make([]uint64, arrLen)
for i := 0; i < arrLen; i++ {
out[i] = binary.LittleEndian.Uint64(data[bytesIndex : bytesIndex+8])
bytesIndex += 8
}

return out
}
Loading

0 comments on commit 7aa7efc

Please sign in to comment.