From 391e6d957549d56a47a3242938659cd82b26a5a3 Mon Sep 17 00:00:00 2001 From: Minke Zhang Date: Thu, 4 Aug 2022 23:38:07 -0700 Subject: [PATCH] Add kyroy/kdtree performance data --- container/kyroy/kyroy.go | 71 +++++++++++++++++++++++++++++++++++ container/kyroy/kyroy_test.go | 8 ++++ go.mod | 3 ++ go.sum | 11 ++++++ internal/perf/perf_test.go | 56 ++++++++++++++++++++++----- 5 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 container/kyroy/kyroy.go create mode 100644 container/kyroy/kyroy_test.go diff --git a/container/kyroy/kyroy.go b/container/kyroy/kyroy.go new file mode 100644 index 0000000..da75754 --- /dev/null +++ b/container/kyroy/kyroy.go @@ -0,0 +1,71 @@ +// Package kyroy is a wrapper around the @kyroy k-D tree implementation. This is +// used for performance testing. +package kyroy + +import ( + "github.com/downflux/go-geometry/nd/hyperrectangle" + "github.com/downflux/go-geometry/nd/vector" + "github.com/downflux/go-kd/filter" + "github.com/downflux/go-kd/point" + "github.com/kyroy/kdtree" + "github.com/kyroy/kdtree/points" +) + +type KD[T point.P] kdtree.KDTree + +func New[T point.P](data []T) *KD[T] { + var ps []kdtree.Point + for _, p := range data { + ps = append(ps, points.NewPoint([]float64(p.P()), p)) + } + return (*KD[T])(kdtree.New(ps)) +} + +func (t *KD[T]) KNN(p vector.V, k int, f filter.F[T]) []T { + var data []T + for _, p := range (*kdtree.KDTree)(t).KNN( + points.NewPoint([]float64(p), nil), + k, + ) { + if f(p.(*points.Point).Data.(T)) { + data = append(data, p.(*points.Point).Data.(T)) + } + } + return data +} + +func (t *KD[T]) Data() []T { + var data []T + for _, p := range (*kdtree.KDTree)(t).Points() { + data = append(data, p.(T)) + } + return data +} + +func (t *KD[T]) RangeSearch(q hyperrectangle.R, f filter.F[T]) []T { + var r [][2]float64 + for i := vector.D(0); i < q.Min().Dimension(); i++ { + r = append(r, [2]float64{q.Min().X(i), q.Max().X(i)}) + } + + var data []T + for _, p := range (*kdtree.KDTree)(t).RangeSearch(r) { + if f(p.(*points.Point).Data.(T)) { + data = append(data, p.(*points.Point).Data.(T)) + } + } + return data +} + +func (t *KD[T]) Balance() { (*kdtree.KDTree)(t).Balance() } +func (t *KD[T]) Insert(p T) { (*kdtree.KDTree)(t).Insert(points.NewPoint([]float64(p.P()), p)) } + +func (t *KD[T]) Remove(p vector.V, f filter.F[T]) (T, bool) { + v := (*kdtree.KDTree)(t).Remove(points.NewPoint([]float64(p), nil)).(*points.Point).Data.(T) + if !f(v) { + var blank T + (*kdtree.KDTree)(t).Insert(points.NewPoint([]float64(p), v)) + return blank, false + } + return v, true +} diff --git a/container/kyroy/kyroy_test.go b/container/kyroy/kyroy_test.go new file mode 100644 index 0000000..6a60147 --- /dev/null +++ b/container/kyroy/kyroy_test.go @@ -0,0 +1,8 @@ +package kyroy + +import ( + "github.com/downflux/go-kd/container" + "github.com/downflux/go-kd/point/mock" +) + +var _ container.C[mock.P] = &KD[mock.P]{} diff --git a/go.mod b/go.mod index bc48cc5..4840da8 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,7 @@ go 1.18 require ( github.com/downflux/go-geometry v0.10.2 github.com/google/go-cmp v0.5.8 + github.com/kyroy/kdtree v0.0.0-20200419114247-70830f883f1d ) + +require github.com/kyroy/priority-queue v0.0.0-20180327160706-6e21825e7e0c // indirect diff --git a/go.sum b/go.sum index 3cf89e7..912433d 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,15 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/downflux/go-geometry v0.10.2 h1:Z79Khzl6AKMSMLnM5xG75fEOL1fmIWlF14+8j+r01D0= github.com/downflux/go-geometry v0.10.2/go.mod h1:XWTzSaMiRMAxupAR+cXAsa1Q75TCSp1Shc/ydsJ0xVE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/jupp0r/go-priority-queue v0.0.0-20160601094913-ab1073853bde/go.mod h1:RDgD/dfPmIwFH0qdUOjw71HjtWg56CtyLIoHL+R1wJw= +github.com/kyroy/kdtree v0.0.0-20200419114247-70830f883f1d h1:1n5M/49q9H6QtNJiiVL/W5mqgT1UdlGQ7oLP+DkJ1vs= +github.com/kyroy/kdtree v0.0.0-20200419114247-70830f883f1d/go.mod h1:6oJGQK7VSg3RxSQ7QspgqpCmKjIbAslgT2wBXbFJUZw= +github.com/kyroy/priority-queue v0.0.0-20180327160706-6e21825e7e0c h1:1c7+XOOGQ19cXjZ1Ss/irljQxgPvb+8z+jNEprCXl20= +github.com/kyroy/priority-queue v0.0.0-20180327160706-6e21825e7e0c/go.mod h1:R477L6j2/dUcE0q0aftk0kR5Xt93W7g1066AodcJhEo= +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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/perf/perf_test.go b/internal/perf/perf_test.go index c19e12a..afe0f6a 100644 --- a/internal/perf/perf_test.go +++ b/internal/perf/perf_test.go @@ -9,6 +9,7 @@ import ( "github.com/downflux/go-geometry/nd/vector" "github.com/downflux/go-kd/container" "github.com/downflux/go-kd/container/bruteforce" + "github.com/downflux/go-kd/container/kyroy" "github.com/downflux/go-kd/internal/perf/util" "github.com/downflux/go-kd/kd" "github.com/downflux/go-kd/point/mock" @@ -22,15 +23,24 @@ func BenchmarkNew(b *testing.B) { k vector.D n int + // kyroy implementation does not take a leaf-size parameter. + kyroy bool + size int } var configs []config for _, k := range util.BenchmarkKRange { for _, n := range util.BenchmarkNRange { + configs = append(configs, config{ + name: fmt.Sprintf("kyroy/K=%v/N=%v", k, n), + k: k, + n: n, + kyroy: true, + }) for _, size := range util.BenchmarkSizeRange { configs = append(configs, config{ - name: fmt.Sprintf("K=%v/N=%v/LeafSize=%v", k, n, size), + name: fmt.Sprintf("Real/K=%v/N=%v/LeafSize=%v", k, n, size), k: k, n: n, size: size, @@ -41,15 +51,24 @@ func BenchmarkNew(b *testing.B) { for _, c := range configs { ps := util.Generate(c.n, c.k) - b.Run(c.name, func(b *testing.B) { - for i := 0; i < b.N; i++ { - kd.New[*mock.P](kd.O[*mock.P]{ - Data: ps, - K: c.k, - N: c.size, - }) - } - }) + + if c.kyroy { + b.Run(c.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + kyroy.New[*mock.P](ps) + } + }) + } else { + b.Run(c.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + kd.New[*mock.P](kd.O[*mock.P]{ + Data: ps, + K: c.k, + N: c.size, + }) + } + }) + } } } @@ -78,6 +97,15 @@ func BenchmarkKNN(b *testing.B) { for _, f := range util.BenchmarkFRange { knn := int(float64(n) * f) + // kyroy implementation does not take a + // leaf-size parameter. + configs = append(configs, config{ + name: fmt.Sprintf("kyroy/K=%v/N=%v/KNN=%v", k, n, f), + t: kyroy.New[*mock.P](ps), + p: vector.V(make([]float64, k)), + knn: knn, + }) + for _, size := range util.BenchmarkSizeRange { configs = append(configs, config{ name: fmt.Sprintf("Real/K=%v/N=%v/LeafSize=%v/KNN=%v", k, n, size, f), @@ -129,6 +157,14 @@ func BenchmarkRangeSearch(b *testing.B) { for _, f := range util.BenchmarkFRange { q := util.RH(k, f) + // kyroy implementation does not take a + // leaf-size parameter. + configs = append(configs, config{ + name: fmt.Sprintf("kyroy/K=%v/N=%v/Coverage=%v", k, n, f), + t: kyroy.New[*mock.P](ps), + q: q, + }) + for _, size := range util.BenchmarkSizeRange { configs = append(configs, config{ name: fmt.Sprintf("Real/K=%v/N=%v/LeafSize=%v/Coverage=%v", k, n, size, f),