generated from cloudwego/.github
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
336 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/* | ||
* Copyright 2024 CloudWeGo 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 ring | ||
|
||
// Ring is a GC friendly ring implementation. | ||
// items are allocated by one malloc and cannot be resized. Item inside can be accesses and modified. | ||
// type V must NOT contain pointer for performance concern. | ||
type Ring[V any] struct { | ||
items []Item[V] | ||
} | ||
|
||
// Item is the element stored in the Ring | ||
type Item[V any] struct { | ||
value V | ||
idx int | ||
} | ||
|
||
func NewFromSlice[V any](vv []V) *Ring[V] { | ||
r := &Ring[V]{} | ||
r.items = make([]Item[V], len(vv)) | ||
for i := 0; i < len(vv); i++ { | ||
r.items[i].value = vv[i] | ||
r.items[i].idx = i | ||
} | ||
return r | ||
} | ||
|
||
// Head returns the first item. | ||
func (r *Ring[V]) Head() *Item[V] { | ||
if len(r.items) == 0 { | ||
return nil | ||
} | ||
return &r.items[0] | ||
} | ||
|
||
// Get returns the ith item. | ||
func (r *Ring[V]) Get(i int) (*Item[V], bool) { | ||
if i < 0 || i >= len(r.items) { | ||
return nil, false | ||
} | ||
return &r.items[i], true | ||
} | ||
|
||
// Next returns the next item of the ith item. | ||
// Return the first(idx=0) item if i == len(r.items) - 1. | ||
func (r *Ring[V]) Next(i int) (*Item[V], bool) { | ||
if i < 0 || i >= len(r.items) { | ||
return nil, false | ||
} | ||
if i == len(r.items)-1 { | ||
return &r.items[0], true | ||
} | ||
return &r.items[i+1], true | ||
} | ||
|
||
// Prev returns the previous item of the ith item | ||
// Return the last item(idx=len(items)-1) if i == 0. | ||
func (r *Ring[V]) Prev(i int) (*Item[V], bool) { | ||
if i < 0 || i >= len(r.items) { | ||
return nil, false | ||
} | ||
if i == 0 { | ||
return &r.items[len(r.items)-1], true | ||
} | ||
return &r.items[i-1], true | ||
} | ||
|
||
// Move returns the item moving n step from the ith item. | ||
func (r *Ring[V]) Move(i, n int) (*Item[V], bool) { | ||
if i < 0 || i >= len(r.items) { | ||
return nil, false | ||
} | ||
var idx int | ||
if n >= 0 { | ||
idx = (i + n) % len(r.items) | ||
} else { | ||
idx = len(r.items) + (i+n)%len(r.items) | ||
} | ||
return &r.items[idx], true | ||
} | ||
|
||
// Do calls function f on each item of the ring in forward order. | ||
func (r *Ring[V]) Do(f func(v *V)) { | ||
for i := 0; i < len(r.items); i++ { | ||
f(&r.items[i].value) | ||
} | ||
} | ||
|
||
// Len returns the length of the ring. | ||
func (r *Ring[V]) Len() int { | ||
return len(r.items) | ||
} | ||
|
||
// Index returns the index of the item in the ring. | ||
func (it *Item[V]) Index() int { | ||
return it.idx | ||
} | ||
|
||
// Value returns the value of the item. | ||
func (it *Item[V]) Value() V { | ||
return it.value | ||
} | ||
|
||
// Pointer returns the pointer of the item. | ||
// Use Pointer if you want to modify V. | ||
// Do not reference to the pointer from other place. | ||
func (it *Item[V]) Pointer() *V { | ||
return &it.value | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
/* | ||
* Copyright 2024 CloudWeGo 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 ring | ||
|
||
import ( | ||
"container/ring" | ||
"fmt" | ||
"math/rand" | ||
"runtime" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
type ringItem struct { | ||
value int | ||
} | ||
|
||
func newRandomValue(n int) []int { | ||
vs := make([]int, 0, n) | ||
for i := 0; i < n; i++ { | ||
vs = append(vs, rand.Intn(n)) | ||
} | ||
return vs | ||
} | ||
|
||
func newRingItemSlice(vs []int) []ringItem { | ||
items := make([]ringItem, 0, len(vs)) | ||
for i := 0; i < len(vs); i++ { | ||
items = append(items, ringItem{value: vs[i]}) | ||
} | ||
return items | ||
} | ||
|
||
func newStdRing(vs []ringItem) *ring.Ring { | ||
r := ring.New(len(vs)) | ||
for i := 0; i < len(vs); i++ { | ||
r.Value = &vs[i] | ||
r = r.Next() | ||
} | ||
return r | ||
} | ||
|
||
func TestRing(t *testing.T) { | ||
n := 100 | ||
vs := newRandomValue(n) | ||
|
||
r := NewFromSlice(newRingItemSlice(vs)) | ||
// Get | ||
for i := 0; i < n; i++ { | ||
it, ok := r.Get(i) | ||
assert.True(t, ok) | ||
assert.Equal(t, vs[i], it.Value().value) | ||
assert.Equal(t, vs[i], it.Pointer().value) | ||
} | ||
// Next | ||
curr := r.Head() | ||
h, _ := r.Get(0) | ||
assert.Equal(t, curr, h) | ||
for i := 0; i < n; i++ { | ||
next, ok := r.Next(curr.Index()) | ||
assert.True(t, ok) | ||
curr = next | ||
} | ||
assert.Equal(t, curr, h) // back to head | ||
_, ok := r.Next(n + 1) | ||
assert.False(t, ok) | ||
// Prev | ||
for i := 0; i < n; i++ { | ||
prev, ok := r.Prev(curr.Index()) | ||
assert.True(t, ok) | ||
curr = prev | ||
} | ||
assert.Equal(t, curr, h) // back to head | ||
_, ok = r.Prev(n + 1) | ||
assert.False(t, ok) | ||
// Do | ||
var ( | ||
expectedTotal int | ||
actualTotal int | ||
) | ||
r.Do(func(v *ringItem) { | ||
actualTotal += v.value | ||
}) | ||
for i := 0; i < n; i++ { | ||
expectedTotal += vs[i] | ||
} | ||
assert.Equal(t, expectedTotal, actualTotal) | ||
// Modify | ||
for i := 0; i < n; i++ { | ||
it, ok := r.Get(i) | ||
assert.True(t, ok) | ||
newValue := i | ||
it.Pointer().value = newValue | ||
assert.Equal(t, newValue, it.Value().value) | ||
} | ||
} | ||
|
||
func TestMove(t *testing.T) { | ||
n := 100 | ||
vs := newRandomValue(n) | ||
r := NewFromSlice(newRingItemSlice(vs)) | ||
|
||
realNext, _ := r.Move(98, 2) | ||
expectedNext, _ := r.Get(0) | ||
assert.Equal(t, realNext, expectedNext) | ||
|
||
realNext, _ = r.Move(98, n+1) | ||
expectedNext, _ = r.Get(99) | ||
assert.Equal(t, realNext, expectedNext) | ||
|
||
realNext, _ = r.Move(1, -2) | ||
expectedNext, _ = r.Get(99) | ||
assert.Equal(t, realNext, expectedNext) | ||
|
||
realNext, _ = r.Move(1, -(2 + n)) | ||
expectedNext, _ = r.Get(99) | ||
assert.Equal(t, realNext, expectedNext) | ||
} | ||
|
||
func BenchmarkNew(b *testing.B) { | ||
nn := []int{100000, 400000} | ||
for _, n := range nn { | ||
vs := newRandomValue(n) | ||
|
||
b.Run(fmt.Sprintf("std-keysize_n_%d", n), func(b *testing.B) { | ||
b.ResetTimer() | ||
for j := 0; j < b.N; j++ { | ||
stdRing := newStdRing(newRingItemSlice(vs)) | ||
_ = stdRing | ||
} | ||
}) | ||
runtime.GC() | ||
|
||
b.Run(fmt.Sprintf("new-keysize_n_%d", n), func(b *testing.B) { | ||
b.ResetTimer() | ||
for j := 0; j < b.N; j++ { | ||
newRing := NewFromSlice(newRingItemSlice(vs)) | ||
_ = newRing | ||
} | ||
}) | ||
runtime.GC() | ||
} | ||
} | ||
|
||
func BenchmarkDo(b *testing.B) { | ||
nn := []int{10000, 40000} | ||
for _, n := range nn { | ||
vs := newRandomValue(n) | ||
b.Run(fmt.Sprintf("std-keysize_n_%d", n), func(b *testing.B) { | ||
b.ResetTimer() | ||
stdRing := newStdRing(newRingItemSlice(vs)) | ||
for j := 0; j < b.N; j++ { | ||
stdRing.Do(func(i any) {}) | ||
} | ||
}) | ||
runtime.GC() | ||
|
||
b.Run(fmt.Sprintf("new-keysize_n_%d", n), func(b *testing.B) { | ||
b.ResetTimer() | ||
newRing := NewFromSlice(newRingItemSlice(vs)) | ||
for j := 0; j < b.N; j++ { | ||
newRing.Do(func(i *ringItem) {}) | ||
} | ||
}) | ||
runtime.GC() | ||
} | ||
} | ||
|
||
func BenchmarkGC(b *testing.B) { | ||
nn := []int{100000, 400000} | ||
for _, n := range nn { | ||
vs := newRandomValue(n) | ||
|
||
b.Run(fmt.Sprintf("std-keysize_n_%d", n), func(b *testing.B) { | ||
stdRing := newStdRing(newRingItemSlice(vs)) | ||
b.ResetTimer() | ||
for j := 0; j < b.N; j++ { | ||
runtime.GC() | ||
} | ||
runtime.KeepAlive(stdRing) | ||
stdRing = nil | ||
_ = stdRing | ||
}) | ||
runtime.GC() | ||
|
||
b.Run(fmt.Sprintf("new-keysize_n_%d", n), func(b *testing.B) { | ||
newRing := NewFromSlice(newRingItemSlice(vs)) | ||
b.ResetTimer() | ||
for j := 0; j < b.N; j++ { | ||
runtime.GC() | ||
} | ||
runtime.KeepAlive(newRing) | ||
newRing = nil | ||
_ = newRing | ||
}) | ||
runtime.GC() | ||
} | ||
} |