Skip to content

Commit

Permalink
Add generic support; CopyFrom() and Init() methods (#20)
Browse files Browse the repository at this point in the history
This code was last updated before Go generics. It's an obvious win here,
and was easy to do.
Also adds CopyFrom() and Init() method in support of
lightstep/otel-launcher-go#576 and new tests.
  • Loading branch information
jmacd authored Nov 29, 2023
1 parent ab68206 commit 36dea43
Show file tree
Hide file tree
Showing 17 changed files with 254 additions and 121 deletions.
16 changes: 0 additions & 16 deletions .circleci/config.yml

This file was deleted.

20 changes: 20 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior. Include code samples here when possible.

**Expected behavior**
A clear and concise description of what you expected to happen.

**Additional context**
Add any other context about the problem here.
20 changes: 20 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.
9 changes: 9 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
**Description:** <Describe what has changed.
Ex. Fixing a bug - Describe the bug and how this fixes the issue.
Ex. Adding a feature - Explain what this achieves.>

**Link to tracking Issue:** <Issue number if applicable>

**Testing:** < Describe what testing was performed and which tests were added.>

**Documentation:** < Describe the documentation added.>
32 changes: 32 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: build

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:

build:
strategy:
matrix:
go-version: [1.21.3]
name: Build
runs-on: ubuntu-latest
steps:

- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
id: go

- name: Check out code into the Go module directory
uses: actions/checkout@v2

- name: Build
run: go test -v ./...

- name: Code coverage
run: bash <(curl -s https://codecov.io/bash)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@ VarOpt supports merging independently collected samples one
observation at a time. This is useful for building distributed
sampling schemes. In this use-case, each node in a distributed system
computes a weighted sample. To combine samples, simply input all the
observations and their corresponding weights into a new VarOpt sample.
observations and their corresponding weights into a new VarOpt sample.
6 changes: 4 additions & 2 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,19 @@ func BenchmarkAdd_Exp_1000000(b *testing.B) {
benchmarkAdd(b, 1000000, expValue)
}

type thing struct{}

func benchmarkAdd(b *testing.B, size int, f func(rnd *rand.Rand) float64) {
b.ReportAllocs()
rnd := rand.New(rand.NewSource(3331))
v := varopt.New(size, rnd)
v := varopt.New[thing](size, rnd)
weights := make([]float64, b.N)
for i := 0; i < b.N; i++ {
weights[i] = f(rnd)
}

b.StartTimer()
for i := 0; i < b.N; i++ {
v.Add(nil, weights[i])
v.Add(thing{}, weights[i])
}
}
11 changes: 5 additions & 6 deletions frequency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type curve struct {
stddev float64
}

type point struct {
type testPoint struct {
color int
xvalue float64
}
Expand Down Expand Up @@ -47,7 +47,7 @@ func ExampleVaropt_GetOriginalWeight() {

// Construct a timeseries consisting of three colored signals,
// for x=0 to x=60 seconds.
var points []point
var points []testPoint

// origCounts stores the original signals at second granularity.
origCounts := make([][]int, len(colors))
Expand All @@ -66,7 +66,7 @@ func ExampleVaropt_GetOriginalWeight() {
continue
}
origCounts[choose][int(math.Floor(xvalue))]++
points = append(points, point{
points = append(points, testPoint{
color: choose,
xvalue: xvalue,
})
Expand All @@ -83,7 +83,7 @@ func ExampleVaropt_GetOriginalWeight() {
// weight. This ensures a uniform distribution of points in each
// second.
sampleSize := int(sampleRatio * float64(totalCount))
sampler := varopt.New(sampleSize, rnd)
sampler := varopt.New[testPoint](sampleSize, rnd)
for _, point := range points {
second := int(math.Floor(point.xvalue))
prob := float64(xcount[second]) / float64(totalCount)
Expand All @@ -103,9 +103,8 @@ func ExampleVaropt_GetOriginalWeight() {
// The effective count of each sample point is its output
// weight divided by its original weight.
for i := 0; i < sampler.Size(); i++ {
sample, weight := sampler.Get(i)
point, weight := sampler.Get(i)
origWeight := sampler.GetOriginalWeight(i)
point := sample.(point)
second := int(math.Floor(point.xvalue))
sampleCounts[point.color][second] += (weight / origWeight)
pointCounts[second]++
Expand Down
13 changes: 11 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
module github.com/lightstep/varopt

go 1.15
go 1.21

require github.com/stretchr/testify v1.4.0
require github.com/stretchr/testify v1.8.4

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
28 changes: 20 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
10 changes: 5 additions & 5 deletions internal/sampleheap.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

package internal

type Vsample struct {
Sample interface{}
type Vsample[T any] struct {
Sample T
Weight float64
}

type SampleHeap []Vsample
type SampleHeap[T any] []Vsample[T]

func (sh *SampleHeap) Push(v Vsample) {
func (sh *SampleHeap[T]) Push(v Vsample[T]) {
l := append(*sh, v)
n := len(l) - 1

Expand All @@ -27,7 +27,7 @@ func (sh *SampleHeap) Push(v Vsample) {
*sh = l
}

func (sh *SampleHeap) Pop() Vsample {
func (sh *SampleHeap[T]) Pop() Vsample[T] {
l := *sh
n := len(l) - 1
result := l[0]
Expand Down
9 changes: 6 additions & 3 deletions internal/sampleheap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,20 @@ func (s *simpleHeap) Pop() interface{} {
}

func TestLargeHeap(t *testing.T) {
var L internal.SampleHeap
var L internal.SampleHeap[float64]
var S simpleHeap

for i := 0; i < 1e6; i++ {
v := rand.NormFloat64()
L.Push(internal.Vsample{Weight: v})
L.Push(internal.Vsample[float64]{
Sample: v,
Weight: v,
})
heap.Push(&S, v)
}

for len(S) > 0 {
v1 := heap.Pop(&S).(float64)
v1 := heap.Pop(&S)
v2 := L.Pop().Weight

require.Equal(t, v1, v2)
Expand Down
33 changes: 17 additions & 16 deletions simple/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,62 +4,63 @@ package simple

import (
"math/rand"

"github.com/lightstep/varopt"
)

// Simple implements unweighted reservoir sampling using Algorithm R
// from "Random sampling with a reservoir" by Jeffrey Vitter (1985)
// https://en.wikipedia.org/wiki/Reservoir_sampling#Algorithm_R
type Simple struct {
type Simple[T any] struct {
capacity int
observed int
buffer []varopt.Sample
buffer []T
rnd *rand.Rand
}

// New returns a simple reservoir sampler with given capacity
// (i.e., reservoir size) and random number generator.
func New(capacity int, rnd *rand.Rand) *Simple {
return &Simple{
func New[T any](capacity int, rnd *rand.Rand) *Simple[T] {
s := &Simple[T]{}
s.Init(capacity, rnd)
return s
}

func (s *Simple[T]) Init(capacity int, rnd *rand.Rand) {
*s = Simple[T]{
capacity: capacity,
buffer: make([]T, 0, s.capacity),
rnd: rnd,
}
}

// Add considers a new observation for the sample. Items have unit
// weight.
func (s *Simple) Add(span varopt.Sample) {
func (s *Simple[T]) Add(item T) {
s.observed++

if s.buffer == nil {
s.buffer = make([]varopt.Sample, 0, s.capacity)
}

if len(s.buffer) < s.capacity {
s.buffer = append(s.buffer, span)
s.buffer = append(s.buffer, item)
return
}

// Give this a capacity/observed chance of replacing an existing entry.
index := s.rnd.Intn(s.observed)
if index < s.capacity {
s.buffer[index] = span
s.buffer[index] = item
}
}

// Get returns the i'th selected item from the sample.
func (s *Simple) Get(i int) varopt.Sample {
func (s *Simple[T]) Get(i int) T {
return s.buffer[i]
}

// Size returns the number of items in the sample. If the reservoir is
// full, Size() equals Capacity().
func (s *Simple) Size() int {
func (s *Simple[T]) Size() int {
return len(s.buffer)
}

// Count returns the number of items that were observed.
func (s *Simple) Count() int {
func (s *Simple[T]) Count() int {
return s.observed
}
8 changes: 3 additions & 5 deletions simple/simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import (
"github.com/stretchr/testify/require"
)

type iRec int

func TestSimple(t *testing.T) {
const (
popSize = 1e6
Expand All @@ -22,19 +20,19 @@ func TestSimple(t *testing.T) {

rnd := rand.New(rand.NewSource(17167))

ss := simple.New(sampleSize, rnd)
ss := simple.New[int](sampleSize, rnd)

psum := 0.
for i := 0; i < popSize; i++ {
ss.Add(iRec(i))
ss.Add(i)
psum += float64(i)
}

require.Equal(t, ss.Size(), sampleSize)

ssum := 0.0
for i := 0; i < sampleSize; i++ {
ssum += float64(ss.Get(i).(iRec))
ssum += float64(ss.Get(i))
}

require.InEpsilon(t, ssum/float64(ss.Size()), psum/popSize, epsilon)
Expand Down
Loading

0 comments on commit 36dea43

Please sign in to comment.