Skip to content

Commit 7b5c5cd

Browse files
authored
add GetAndSet to bitmap (#942)
* add GetAndSet to bitmap * cleanup * benchmark GetAndSet
1 parent 442c0b9 commit 7b5c5cd

File tree

7 files changed

+160
-53
lines changed

7 files changed

+160
-53
lines changed

logger/slog.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2023 LiveKit, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
package logger
216

317
import (

logger/zaputil/deferrer.go

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package zaputil
1717
import (
1818
"sync"
1919

20+
"go.uber.org/atomic"
2021
"go.uber.org/zap"
2122
"go.uber.org/zap/zapcore"
2223
)
@@ -70,25 +71,27 @@ type DeferredFieldResolver func(args ...any)
7071

7172
func NewDeferrer() (*Deferrer, DeferredFieldResolver) {
7273
buf := &Deferrer{}
73-
var resolveOnce sync.Once
74+
var resolved atomic.Bool
7475
resolve := func(args ...any) {
75-
resolveOnce.Do(func() {
76-
fields := make([]zapcore.Field, 0, len(args))
77-
for i := 0; i < len(args); i++ {
78-
switch arg := args[i].(type) {
79-
case zapcore.Field:
80-
fields = append(fields, arg)
81-
case string:
82-
if i < len(args)-1 {
83-
fields = append(fields, zap.Any(arg, args[i+1]))
84-
i++
85-
}
76+
if resolved.Swap(true) {
77+
return
78+
}
79+
80+
fields := make([]zapcore.Field, 0, len(args))
81+
for i := 0; i < len(args); i++ {
82+
switch arg := args[i].(type) {
83+
case zapcore.Field:
84+
fields = append(fields, arg)
85+
case string:
86+
if i < len(args)-1 {
87+
fields = append(fields, zap.Any(arg, args[i+1]))
88+
i++
8689
}
8790
}
91+
}
8892

89-
buf.fields = fields
90-
buf.flush()
91-
})
93+
buf.fields = fields
94+
buf.flush()
9295
}
9396
return buf, resolve
9497
}
@@ -99,8 +102,6 @@ type deferredValueCore struct {
99102
}
100103

101104
func NewDeferredValueCore(core zapcore.Core, def *Deferrer) zapcore.Core {
102-
def.mu.Lock()
103-
defer def.mu.Unlock()
104105
return &deferredValueCore{core, def}
105106
}
106107

utils/bitmap.go

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,11 @@ type bitmapNumber interface {
2222

2323
type Bitmap[T bitmapNumber] struct {
2424
bits []uint64
25-
mask int
2625
}
2726

2827
func NewBitmap[T bitmapNumber](size int) *Bitmap[T] {
29-
numElems := 1 << bits.Len64(uint64(size+63)/64)
3028
return &Bitmap[T]{
31-
bits: make([]uint64, numElems),
32-
mask: numElems - 1,
29+
bits: make([]uint64, 1<<bits.Len64(uint64(size+63)/64)),
3330
}
3431
}
3532

@@ -38,61 +35,71 @@ func (b *Bitmap[T]) Len() int {
3835
}
3936

4037
func (b *Bitmap[T]) Set(val T) {
41-
s, o := b.getSlotAndOffset(val)
42-
b.bits[s&b.mask] |= 1 << o
38+
sm, s, o := b.getSlotAndOffset(val)
39+
b.bits[s&sm] |= 1 << o
40+
}
41+
42+
func (b *Bitmap[T]) GetAndSet(val T) bool {
43+
sm, s, o := b.getSlotAndOffset(val)
44+
prev := b.bits[s&sm]&(1<<o) != 0
45+
b.bits[s&sm] |= 1 << o
46+
return prev
4347
}
4448

4549
func (b *Bitmap[T]) SetRange(min, max T) {
4650
if max < min {
4751
return
4852
}
4953

50-
ls, rs, lo, ro := b.getSlotsAndOffsets(min, max)
54+
sm, ls, rs, lo, ro := b.getSlotsAndOffsets(min, max)
5155
if ls == rs {
52-
b.bits[ls&b.mask] |= (((1 << (ro - lo + 1)) - 1) << lo)
56+
b.bits[ls&sm] |= (((1 << (ro - lo + 1)) - 1) << lo)
5357
} else {
54-
b.bits[ls&b.mask] |= ^((1 << lo) - 1)
58+
b.bits[ls&sm] |= ^((1 << lo) - 1)
5559
for i := ls + 1; i < rs; i++ {
56-
b.bits[i&b.mask] = ^uint64(0)
60+
b.bits[i&sm] = ^uint64(0)
5761
}
58-
b.bits[rs&b.mask] |= (1 << (ro + 1)) - 1
62+
b.bits[rs&sm] |= (1 << (ro + 1)) - 1
5963
}
6064
}
6165

6266
func (b *Bitmap[T]) Clear(val T) {
63-
s, o := b.getSlotAndOffset(val)
64-
b.bits[s&b.mask] &= ^(1 << o)
67+
sm, s, o := b.getSlotAndOffset(val)
68+
b.bits[s&sm] &= ^(1 << o)
6569
}
6670

6771
func (b *Bitmap[T]) ClearRange(min, max T) {
6872
if max < min {
6973
return
7074
}
7175

72-
ls, rs, lo, ro := b.getSlotsAndOffsets(min, max)
76+
sm, ls, rs, lo, ro := b.getSlotsAndOffsets(min, max)
7377
if ls == rs {
74-
b.bits[ls&b.mask] &= ^(((1 << (ro - lo + 1)) - 1) << lo)
78+
b.bits[ls&sm] &= ^(((1 << (ro - lo + 1)) - 1) << lo)
7579
} else {
76-
b.bits[ls&b.mask] &= ^uint64(0) >> (64 - lo)
80+
b.bits[ls&sm] &= ^uint64(0) >> (64 - lo)
7781
for i := ls + 1; i < rs; i++ {
78-
b.bits[i&b.mask] = 0
82+
b.bits[i&sm] = 0
7983
}
80-
b.bits[rs&b.mask] &= ^uint64(0) << (ro + 1)
84+
b.bits[rs&sm] &= ^uint64(0) << (ro + 1)
8185
}
8286
}
8387

8488
func (b *Bitmap[T]) IsSet(val T) bool {
85-
s, o := b.getSlotAndOffset(val)
86-
return b.bits[s&b.mask]&(1<<o) != 0
89+
sm, s, o := b.getSlotAndOffset(val)
90+
return b.bits[s&sm]&(1<<o) != 0
8791
}
8892

89-
func (b *Bitmap[T]) getSlotAndOffset(val T) (s, o int) {
90-
s = int(val >> 6) // slot
91-
o = int(val & 0x3f) // offset
93+
func (b *Bitmap[T]) getSlotAndOffset(val T) (sm, s, o int) {
94+
sm = len(b.bits) - 1 // slot mask
95+
s = int(val >> 6) // slot
96+
o = int(val & 0x3f) // offset
9297
return
9398
}
9499

95-
func (b *Bitmap[T]) getSlotsAndOffsets(min, max T) (ls, rs, lo, ro int) {
100+
func (b *Bitmap[T]) getSlotsAndOffsets(min, max T) (sm, ls, rs, lo, ro int) {
101+
sm = len(b.bits) - 1 // slot mask
102+
96103
ls = int(min >> 6) // left slot
97104
rs = int(max >> 6) // right slot
98105

utils/bitmap_test.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,31 @@ func TestBitmap(t *testing.T) {
9494
require.Equal(t, e, b.bits)
9595

9696
// large range changes touch each word once
97-
ls, rs, lo, ro := b.getSlotsAndOffsets(0, math.MaxUint32)
98-
require.Equal(t, []int{0, 4, 0, 0}, []int{ls, rs, lo, ro})
97+
sm, ls, rs, lo, ro := b.getSlotsAndOffsets(0, math.MaxUint32)
98+
require.Equal(t, []int{3, 0, 4, 0, 0}, []int{sm, ls, rs, lo, ro})
99+
}
100+
101+
func BenchmarkBitmap(b *testing.B) {
102+
m := NewBitmap[uint64](128)
103+
b.Run("SetRange", func(b *testing.B) {
104+
for range b.N {
105+
m.SetRange(100, 1100)
106+
}
107+
})
108+
b.Run("Set", func(b *testing.B) {
109+
for i := range b.N {
110+
m.Set(uint64(i))
111+
}
112+
})
113+
b.Run("IsSet/Set", func(b *testing.B) {
114+
for i := range b.N {
115+
_ = m.IsSet(uint64(i))
116+
m.Set(uint64(i))
117+
}
118+
})
119+
b.Run("GetAndSet", func(b *testing.B) {
120+
for i := range b.N {
121+
m.GetAndSet(uint64(i))
122+
}
123+
})
99124
}

utils/math.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@
1414

1515
package utils
1616

17-
import "math"
17+
import (
18+
"math"
19+
20+
"golang.org/x/exp/constraints"
21+
)
1822

1923
func LogisticFunc(x0, L, k float64) func(x float64) float64 {
2024
return func(x float64) float64 {
21-
return L / (1 + math.Pow(math.E, -k*(x-x0)))
25+
return L / (1 + math.Exp(-k*(x-x0)))
2226
}
2327
}
2428

@@ -27,3 +31,10 @@ func FastLogisticFunc(x0, L, k float64) func(x float64) float64 {
2731
return L / 2 * (1 + k*(x-x0)/(1+math.Abs(k*(x-x0))))
2832
}
2933
}
34+
35+
func Abs[T constraints.Signed | constraints.Float](v T) T {
36+
if v < 0 {
37+
return -v
38+
}
39+
return v
40+
}

utils/math_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2023 LiveKit, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package utils
16+
17+
import (
18+
"testing"
19+
)
20+
21+
func BenchmarkLogisticFunc(b *testing.B) {
22+
b.Run("LogisticFunc", func(b *testing.B) {
23+
l := LogisticFunc(0, 1, 1)
24+
step := 4.0 / float64(b.N)
25+
var i float64
26+
for range b.N {
27+
_ = l(i)
28+
i += step
29+
}
30+
})
31+
b.Run("FastLogisticFunc", func(b *testing.B) {
32+
l := FastLogisticFunc(0, 1, 1)
33+
step := 4.0 / float64(b.N)
34+
var i float64
35+
for range b.N {
36+
_ = l(i)
37+
i += step
38+
}
39+
})
40+
}

utils/timed_version.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,7 @@ func (t TimedVersion) Value() (driver.Value, error) {
166166
return nil, nil
167167
}
168168

169-
ts, ticks := timedVersionComponents(t)
170-
b := make([]byte, 0, 12)
171-
b = binary.BigEndian.AppendUint64(b, uint64(ts))
172-
b = binary.BigEndian.AppendUint32(b, uint32(ticks))
173-
return b, nil
169+
return t.MarshalBinary()
174170
}
175171

176172
func (t *TimedVersion) Scan(src interface{}) (err error) {
@@ -180,9 +176,7 @@ func (t *TimedVersion) Scan(src interface{}) (err error) {
180176
case 0:
181177
*t = 0
182178
case 12:
183-
ts := int64(binary.BigEndian.Uint64(b))
184-
ticks := int32(binary.BigEndian.Uint32(b[8:]))
185-
*t = timedVersionFromComponents(ts, ticks)
179+
t.UnmarshalBinary(b)
186180
default:
187181
return errors.New("(*TimedVersion).Scan: unsupported format")
188182
}
@@ -194,6 +188,21 @@ func (t *TimedVersion) Scan(src interface{}) (err error) {
194188
return nil
195189
}
196190

191+
func (t TimedVersion) MarshalBinary() ([]byte, error) {
192+
ts, ticks := timedVersionComponents(t)
193+
b := make([]byte, 0, 12)
194+
b = binary.BigEndian.AppendUint64(b, uint64(ts))
195+
b = binary.BigEndian.AppendUint32(b, uint32(ticks))
196+
return b, nil
197+
}
198+
199+
func (t *TimedVersion) UnmarshalBinary(b []byte) error {
200+
ts := int64(binary.BigEndian.Uint64(b))
201+
ticks := int32(binary.BigEndian.Uint32(b[8:]))
202+
*t = timedVersionFromComponents(ts, ticks)
203+
return nil
204+
}
205+
197206
func (t TimedVersion) GormDataType() string {
198207
return "bytes"
199208
}

0 commit comments

Comments
 (0)