From 0359ff0fff15ee4dc8f35c244b070fe1cf436a9b Mon Sep 17 00:00:00 2001 From: AlisaLC Date: Sun, 28 Jul 2024 18:22:03 +0330 Subject: [PATCH 1/3] fixed go package name and test errors --- README_GO.rst | 22 +-- src/annoygomodule.i | 2 +- test/annoy_test.go | 358 ++++++++++++++++++++++---------------------- 3 files changed, 191 insertions(+), 191 deletions(-) diff --git a/README_GO.rst b/README_GO.rst index 81cbdffe..f24565c3 100644 --- a/README_GO.rst +++ b/README_GO.rst @@ -1,16 +1,15 @@ Install ------- -To install, you'll need Swig (tested with Swig 3.0.6 on OS X), and then just:: +To install, you'll need Swig (tested with Swig 4.2.1 on Ubuntu 24.04), and then just:: swig -go -intgosize 64 -cgo -c++ src/annoygomodule.i - mkdir -p $GOPATH/src/annoyindex - cp src/annoygomodule_wrap.cxx src/annoyindex.go src/annoygomodule.h src/annoylib.h src/kissrandom.h test/annoy_test.go $GOPATH/src/annoyindex - cd $GOPATH/src/annoyindex - go mod init annoyindex - go get -t ... + mkdir -p $(go env GOPATH)/src/annoy + cp src/annoygomodule_wrap.cxx src/annoy.go src/annoygomodule.h src/annoylib.h src/kissrandom.h test/annoy_test.go $(go env GOPATH)/src/annoy + cd $(go env GOPATH)/src/annoy + go mod init github.com/spotify/annoy + go mod tidy go test - go build Background ---------- @@ -25,14 +24,15 @@ Go code example package main import ( - "annoyindex" "fmt" "math/rand" + + "github.com/spotify/annoy" ) func main() { f := 40 - t := annoyindex.NewAnnoyIndexAngular(f) + t := annoy.NewAnnoyIndexAngular(f) for i := 0; i < 1000; i++ { item := make([]float32, 0, f) for x:= 0; x < f; x++ { @@ -43,9 +43,9 @@ Go code example t.Build(10) t.Save("test.ann") - annoyindex.DeleteAnnoyIndexAngular(t) + annoy.DeleteAnnoyIndexAngular(t) - t = annoyindex.NewAnnoyIndexAngular(f) + t = annoy.NewAnnoyIndexAngular(f) t.Load("test.ann") var result []int diff --git a/src/annoygomodule.i b/src/annoygomodule.i index 1022021d..171b6c21 100644 --- a/src/annoygomodule.i +++ b/src/annoygomodule.i @@ -1,4 +1,4 @@ -%module annoyindex +%module annoy namespace Annoy {} diff --git a/test/annoy_test.go b/test/annoy_test.go index bd0e569d..8eeb3586 100644 --- a/test/annoy_test.go +++ b/test/annoy_test.go @@ -12,247 +12,247 @@ # the License. */ -package annoyindex_test +package annoy_test import ( - "annoyindex" - "os" - "testing" - "math" - "math/rand" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" + "math" + "math/rand" + "os" + "testing" + + "github.com/spotify/annoy" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) type AnnoyTestSuite struct { - suite.Suite + suite.Suite } func Round(f float64) float64 { - return math.Floor(f + 0.5) + return math.Floor(f + 0.5) } -func RoundPlus(f float64, places int) (float64) { - shift := math.Pow(10, float64(places)) - return Round(f * shift) / shift +func RoundPlus(f float64, places int) float64 { + shift := math.Pow(10, float64(places)) + return Round(f*shift) / shift } func (suite *AnnoyTestSuite) SetupTest() { } func (suite *AnnoyTestSuite) TestFileHandling() { - index := annoyindex.NewAnnoyIndexAngular(3) - index.AddItem(0, []float32{0, 0, 1}) - index.AddItem(1, []float32{0, 1, 0}) - index.AddItem(2, []float32{1, 0, 0}) - index.Build(10) - - index.Save("go_test.ann") - - info, err := os.Stat("go_test.ann") - if err != nil { - assert.Fail(suite.T(), "Failed to create file, file not found") - } - if info.Size() == 0 { - assert.Fail(suite.T(), "Failed to create file, file size zero") - } - - annoyindex.DeleteAnnoyIndexAngular(index) - - index = annoyindex.NewAnnoyIndexAngular(3) - if ret := index.Load("go_test.ann"); ret == false { - assert.Fail(suite.T(), "Failed to load file") - } - - os.Remove("go_test.ann") - index.Save("go_test2.ann", false) - - info, err = os.Stat("go_test2.ann") - if err != nil { - assert.Fail(suite.T(), "Failed to create file without prefault, file not found") - } - if info.Size() == 0 { - assert.Fail(suite.T(), "Failed to create file without prefault, file size zero") - } - - annoyindex.DeleteAnnoyIndexAngular(index) - - index = annoyindex.NewAnnoyIndexAngular(3) - if ret := index.Load("go_test2.ann", false); ret == false { - assert.Fail(suite.T(), "Failed to load file without prefault") - } - - os.Remove("go_test2.ann") - index.Save("go_test3.ann", true) - - info, err = os.Stat("go_test3.ann") - if err != nil { - assert.Fail(suite.T(), "Failed to create file allowing prefault, file not found") - } - if info.Size() == 0 { - assert.Fail(suite.T(), "Failed to create file allowing prefault, file size zero") - } - - annoyindex.DeleteAnnoyIndexAngular(index) - - index = annoyindex.NewAnnoyIndexAngular(3) - if ret := index.Load("go_test3.ann", true); ret == false { - assert.Fail(suite.T(), "Failed to load file allowing prefault") - } - annoyindex.DeleteAnnoyIndexAngular(index) - - os.Remove("go_test3.ann") + index := annoy.NewAnnoyIndexAngular(3) + index.AddItem(0, []float32{0, 0, 1}) + index.AddItem(1, []float32{0, 1, 0}) + index.AddItem(2, []float32{1, 0, 0}) + index.Build(10) + + index.Save("go_test.ann") + + info, err := os.Stat("go_test.ann") + if err != nil { + assert.Fail(suite.T(), "Failed to create file, file not found") + } + if info.Size() == 0 { + assert.Fail(suite.T(), "Failed to create file, file size zero") + } + + annoy.DeleteAnnoyIndexAngular(index) + + index = annoy.NewAnnoyIndexAngular(3) + if ret := index.Load("go_test.ann"); ret == false { + assert.Fail(suite.T(), "Failed to load file") + } + + os.Remove("go_test.ann") + index.Save("go_test2.ann", false) + + info, err = os.Stat("go_test2.ann") + if err != nil { + assert.Fail(suite.T(), "Failed to create file without prefault, file not found") + } + if info.Size() == 0 { + assert.Fail(suite.T(), "Failed to create file without prefault, file size zero") + } + + annoy.DeleteAnnoyIndexAngular(index) + + index = annoy.NewAnnoyIndexAngular(3) + if ret := index.Load("go_test2.ann", false); ret == false { + assert.Fail(suite.T(), "Failed to load file without prefault") + } + + os.Remove("go_test2.ann") + index.Save("go_test3.ann", true) + + info, err = os.Stat("go_test3.ann") + if err != nil { + assert.Fail(suite.T(), "Failed to create file allowing prefault, file not found") + } + if info.Size() == 0 { + assert.Fail(suite.T(), "Failed to create file allowing prefault, file size zero") + } + + annoy.DeleteAnnoyIndexAngular(index) + + index = annoy.NewAnnoyIndexAngular(3) + if ret := index.Load("go_test3.ann", true); ret == false { + assert.Fail(suite.T(), "Failed to load file allowing prefault") + } + annoy.DeleteAnnoyIndexAngular(index) + + os.Remove("go_test3.ann") } func (suite *AnnoyTestSuite) TestOnDiskBuild() { - index := annoyindex.NewAnnoyIndexAngular(3) - index.OnDiskBuild("go_test.ann"); + index := annoy.NewAnnoyIndexAngular(3) + index.OnDiskBuild("go_test.ann") - info, err := os.Stat("go_test.ann") - if err != nil { - assert.Fail(suite.T(), "Failed to create file, file not found") - } - if info.Size() == 0 { - assert.Fail(suite.T(), "Failed to create file, file size zero") - } + info, err := os.Stat("go_test.ann") + if err != nil { + assert.Fail(suite.T(), "Failed to create file, file not found") + } + if info.Size() == 0 { + assert.Fail(suite.T(), "Failed to create file, file size zero") + } - index.AddItem(0, []float32{0, 0, 1}) - index.AddItem(1, []float32{0, 1, 0}) - index.AddItem(2, []float32{1, 0, 0}) - index.Build(10) + index.AddItem(0, []float32{0, 0, 1}) + index.AddItem(1, []float32{0, 1, 0}) + index.AddItem(2, []float32{1, 0, 0}) + index.Build(10) - index.Unload(); - index.Load("go_test.ann"); + index.Unload() + index.Load("go_test.ann") - var result []int - index.GetNnsByVector([]float32{3, 2, 1}, 3, -1, &result) - assert.Equal(suite.T(), []int{2, 1, 0}, result) + var result []int + index.GetNnsByVector([]float32{3, 2, 1}, 3, -1, &result) + assert.Equal(suite.T(), []int{2, 1, 0}, result) - index.GetNnsByVector([]float32{1, 2, 3}, 3, -1, &result) - assert.Equal(suite.T(), []int{0, 1, 2}, result) + index.GetNnsByVector([]float32{1, 2, 3}, 3, -1, &result) + assert.Equal(suite.T(), []int{0, 1, 2}, result) - index.GetNnsByVector([]float32{2, 0, 1}, 3, -1, &result) - assert.Equal(suite.T(), []int{2, 0, 1}, result) + index.GetNnsByVector([]float32{2, 0, 1}, 3, -1, &result) + assert.Equal(suite.T(), []int{2, 0, 1}, result) - annoyindex.DeleteAnnoyIndexAngular(index) + annoy.DeleteAnnoyIndexAngular(index) - os.Remove("go_test.ann") + os.Remove("go_test.ann") } func (suite *AnnoyTestSuite) TestGetNnsByVector() { - index := annoyindex.NewAnnoyIndexAngular(3) - index.AddItem(0, []float32{0, 0, 1}) - index.AddItem(1, []float32{0, 1, 0}) - index.AddItem(2, []float32{1, 0, 0}) - index.Build(10) + index := annoy.NewAnnoyIndexAngular(3) + index.AddItem(0, []float32{0, 0, 1}) + index.AddItem(1, []float32{0, 1, 0}) + index.AddItem(2, []float32{1, 0, 0}) + index.Build(10) - var result []int - index.GetNnsByVector([]float32{3, 2, 1}, 3, -1, &result) - assert.Equal(suite.T(), []int{2, 1, 0}, result) + var result []int + index.GetNnsByVector([]float32{3, 2, 1}, 3, -1, &result) + assert.Equal(suite.T(), []int{2, 1, 0}, result) - index.GetNnsByVector([]float32{1, 2, 3}, 3, -1, &result) - assert.Equal(suite.T(), []int{0, 1, 2}, result) + index.GetNnsByVector([]float32{1, 2, 3}, 3, -1, &result) + assert.Equal(suite.T(), []int{0, 1, 2}, result) - index.GetNnsByVector([]float32{2, 0, 1}, 3, -1, &result) - assert.Equal(suite.T(), []int{2, 0, 1}, result) + index.GetNnsByVector([]float32{2, 0, 1}, 3, -1, &result) + assert.Equal(suite.T(), []int{2, 0, 1}, result) - annoyindex.DeleteAnnoyIndexAngular(index) + annoy.DeleteAnnoyIndexAngular(index) } func (suite *AnnoyTestSuite) TestGetNnsByItem() { - index := annoyindex.NewAnnoyIndexAngular(3) - index.AddItem(0, []float32{2, 1, 0}) - index.AddItem(1, []float32{1, 2, 0}) - index.AddItem(2, []float32{0, 0, 1}) - index.Build(10) + index := annoy.NewAnnoyIndexAngular(3) + index.AddItem(0, []float32{2, 1, 0}) + index.AddItem(1, []float32{1, 2, 0}) + index.AddItem(2, []float32{0, 0, 1}) + index.Build(10) - var result []int - index.GetNnsByItem(0, 3, -1, &result) - assert.Equal(suite.T(), []int{0, 1, 2}, result) + var result []int + index.GetNnsByItem(0, 3, -1, &result) + assert.Equal(suite.T(), []int{0, 1, 2}, result) - index.GetNnsByItem(1, 3, -1, &result) - assert.Equal(suite.T(), []int{1, 0, 2}, result) + index.GetNnsByItem(1, 3, -1, &result) + assert.Equal(suite.T(), []int{1, 0, 2}, result) - annoyindex.DeleteAnnoyIndexAngular(index) + annoy.DeleteAnnoyIndexAngular(index) } func (suite *AnnoyTestSuite) TestGetItem() { - index := annoyindex.NewAnnoyIndexAngular(3) - index.AddItem(0, []float32{2, 1, 0}) - index.AddItem(1, []float32{1, 2, 0}) - index.AddItem(2, []float32{0, 0, 1}) - index.Build(10) + index := annoy.NewAnnoyIndexAngular(3) + index.AddItem(0, []float32{2, 1, 0}) + index.AddItem(1, []float32{1, 2, 0}) + index.AddItem(2, []float32{0, 0, 1}) + index.Build(10) - var result []float32 + var result []float32 - index.GetItem(0, &result) - assert.Equal(suite.T(), []float32{2, 1, 0}, result) + index.GetItem(0, &result) + assert.Equal(suite.T(), []float32{2, 1, 0}, result) - index.GetItem(1, &result) - assert.Equal(suite.T(), []float32{1, 2, 0}, result) + index.GetItem(1, &result) + assert.Equal(suite.T(), []float32{1, 2, 0}, result) - index.GetItem(2, &result) - assert.Equal(suite.T(), []float32{0, 0, 1}, result) + index.GetItem(2, &result) + assert.Equal(suite.T(), []float32{0, 0, 1}, result) - annoyindex.DeleteAnnoyIndexAngular(index) + annoy.DeleteAnnoyIndexAngular(index) } - func (suite *AnnoyTestSuite) TestGetDistance() { - index := annoyindex.NewAnnoyIndexAngular(2) - index.AddItem(0, []float32{0, 1}) - index.AddItem(1, []float32{1, 1}) - index.Build(10) + index := annoy.NewAnnoyIndexAngular(2) + index.AddItem(0, []float32{0, 1}) + index.AddItem(1, []float32{1, 1}) + index.Build(10) - assert.Equal(suite.T(), RoundPlus(math.Pow(2 * (1.0 - math.Pow(2, -0.5)), 0.5), 3), RoundPlus(float64(index.GetDistance(0, 1)), 3)) + assert.Equal(suite.T(), RoundPlus(math.Pow(2*(1.0-math.Pow(2, -0.5)), 0.5), 3), RoundPlus(float64(index.GetDistance(0, 1)), 3)) - annoyindex.DeleteAnnoyIndexAngular(index) + annoy.DeleteAnnoyIndexAngular(index) } func (suite *AnnoyTestSuite) TestGetDotProductDistance() { - index := annoyindex.NewAnnoyIndexDotProduct(2) - index.AddItem(0, []float32{0, 1}) - index.AddItem(1, []float32{1, 1}) - index.Build(10) + index := annoy.NewAnnoyIndexDotProduct(2) + index.AddItem(0, []float32{0, 1}) + index.AddItem(1, []float32{1, 1}) + index.Build(10) - assert.True(suite.T(), - math.Abs(1.0-float64(index.GetDistance(0, 1))) < 0.00001) + assert.True(suite.T(), + math.Abs(1.0-float64(index.GetDistance(0, 1))) < 0.00001) - annoyindex.DeleteAnnoyIndexDotProduct(index) + annoy.DeleteAnnoyIndexDotProduct(index) } func (suite *AnnoyTestSuite) TestLargeEuclideanIndex() { - index := annoyindex.NewAnnoyIndexEuclidean(10) - - for j := 0; j < 10000; j += 2 { - p := make([]float32, 0, 10) - for i := 0; i < 10; i++ { - p = append(p, rand.Float32()) - } - x := make([]float32, 0, 10) - for i := 0; i < 10; i++ { - x = append(x, 1 + p[i] + rand.Float32() * 1e-2) - } - y := make([]float32, 0, 10) - for i := 0; i < 10; i++ { - y = append(y, 1 + p[i] + rand.Float32() * 1e-2) - } - index.AddItem(j, x) - index.AddItem(j + 1, y) - } - index.Build(10) - for j := 0; j < 10000; j += 2 { - var result []int - index.GetNnsByItem(j, 2, -1, &result) - - assert.Equal(suite.T(), result, []int{j, j + 1}) - - index.GetNnsByItem(j + 1, 2, -1, &result) - assert.Equal(suite.T(), result, []int{j + 1, j}) - } - annoyindex.DeleteAnnoyIndexEuclidean(index) + index := annoy.NewAnnoyIndexEuclidean(10) + + for j := 0; j < 10000; j += 2 { + p := make([]float32, 0, 10) + for i := 0; i < 10; i++ { + p = append(p, rand.Float32()) + } + x := make([]float32, 0, 10) + for i := 0; i < 10; i++ { + x = append(x, 1+p[i]+rand.Float32()*1e-2) + } + y := make([]float32, 0, 10) + for i := 0; i < 10; i++ { + y = append(y, 1+p[i]+rand.Float32()*1e-2) + } + index.AddItem(j, x) + index.AddItem(j+1, y) + } + index.Build(10) + for j := 0; j < 10000; j += 2 { + var result []int + index.GetNnsByItem(j, 2, -1, &result) + + assert.Equal(suite.T(), result, []int{j, j + 1}) + + index.GetNnsByItem(j+1, 2, -1, &result) + assert.Equal(suite.T(), result, []int{j + 1, j}) + } + annoy.DeleteAnnoyIndexEuclidean(index) } func TestAnnoyTestSuite(t *testing.T) { - suite.Run(t, new(AnnoyTestSuite)) + suite.Run(t, new(AnnoyTestSuite)) } From 255739207fdbe743b8683a618f984ec0423222e9 Mon Sep 17 00:00:00 2001 From: AlisaLC Date: Sun, 28 Jul 2024 19:24:58 +0330 Subject: [PATCH 2/3] fixed memory leak --- README_GO.rst | 7 ++- src/annoygomodule.h | 111 +++++++++++++++++++++++++++++++--- src/annoygomodule.i | 142 ++++++++++++++++++++++++++++++-------------- test/annoy_test.go | 106 +++++++++++++++++++++++---------- 4 files changed, 278 insertions(+), 88 deletions(-) diff --git a/README_GO.rst b/README_GO.rst index f24565c3..f64ab83d 100644 --- a/README_GO.rst +++ b/README_GO.rst @@ -48,9 +48,10 @@ Go code example t = annoy.NewAnnoyIndexAngular(f) t.Load("test.ann") - var result []int - t.GetNnsByItem(0, 1000, -1, &result) - fmt.Printf("%v\n", result) + result := annoyindex.NewAnnoyVectorInt() + defer result.Free() + t.GetNnsByItem(0, 1000, -1, result) + fmt.Printf("%v\n", result.ToSlice()) } diff --git a/src/annoygomodule.h b/src/annoygomodule.h index 074cc635..cc005ee5 100644 --- a/src/annoygomodule.h +++ b/src/annoygomodule.h @@ -5,6 +5,73 @@ using namespace Annoy; namespace GoAnnoy { + +class AnnoyVectorFloat { + protected: + float *ptr; + int len; + + public: + ~AnnoyVectorFloat() { + free(ptr); + }; + float* ArrayPtr() { + return ptr; + }; + int Len() { + return len; + }; + float Get(int i) { + if (i >= len) { + return 0.0; + } + return ptr[i]; + }; + void fill_from_vector(vector* v) { + if (ptr != NULL) { + free(ptr); + } + ptr = (float*) malloc(v->size() * sizeof(float)); + for (int i = 0; i < v->size(); i++) { + ptr[i] = (float)(*v)[i]; + } + len = v->size(); + }; +}; + +class AnnoyVectorInt { + protected: + int32_t *ptr; + int len; + + public: + ~AnnoyVectorInt() { + free(ptr); + }; + int32_t* ArrayPtr() { + return ptr; + }; + int Len() { + return len; + }; + int32_t Get(int i) { + if (i >= len) { + return 0.0; + } + return ptr[i]; + }; + void fill_from_vector(vector* v) { + if (ptr != NULL) { + free(ptr); + } + ptr = (int32_t*) malloc(v->size() * sizeof(int32_t)); + for (int i = 0; i < v->size(); i++) { + ptr[i] = (int32_t)(*v)[i]; + } + len = v->size(); + }; +}; + class AnnoyIndex { protected: ::AnnoyIndexInterface *ptr; @@ -39,17 +106,43 @@ class AnnoyIndex { float getDistance(int i, int j) { return ptr->get_distance(i, j); }; - void getNnsByItem(int item, int n, int search_k, vector* result, vector* distances) { + void getNnsByItem(int item, int n, int search_k, AnnoyVectorInt* out_result, AnnoyVectorFloat* out_distances) { + vector* result = new vector(); + vector* distances = new vector(); + ptr->get_nns_by_item(item, n, search_k, result, distances); - }; - void getNnsByVector(const float* w, int n, int search_k, vector* result, vector* distances) { + + out_result->fill_from_vector(result); + out_distances->fill_from_vector(distances); + delete result; + delete distances; + }; + void getNnsByVector(const float* w, int n, int search_k, AnnoyVectorInt* out_result, AnnoyVectorFloat* out_distances) { + vector* result = new vector(); + vector* distances = new vector(); + ptr->get_nns_by_vector(w, n, search_k, result, distances); + + out_result->fill_from_vector(result); + out_distances->fill_from_vector(distances); + delete result; + delete distances; }; - void getNnsByItem(int item, int n, int search_k, vector* result) { + void getNnsByItem(int item, int n, int search_k, AnnoyVectorInt* out_result) { + vector* result = new vector(); + ptr->get_nns_by_item(item, n, search_k, result, NULL); + + out_result->fill_from_vector(result); + delete result; }; - void getNnsByVector(const float* w, int n, int search_k, vector* result) { + void getNnsByVector(const float* w, int n, int search_k, AnnoyVectorInt* out_result) { + vector* result = new vector(); + ptr->get_nns_by_vector(w, n, search_k, result, NULL); + + out_result->fill_from_vector(result); + delete result; }; int getNItems() { @@ -58,9 +151,11 @@ class AnnoyIndex { void verbose(bool v) { ptr->verbose(v); }; - void getItem(int item, vector *v) { - v->resize(this->f); - ptr->get_item(item, &v->front()); + void getItem(int item, AnnoyVectorFloat *v) { + vector* r = new vector(); + r->resize(this->f); + ptr->get_item(item, &r->front()); + v->fill_from_vector(r); }; bool onDiskBuild(const char* filename) { return ptr->on_disk_build(filename); diff --git a/src/annoygomodule.i b/src/annoygomodule.i index 171b6c21..62c507c7 100644 --- a/src/annoygomodule.i +++ b/src/annoygomodule.i @@ -9,6 +9,7 @@ namespace Annoy {} // const float * %typemap(gotype) (const float *) "[]float32" +%typemap(gotype) (int32_t) "int32" %typemap(in) (const float *) %{ @@ -21,76 +22,125 @@ namespace Annoy {} $1 = &w[0]; %} -// vector * -%typemap(gotype) (vector *) "*[]int" -%typemap(in) (vector *) +%typemap(gotype) (const char *) "string" + +%typemap(in) (const char *) %{ - $1 = new vector(); + $1 = (char *)calloc((((_gostring_)$input).n + 1), sizeof(char)); + strncpy($1, (((_gostring_)$input).p), ((_gostring_)$input).n); %} -%typemap(freearg) (vector *) +%typemap(freearg) (const char *) %{ - delete $1; + free($1); %} -%typemap(argout) (vector *) -%{ - { - $input->len = $1->size(); - $input->cap = $1->size(); - $input->array = malloc($input->len * sizeof(intgo)); - for (int i = 0; i < $1->size(); i++) { - ((intgo *)$input->array)[i] = (intgo)(*$1)[i]; + +%ignore fill_from_vector; +%rename(X_RawAnnoyVectorInt) AnnoyVectorInt; +%rename(X_RawAnnoyVectorFloat) AnnoyVectorFloat; + +%insert(go_wrapper) %{ + +type AnnoyVectorInt interface { + X_RawAnnoyVectorInt + ToSlice() []int32 + Copy(in *[]int32) + InnerArray() []int32 + Free() +} + +func NewAnnoyVectorInt() AnnoyVectorInt { + vec := NewX_RawAnnoyVectorInt() + return vec.(SwigcptrX_RawAnnoyVectorInt) +} + +func (p SwigcptrX_RawAnnoyVectorInt) ToSlice() []int32 { + var out []int32 + p.Copy(&out) + return out +} + +func (p SwigcptrX_RawAnnoyVectorInt) Copy(in *[]int32) { + out := *in + inner := p.InnerArray() + if cap(out) >= len(inner) { + if len(out) != len(inner) { + out = out[:len(inner)] + } + } else { + out = make([]int32, len(inner)) } - } -%} + copy(out, inner) + *in = out +} -// vector * -%typemap(gotype) (vector *) "*[]float32" +func (p SwigcptrX_RawAnnoyVectorInt) Free() { + DeleteX_RawAnnoyVectorInt(p) +} -%typemap(in) (vector *) -%{ - $1 = new vector(); -%} +func (p SwigcptrX_RawAnnoyVectorInt) InnerArray() []int32 { + length := p.Len() + ptr := unsafe.Pointer(p.ArrayPtr()) + return ((*[1 << 30]int32)(ptr))[:length:length] +} -%typemap(freearg) (vector *) -%{ - delete $1; %} -%typemap(argout) (vector *) -%{ - { - $input->len = $1->size(); - $input->cap = $1->size(); - $input->array = malloc($input->len * sizeof(float)); - for (int i = 0; i < $1->size(); i++) { - ((float *)$input->array)[i] = (float)(*$1)[i]; +%insert(go_wrapper) %{ + +type AnnoyVectorFloat interface { + X_RawAnnoyVectorFloat + ToSlice() []float32 + Copy(in *[]float32) + InnerArray() []float32 + Free() +} + +func NewAnnoyVectorFloat() AnnoyVectorFloat { + vec := NewX_RawAnnoyVectorFloat() + return vec.(SwigcptrX_RawAnnoyVectorFloat) +} + +func (p SwigcptrX_RawAnnoyVectorFloat) ToSlice() []float32 { + var out []float32 + p.Copy(&out) + return out +} + +func (p SwigcptrX_RawAnnoyVectorFloat) Copy(in *[]float32) { + out := *in + inner := p.InnerArray() + if cap(out) >= len(inner) { + if len(out) != len(inner) { + out = out[:len(inner)] + } + } else { + out = make([]float32, len(inner)) } - } -%} + copy(out, inner) + *in = out +} -%typemap(gotype) (const char *) "string" +func (p SwigcptrX_RawAnnoyVectorFloat) Free() { + DeleteX_RawAnnoyVectorFloat(p) +} -%typemap(in) (const char *) -%{ - $1 = (char *)calloc((((_gostring_)$input).n + 1), sizeof(char)); - strncpy($1, (((_gostring_)$input).p), ((_gostring_)$input).n); -%} +func (p SwigcptrX_RawAnnoyVectorFloat) InnerArray() []float32 { + length := p.Len() + ptr := unsafe.Pointer(p.ArrayPtr()) + return ((*[1 << 30]float32)(ptr))[:length:length] +} -%typemap(freearg) (const char *) -%{ - free($1); %} - /* Let's just grab the original header file here */ %include "annoygomodule.h" %feature("notabstract") GoAnnoyIndexAngular; %feature("notabstract") GoAnnoyIndexEuclidean; %feature("notabstract") GoAnnoyIndexManhattan; -%feature("notabstract") GoAnnoyIndexDotProduct; +%feature("notabstract") GoAnnoyIndexDotProduct; \ No newline at end of file diff --git a/test/annoy_test.go b/test/annoy_test.go index 8eeb3586..607f6025 100644 --- a/test/annoy_test.go +++ b/test/annoy_test.go @@ -22,6 +22,7 @@ import ( "github.com/spotify/annoy" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -125,15 +126,17 @@ func (suite *AnnoyTestSuite) TestOnDiskBuild() { index.Unload() index.Load("go_test.ann") - var result []int - index.GetNnsByVector([]float32{3, 2, 1}, 3, -1, &result) - assert.Equal(suite.T(), []int{2, 1, 0}, result) + result := annoy.NewAnnoyVectorInt() + defer result.Free() - index.GetNnsByVector([]float32{1, 2, 3}, 3, -1, &result) - assert.Equal(suite.T(), []int{0, 1, 2}, result) + index.GetNnsByVector([]float32{3, 2, 1}, 3, -1, result) + assert.Equal(suite.T(), []int32{2, 1, 0}, result.ToSlice()) - index.GetNnsByVector([]float32{2, 0, 1}, 3, -1, &result) - assert.Equal(suite.T(), []int{2, 0, 1}, result) + index.GetNnsByVector([]float32{1, 2, 3}, 3, -1, result) + assert.Equal(suite.T(), []int32{0, 1, 2}, result.ToSlice()) + + index.GetNnsByVector([]float32{2, 0, 1}, 3, -1, result) + assert.Equal(suite.T(), []int32{2, 0, 1}, result.ToSlice()) annoy.DeleteAnnoyIndexAngular(index) @@ -141,21 +144,58 @@ func (suite *AnnoyTestSuite) TestOnDiskBuild() { } func (suite *AnnoyTestSuite) TestGetNnsByVector() { + t := suite.T() index := annoy.NewAnnoyIndexAngular(3) index.AddItem(0, []float32{0, 0, 1}) index.AddItem(1, []float32{0, 1, 0}) index.AddItem(2, []float32{1, 0, 0}) index.Build(10) - var result []int - index.GetNnsByVector([]float32{3, 2, 1}, 3, -1, &result) - assert.Equal(suite.T(), []int{2, 1, 0}, result) + t.Run("regular", func(t *testing.T) { + result := annoy.NewAnnoyVectorInt() + defer result.Free() + + index.GetNnsByVector([]float32{3, 2, 1}, 3, -1, result) + assert.Equal(t, []int32{2, 1, 0}, result.ToSlice()) + + index.GetNnsByVector([]float32{1, 2, 3}, 3, -1, result) + assert.Equal(t, []int32{0, 1, 2}, result.ToSlice()) + + index.GetNnsByVector([]float32{2, 0, 1}, 3, -1, result) + assert.Equal(t, []int32{2, 0, 1}, result.ToSlice()) + }) + + t.Run("with copying", func(t *testing.T) { + result := annoy.NewAnnoyVectorInt() + defer result.Free() - index.GetNnsByVector([]float32{1, 2, 3}, 3, -1, &result) - assert.Equal(suite.T(), []int{0, 1, 2}, result) + var notAllocated []int32 + index.GetNnsByVector([]float32{3, 2, 1}, 3, -1, result) + result.Copy(¬Allocated) + assert.Equal(t, []int32{2, 1, 0}, notAllocated) - index.GetNnsByVector([]float32{2, 0, 1}, 3, -1, &result) - assert.Equal(suite.T(), []int{2, 0, 1}, result) + // to make sure it will be overwritten + var alreadyAllocated = make([]int32, 10) + for i := 0; i < len(alreadyAllocated); i++ { + alreadyAllocated[i] = -1 + } + index.GetNnsByVector([]float32{3, 2, 1}, 3, -1, result) + result.Copy(&alreadyAllocated) + assert.Equal(t, []int32{2, 1, 0}, alreadyAllocated) + + var alreadyAllocatedCap = make([]int32, 0, 00) + index.GetNnsByVector([]float32{3, 2, 1}, 3, -1, result) + result.Copy(&alreadyAllocatedCap) + assert.Equal(t, []int32{2, 1, 0}, alreadyAllocatedCap) + }) + + t.Run("with inner array", func(t *testing.T) { + result := annoy.NewAnnoyVectorInt() + defer result.Free() + + index.GetNnsByVector([]float32{3, 2, 1}, 3, -1, result) + assert.Equal(t, []int32{2, 1, 0}, result.InnerArray()) + }) annoy.DeleteAnnoyIndexAngular(index) } @@ -167,12 +207,14 @@ func (suite *AnnoyTestSuite) TestGetNnsByItem() { index.AddItem(2, []float32{0, 0, 1}) index.Build(10) - var result []int - index.GetNnsByItem(0, 3, -1, &result) - assert.Equal(suite.T(), []int{0, 1, 2}, result) + var result = annoy.NewAnnoyVectorInt() + defer result.Free() + + index.GetNnsByItem(0, 3, -1, result) + assert.Equal(suite.T(), []int32{0, 1, 2}, result.ToSlice()) - index.GetNnsByItem(1, 3, -1, &result) - assert.Equal(suite.T(), []int{1, 0, 2}, result) + index.GetNnsByItem(1, 3, -1, result) + assert.Equal(suite.T(), []int32{1, 0, 2}, result.ToSlice()) annoy.DeleteAnnoyIndexAngular(index) } @@ -184,16 +226,17 @@ func (suite *AnnoyTestSuite) TestGetItem() { index.AddItem(2, []float32{0, 0, 1}) index.Build(10) - var result []float32 + var result = annoy.NewAnnoyVectorFloat() + defer result.Free() - index.GetItem(0, &result) - assert.Equal(suite.T(), []float32{2, 1, 0}, result) + index.GetItem(0, result) + assert.Equal(suite.T(), []float32{2, 1, 0}, result.ToSlice()) - index.GetItem(1, &result) - assert.Equal(suite.T(), []float32{1, 2, 0}, result) + index.GetItem(1, result) + assert.Equal(suite.T(), []float32{1, 2, 0}, result.ToSlice()) - index.GetItem(2, &result) - assert.Equal(suite.T(), []float32{0, 0, 1}, result) + index.GetItem(2, result) + assert.Equal(suite.T(), []float32{0, 0, 1}, result.ToSlice()) annoy.DeleteAnnoyIndexAngular(index) } @@ -241,14 +284,15 @@ func (suite *AnnoyTestSuite) TestLargeEuclideanIndex() { index.AddItem(j+1, y) } index.Build(10) + result := annoy.NewAnnoyVectorInt() + defer result.Free() for j := 0; j < 10000; j += 2 { - var result []int - index.GetNnsByItem(j, 2, -1, &result) + index.GetNnsByItem(j, 2, -1, result) - assert.Equal(suite.T(), result, []int{j, j + 1}) + require.Equal(suite.T(), result.ToSlice(), []int32{int32(j), int32(j + 1)}) - index.GetNnsByItem(j+1, 2, -1, &result) - assert.Equal(suite.T(), result, []int{j + 1, j}) + index.GetNnsByItem(j+1, 2, -1, result) + require.Equal(suite.T(), result.ToSlice(), []int32{int32(j) + 1, int32(j)}) } annoy.DeleteAnnoyIndexEuclidean(index) } From 5455ad5577838981651f11ece72d139ae3422428 Mon Sep 17 00:00:00 2001 From: AlisaLC Date: Sun, 28 Jul 2024 19:29:48 +0330 Subject: [PATCH 3/3] update go readme --- README_GO.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_GO.rst b/README_GO.rst index f64ab83d..27af1703 100644 --- a/README_GO.rst +++ b/README_GO.rst @@ -69,6 +69,6 @@ A simple test is supplied in test/annoy_test.go. Discuss ------- -There might be some memory leaks. See [this issue](https://github.com/swig/swig/issues/2292). +Memroy leak in the previous versions has been fixed thanks to https://github.com/swig/swig/issues/2292. (memory leak fix is implemented in https://github.com/Rikanishu/annoy-go) Go glue written by Taneli Leppä (@rosmo). You can contact me via email (see https://github.com/rosmo).