annoy-rb 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06af1da176d1d25ba1d0058a34d21483413deff690004d9e3dd5471c9d9f4294
4
- data.tar.gz: 98eab997b5d6b2439c7a2b3ab26a25818428bb6fcc0811447a903c3a83decfab
3
+ metadata.gz: a8884d4c472560181032959b32eb3396b698dedc7dac7211af67bdb03aaa90f6
4
+ data.tar.gz: 1eb4acf66b0685e06aed6455c4efd0ffcb17f8805e9b619f003fb62046db41db
5
5
  SHA512:
6
- metadata.gz: 054df371bf5596ebe6a59d003c5c483d1b4ba885ad55fcd1e0232d6a3d52ad0650e4d6f24805737123ca68350789b7cded0a9e58aafabb94cfa31ed5f813619b
7
- data.tar.gz: 9b22bf40a94e081a39249f1068d32cfa0b4112f951f0172a9004a887ab2b7ebe460b6baa4bbd904906928c1571257cb0e277e59adf6d21e5df61c842759d46df
6
+ metadata.gz: 0103da7a9f443c4fed168ac61004aa135bbd534edaff6f20d641ef818348288a851370693d75657794c8ebfe9dd45dd54177315c7ac68a3400e43c87dfb1c4bb
7
+ data.tar.gz: 8e7314fbee4ed318c47848941aba40f6363f350181c616045354af696dc99cbd678c7b999b5bfa35339ad82e6cb3f6782510354d44db5d4d1fb81591128f4822
@@ -0,0 +1,22 @@
1
+ name: build
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [ '2.6', '2.7', '3.0' ]
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - name: Set upt Ruby ${{ matrix.ruby }}
15
+ uses: actions/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby }}
18
+ - name: Build and test with Rake
19
+ run: |
20
+ gem install --no-document bundler
21
+ bundle install --jobs 4 --retry 3
22
+ bundle exec rake
data/CHANGELOG.md CHANGED
@@ -1,2 +1,21 @@
1
+ ## 0.3.0
2
+ - Add type declaration file: sig/annoy.rbs
3
+ - Fix get_distance method to return integer typed value on hamming metric index.
4
+ - Rename native extension files.
5
+
6
+ ## 0.2.3
7
+ - Add GC guard to index saving and loading methods.
8
+
9
+ ## 0.2.2
10
+ - Replace Data_ functions to TypedData_ functions.
11
+
12
+ ## 0.2.1
13
+ - Fix to free char array of error message before calling rb_raise.
14
+ - Fix to use array allocated with ruby_xmalloc instead of vector class in C++.
15
+
16
+ ## 0.2.0
17
+ - Update bundled Annoy version to 1.17.0.
18
+ - Support multithreaded index building.
19
+
1
20
  ## 0.1.0
2
21
  - First release.
data/Gemfile CHANGED
@@ -1,8 +1,10 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in annoy.gemspec
4
4
  gemspec
5
5
 
6
- gem "rake", "~> 12.0"
7
- gem "rake-compiler"
8
- gem "rspec", "~> 3.0"
6
+ gem 'rake', '~> 13.0'
7
+ gem 'rake-compiler', '~> 1.1'
8
+ gem 'rspec', '~> 3.0'
9
+ gem 'rbs', '~> 1.2'
10
+ gem 'steep', '~> 0.44'
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # Annoy.rb
2
2
 
3
- [![Build Status](https://travis-ci.org/yoshoku/annoy.rb.svg?branch=master)](https://travis-ci.org/yoshoku/annoy.rb)
3
+ [![Build Status](https://github.com/yoshoku/annoy.rb/workflows/build/badge.svg)](https://github.com/yoshoku/annoy.rb/actions?query=workflow%3Abuild)
4
4
  [![Gem Version](https://badge.fury.io/rb/annoy-rb.svg)](https://badge.fury.io/rb/annoy-rb)
5
- [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/yoshoku/annoy.rb/blob/master/LICENSE.txt)
5
+ [![License](https://img.shields.io/badge/License-Apache%202.0-yellowgreen.svg)](https://github.com/yoshoku/annoy.rb/blob/main/LICENSE.txt)
6
+ [![Documentation](http://img.shields.io/badge/api-reference-blue.svg)](https://yoshoku.github.io/annoy.rb/doc/)
6
7
 
7
8
  Annoy.rb is a Ruby binding for the [Annoy (Approximate Nearest Neighbors Oh Yeah)](https://github.com/spotify/annoy).
8
9
 
@@ -24,6 +25,10 @@ Or install it yourself as:
24
25
 
25
26
  Note: Annoy.rb does not require the installation of another external library.
26
27
 
28
+ ## Documentation
29
+
30
+ * [Annoy.rb API Documentation](https://yoshoku.github.io/annoy.rb/doc/)
31
+
27
32
  ## Usage
28
33
 
29
34
  ```ruby
@@ -51,8 +56,8 @@ The gem is available as open source under the terms of the [Apache-2.0 License](
51
56
 
52
57
  ## Contributing
53
58
 
54
- Bug reports and pull requests are welcome on GitHub at https://github.com/yoshoku/annoy.rb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/yoshoku/annoy.rb/blob/master/CODE_OF_CONDUCT.md).
59
+ Bug reports and pull requests are welcome on GitHub at https://github.com/yoshoku/annoy.rb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/yoshoku/annoy.rb/blob/main/CODE_OF_CONDUCT.md).
55
60
 
56
61
  ## Code of Conduct
57
62
 
58
- Everyone interacting in the Annoy.rb project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/yoshoku/annoy.rb/blob/master/CODE_OF_CONDUCT.md).
63
+ Everyone interacting in the Annoy.rb project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/yoshoku/annoy.rb/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -7,7 +7,8 @@ require 'rake/extensiontask'
7
7
 
8
8
  task :build => :compile
9
9
 
10
- Rake::ExtensionTask.new('annoy') do |ext|
10
+ Rake::ExtensionTask.new('annoyext') do |ext|
11
+ ext.ext_dir = 'ext/annoy'
11
12
  ext.lib_dir = 'lib/annoy'
12
13
  end
13
14
 
data/Steepfile ADDED
@@ -0,0 +1,20 @@
1
+ target :lib do
2
+ signature "sig"
3
+
4
+ check "lib" # Directory name
5
+ # check "Gemfile" # File name
6
+ # check "app/models/**/*.rb" # Glob
7
+ # # ignore "lib/templates/*.rb"
8
+ #
9
+ # # library "pathname", "set" # Standard libraries
10
+ # # library "strong_json" # Gems
11
+ end
12
+
13
+ # target :spec do
14
+ # signature "sig", "sig-private"
15
+ #
16
+ # check "spec"
17
+ #
18
+ # # library "pathname", "set" # Standard libraries
19
+ # # library "rspec"
20
+ # end
data/annoy-rb.gemspec CHANGED
@@ -13,7 +13,8 @@ Gem::Specification.new do |spec|
13
13
 
14
14
  spec.metadata['homepage_uri'] = spec.homepage
15
15
  spec.metadata['source_code_uri'] = spec.homepage
16
- spec.metadata['changelog_uri'] = 'https://github.com/yoshoku/annoy.rb/blob/master/CHANGELOG.md'
16
+ spec.metadata['changelog_uri'] = 'https://github.com/yoshoku/annoy.rb/blob/main/CHANGELOG.md'
17
+ spec.metadata['documentation_uri'] = 'https://yoshoku.github.io/annoy.rb/doc/'
17
18
 
18
19
  # Specify which files should be added to the gem when it is released.
19
20
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -16,15 +16,15 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
- #include "annoy.hpp"
19
+ #include "annoyext.hpp"
20
20
 
21
21
  extern "C"
22
- void Init_annoy(void)
22
+ void Init_annoyext(void)
23
23
  {
24
24
  VALUE rb_mAnnoy = rb_define_module("Annoy");
25
- VALUE rb_cAnnoyIndexAngular = RbAnnoyIndex<AnnoyIndexAngular, double>::define_class(rb_mAnnoy, "AnnoyIndexAngular");
26
- VALUE rb_cAnnoyIndexDotProduct = RbAnnoyIndex<AnnoyIndexDotProduct, double>::define_class(rb_mAnnoy, "AnnoyIndexDotProduct");
27
- VALUE rb_cAnnoyIndexHamming = RbAnnoyIndex<AnnoyIndexHamming, uint64_t>::define_class(rb_mAnnoy, "AnnoyIndexHamming");
28
- VALUE rb_cAnnoyIndexEuclidean = RbAnnoyIndex<AnnoyIndexEuclidean, double>::define_class(rb_mAnnoy, "AnnoyIndexEuclidean");
29
- VALUE rb_cAnnoyIndexManhattan = RbAnnoyIndex<AnnoyIndexManhattan, double>::define_class(rb_mAnnoy, "AnnoyIndexManhattan");
25
+ RbAnnoyIndex<AnnoyIndexAngular, double>::define_class(rb_mAnnoy, "AnnoyIndexAngular");
26
+ RbAnnoyIndex<AnnoyIndexDotProduct, double>::define_class(rb_mAnnoy, "AnnoyIndexDotProduct");
27
+ RbAnnoyIndex<AnnoyIndexHamming, uint64_t>::define_class(rb_mAnnoy, "AnnoyIndexHamming");
28
+ RbAnnoyIndex<AnnoyIndexEuclidean, double>::define_class(rb_mAnnoy, "AnnoyIndexEuclidean");
29
+ RbAnnoyIndex<AnnoyIndexManhattan, double>::define_class(rb_mAnnoy, "AnnoyIndexManhattan");
30
30
  }
@@ -16,8 +16,8 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
- #ifndef ANNOY_HPP
20
- #define ANNOY_HPP 1
19
+ #ifndef ANNOYEXT_HPP
20
+ #define ANNOYEXT_HPP 1
21
21
 
22
22
  #include <typeinfo>
23
23
 
@@ -25,28 +25,38 @@
25
25
  #include <annoylib.h>
26
26
  #include <kissrandom.h>
27
27
 
28
- typedef AnnoyIndex<int, double, Angular, Kiss64Random> AnnoyIndexAngular;
29
- typedef AnnoyIndex<int, double, DotProduct, Kiss64Random> AnnoyIndexDotProduct;
30
- typedef AnnoyIndex<int, uint64_t, Hamming, Kiss64Random> AnnoyIndexHamming;
31
- typedef AnnoyIndex<int, double, Euclidean, Kiss64Random> AnnoyIndexEuclidean;
32
- typedef AnnoyIndex<int, double, Manhattan, Kiss64Random> AnnoyIndexManhattan;
28
+ #ifdef ANNOYLIB_MULTITHREADED_BUILD
29
+ typedef AnnoyIndexMultiThreadedBuildPolicy AnnoyIndexThreadedBuildPolicy;
30
+ #else
31
+ typedef AnnoyIndexSingleThreadedBuildPolicy AnnoyIndexThreadedBuildPolicy;
32
+ #endif
33
+
34
+ typedef AnnoyIndex<int, double, Angular, Kiss64Random, AnnoyIndexThreadedBuildPolicy> AnnoyIndexAngular;
35
+ typedef AnnoyIndex<int, double, DotProduct, Kiss64Random, AnnoyIndexThreadedBuildPolicy> AnnoyIndexDotProduct;
36
+ typedef AnnoyIndex<int, uint64_t, Hamming, Kiss64Random, AnnoyIndexThreadedBuildPolicy> AnnoyIndexHamming;
37
+ typedef AnnoyIndex<int, double, Euclidean, Kiss64Random, AnnoyIndexThreadedBuildPolicy> AnnoyIndexEuclidean;
38
+ typedef AnnoyIndex<int, double, Manhattan, Kiss64Random, AnnoyIndexThreadedBuildPolicy> AnnoyIndexManhattan;
33
39
 
34
40
  template<class T, typename F> class RbAnnoyIndex
35
41
  {
36
42
  public:
37
43
  static VALUE annoy_index_alloc(VALUE self) {
38
44
  T* ptr = (T*)ruby_xmalloc(sizeof(T));
39
- return Data_Wrap_Struct(self, NULL, annoy_index_free, ptr);
45
+ return TypedData_Wrap_Struct(self, &annoy_index_type, ptr);
40
46
  };
41
47
 
42
- static void annoy_index_free(T* ptr) {
43
- ptr->~AnnoyIndex();
48
+ static void annoy_index_free(void* ptr) {
49
+ ((T*)ptr)->~AnnoyIndex();
44
50
  ruby_xfree(ptr);
45
51
  };
46
52
 
53
+ static size_t annoy_index_size(const void* ptr) {
54
+ return sizeof(*((T*)ptr));
55
+ };
56
+
47
57
  static T* get_annoy_index(VALUE self) {
48
58
  T* ptr;
49
- Data_Get_Struct(self, T, ptr);
59
+ TypedData_Get_Struct(self, T, &annoy_index_type, ptr);
50
60
  return ptr;
51
61
  };
52
62
 
@@ -55,7 +65,7 @@ template<class T, typename F> class RbAnnoyIndex
55
65
  rb_define_alloc_func(rb_cAnnoyIndex, annoy_index_alloc);
56
66
  rb_define_method(rb_cAnnoyIndex, "initialize", RUBY_METHOD_FUNC(_annoy_index_init), 1);
57
67
  rb_define_method(rb_cAnnoyIndex, "add_item", RUBY_METHOD_FUNC(_annoy_index_add_item), 2);
58
- rb_define_method(rb_cAnnoyIndex, "build", RUBY_METHOD_FUNC(_annoy_index_build), 1);
68
+ rb_define_method(rb_cAnnoyIndex, "build", RUBY_METHOD_FUNC(_annoy_index_build), 2);
59
69
  rb_define_method(rb_cAnnoyIndex, "save", RUBY_METHOD_FUNC(_annoy_index_save), 2);
60
70
  rb_define_method(rb_cAnnoyIndex, "load", RUBY_METHOD_FUNC(_annoy_index_load), 2);
61
71
  rb_define_method(rb_cAnnoyIndex, "unload", RUBY_METHOD_FUNC(_annoy_index_unload), 0);
@@ -73,6 +83,7 @@ template<class T, typename F> class RbAnnoyIndex
73
83
  };
74
84
 
75
85
  private:
86
+ static const rb_data_type_t annoy_index_type;
76
87
 
77
88
  static VALUE _annoy_index_init(VALUE self, VALUE _n_dims) {
78
89
  const int n_dims = NUM2INT(_n_dims);
@@ -95,31 +106,34 @@ template<class T, typename F> class RbAnnoyIndex
95
106
  return Qfalse;
96
107
  }
97
108
 
98
- std::vector<F> vec(n_dims, 0);
109
+ F* vec = (F*)ruby_xmalloc(n_dims * sizeof(F));
99
110
  for (int i = 0; i < n_dims; i++) {
100
111
  vec[i] = typeid(F) == typeid(double) ? NUM2DBL(rb_ary_entry(arr, i)) : NUM2UINT(rb_ary_entry(arr, i));
101
112
  }
102
113
 
103
114
  char* error;
104
- if (!get_annoy_index(self)->add_item(idx, &vec[0], &error)) {
105
- rb_raise(rb_eRuntimeError, "%s", error);
115
+ if (!get_annoy_index(self)->add_item(idx, vec, &error)) {
116
+ VALUE error_str = rb_str_new_cstr(error);
106
117
  free(error);
118
+ ruby_xfree(vec);
119
+ rb_raise(rb_eRuntimeError, "%s", StringValuePtr(error_str));
107
120
  return Qfalse;
108
121
  }
109
122
 
123
+ ruby_xfree(vec);
110
124
  return Qtrue;
111
125
  };
112
126
 
113
- static VALUE _annoy_index_build(VALUE self, VALUE _n_trees) {
127
+ static VALUE _annoy_index_build(VALUE self, VALUE _n_trees, VALUE _n_jobs) {
114
128
  const int n_trees = NUM2INT(_n_trees);
129
+ const int n_jobs = NUM2INT(_n_jobs);
115
130
  char* error;
116
-
117
- if (!get_annoy_index(self)->build(n_trees, &error)) {
118
- rb_raise(rb_eRuntimeError, "%s", error);
131
+ if (!get_annoy_index(self)->build(n_trees, n_jobs, &error)) {
132
+ VALUE error_str = rb_str_new_cstr(error);
119
133
  free(error);
134
+ rb_raise(rb_eRuntimeError, "%s", StringValuePtr(error_str));
120
135
  return Qfalse;
121
136
  }
122
-
123
137
  return Qtrue;
124
138
  };
125
139
 
@@ -127,13 +141,13 @@ template<class T, typename F> class RbAnnoyIndex
127
141
  const char* filename = StringValuePtr(_filename);
128
142
  const bool prefault = _prefault == Qtrue ? true : false;
129
143
  char* error;
130
-
131
144
  if (!get_annoy_index(self)->save(filename, prefault, &error)) {
132
- rb_raise(rb_eRuntimeError, "%s", error);
145
+ VALUE error_str = rb_str_new_cstr(error);
133
146
  free(error);
147
+ rb_raise(rb_eRuntimeError, "%s", StringValuePtr(error_str));
134
148
  return Qfalse;
135
149
  }
136
-
150
+ RB_GC_GUARD(_filename);
137
151
  return Qtrue;
138
152
  };
139
153
 
@@ -141,13 +155,13 @@ template<class T, typename F> class RbAnnoyIndex
141
155
  const char* filename = StringValuePtr(_filename);
142
156
  const bool prefault = _prefault == Qtrue ? true : false;
143
157
  char* error;
144
-
145
158
  if (!get_annoy_index(self)->load(filename, prefault, &error)) {
146
- rb_raise(rb_eRuntimeError, "%s", error);
159
+ VALUE error_str = rb_str_new_cstr(error);
147
160
  free(error);
161
+ rb_raise(rb_eRuntimeError, "%s", StringValuePtr(error_str));
148
162
  return Qfalse;
149
163
  }
150
-
164
+ RB_GC_GUARD(_filename);
151
165
  return Qtrue;
152
166
  };
153
167
 
@@ -201,7 +215,7 @@ template<class T, typename F> class RbAnnoyIndex
201
215
  return Qfalse;
202
216
  }
203
217
 
204
- std::vector<F> vec(n_dims, 0);
218
+ F* vec = (F*)ruby_xmalloc(n_dims * sizeof(F));
205
219
  for (int i = 0; i < n_dims; i++) {
206
220
  vec[i] = typeid(F) == typeid(double) ? NUM2DBL(rb_ary_entry(_vec, i)) : NUM2UINT(rb_ary_entry(_vec, i));
207
221
  }
@@ -212,7 +226,9 @@ template<class T, typename F> class RbAnnoyIndex
212
226
  std::vector<int> neighbors;
213
227
  std::vector<F> distances;
214
228
 
215
- get_annoy_index(self)->get_nns_by_vector(&vec[0], n_neighbors, search_k, &neighbors, include_distances ? &distances : NULL);
229
+ get_annoy_index(self)->get_nns_by_vector(vec, n_neighbors, search_k, &neighbors, include_distances ? &distances : NULL);
230
+
231
+ ruby_xfree(vec);
216
232
 
217
233
  const int sz_neighbors = neighbors.size();
218
234
  VALUE neighbors_arr = rb_ary_new2(sz_neighbors);
@@ -239,23 +255,24 @@ template<class T, typename F> class RbAnnoyIndex
239
255
  static VALUE _annoy_index_get_item(VALUE self, VALUE _idx) {
240
256
  const int idx = NUM2INT(_idx);
241
257
  const int n_dims = get_annoy_index(self)->get_f();
242
- std::vector<F> vec(n_dims, 0);
258
+ F* vec = (F*)ruby_xmalloc(n_dims * sizeof(F));
243
259
  VALUE arr = rb_ary_new2(n_dims);
244
260
 
245
- get_annoy_index(self)->get_item(idx, &vec[0]);
261
+ get_annoy_index(self)->get_item(idx, vec);
246
262
 
247
263
  for (int i = 0; i < n_dims; i++) {
248
264
  rb_ary_store(arr, i, typeid(F) == typeid(double) ? DBL2NUM(vec[i]) : UINT2NUM(vec[i]));
249
265
  }
250
266
 
267
+ ruby_xfree(vec);
251
268
  return arr;
252
269
  };
253
270
 
254
271
  static VALUE _annoy_index_get_distance(VALUE self, VALUE _i, VALUE _j) {
255
272
  const int i = NUM2INT(_i);
256
273
  const int j = NUM2INT(_j);
257
- const double dist = get_annoy_index(self)->get_distance(i, j);
258
- return DBL2NUM(dist);
274
+ const F dist = get_annoy_index(self)->get_distance(i, j);
275
+ return typeid(F) == typeid(double) ? DBL2NUM(dist) : UINT2NUM(dist);
259
276
  };
260
277
 
261
278
  static VALUE _annoy_index_get_n_items(VALUE self) {
@@ -272,10 +289,12 @@ template<class T, typename F> class RbAnnoyIndex
272
289
  const char* filename = StringValuePtr(_filename);
273
290
  char* error;
274
291
  if (!get_annoy_index(self)->on_disk_build(filename, &error)) {
275
- rb_raise(rb_eRuntimeError, "%s", error);
292
+ VALUE error_str = rb_str_new_cstr(error);
276
293
  free(error);
294
+ rb_raise(rb_eRuntimeError, "%s", StringValuePtr(error_str));
277
295
  return Qfalse;
278
296
  }
297
+ RB_GC_GUARD(_filename);
279
298
  return Qtrue;
280
299
  };
281
300
 
@@ -297,4 +316,17 @@ template<class T, typename F> class RbAnnoyIndex
297
316
  };
298
317
  };
299
318
 
300
- #endif /* ANNOY_HPP */
319
+ template<class T, typename F>
320
+ const rb_data_type_t RbAnnoyIndex<T, F>::annoy_index_type = {
321
+ "RbAnnoyIndex",
322
+ {
323
+ NULL,
324
+ RbAnnoyIndex::annoy_index_free,
325
+ RbAnnoyIndex::annoy_index_size
326
+ },
327
+ NULL,
328
+ NULL,
329
+ RUBY_TYPED_FREE_IMMEDIATELY
330
+ };
331
+
332
+ #endif /* ANNOYEXT_HPP */
data/ext/annoy/extconf.rb CHANGED
@@ -2,8 +2,8 @@ require 'mkmf'
2
2
 
3
3
  abort 'libstdc++ is not found.' unless have_library('stdc++')
4
4
 
5
- $CXXFLAGS << " -march=native"
5
+ $CXXFLAGS << " -std=c++14 -march=native -DANNOYLIB_MULTITHREADED_BUILD"
6
6
  $INCFLAGS << " -I$(srcdir)/src"
7
7
  $VPATH << "$(srcdir)/src"
8
8
 
9
- create_makefile('annoy/annoy')
9
+ create_makefile('annoy/annoyext')
@@ -58,6 +58,12 @@ typedef signed __int64 int64_t;
58
58
  #include <queue>
59
59
  #include <limits>
60
60
 
61
+ #ifdef ANNOYLIB_MULTITHREADED_BUILD
62
+ #include <thread>
63
+ #include <mutex>
64
+ #include <shared_mutex>
65
+ #endif
66
+
61
67
  #ifdef _MSC_VER
62
68
  // Needed for Visual Studio to disable runtime checks for mempcy
63
69
  #pragma runtime_checks("s", off)
@@ -104,7 +110,6 @@ inline void set_error_from_string(char **error, const char* msg) {
104
110
  #ifndef _MSC_VER
105
111
  #define popcount __builtin_popcountll
106
112
  #else // See #293, #358
107
- #define isnan(x) _isnan(x)
108
113
  #define popcount cole_popcount
109
114
  #endif
110
115
 
@@ -346,7 +351,7 @@ inline float euclidean_distance<float>(const float* x, const float* y, int f) {
346
351
 
347
352
  #endif
348
353
 
349
-
354
+
350
355
  template<typename T>
351
356
  inline T get_norm(T* v, int f) {
352
357
  return sqrt(dot(v, v, f));
@@ -358,7 +363,7 @@ inline void two_means(const vector<Node*>& nodes, int f, Random& random, bool co
358
363
  This algorithm is a huge heuristic. Empirically it works really well, but I
359
364
  can't motivate it well. The basic idea is to keep two centroids and assign
360
365
  points to either one of them. We weight each centroid by the number of points
361
- assigned to it, so to balance it.
366
+ assigned to it, so to balance it.
362
367
  */
363
368
  static int iteration_steps = 200;
364
369
  size_t count = nodes.size();
@@ -548,7 +553,7 @@ struct DotProduct : Angular {
548
553
  static inline void create_split(const vector<Node<S, T>*>& nodes, int f, size_t s, Random& random, Node<S, T>* n) {
549
554
  Node<S, T>* p = (Node<S, T>*)alloca(s);
550
555
  Node<S, T>* q = (Node<S, T>*)alloca(s);
551
- DotProduct::zero_value(p);
556
+ DotProduct::zero_value(p);
552
557
  DotProduct::zero_value(q);
553
558
  two_means<T, Random, DotProduct, Node<S, T> >(nodes, f, random, true, p, q);
554
559
  for (int z = 0; z < f; z++)
@@ -594,8 +599,8 @@ struct DotProduct : Angular {
594
599
  // Step one: compute the norm of each vector and store that in its extra dimension (f-1)
595
600
  for (S i = 0; i < node_count; i++) {
596
601
  Node* node = get_node_ptr<S, Node>(nodes, _s, i);
597
- T norm = sqrt(dot(node->v, node->v, f));
598
- if (isnan(norm)) norm = 0;
602
+ T d = dot(node->v, node->v, f);
603
+ T norm = d < 0 ? 0 : sqrt(d);
599
604
  node->dot_factor = norm;
600
605
  }
601
606
 
@@ -612,9 +617,8 @@ struct DotProduct : Angular {
612
617
  for (S i = 0; i < node_count; i++) {
613
618
  Node* node = get_node_ptr<S, Node>(nodes, _s, i);
614
619
  T node_norm = node->dot_factor;
615
-
616
- T dot_factor = sqrt(pow(max_norm, static_cast<T>(2.0)) - pow(node_norm, static_cast<T>(2.0)));
617
- if (isnan(dot_factor)) dot_factor = 0;
620
+ T squared_norm_diff = pow(max_norm, static_cast<T>(2.0)) - pow(node_norm, static_cast<T>(2.0));
621
+ T dot_factor = squared_norm_diff < 0 ? 0 : sqrt(squared_norm_diff);
618
622
 
619
623
  node->dot_factor = dot_factor;
620
624
  }
@@ -753,7 +757,7 @@ struct Minkowski : Base {
753
757
  struct Euclidean : Minkowski {
754
758
  template<typename S, typename T>
755
759
  static inline T distance(const Node<S, T>* x, const Node<S, T>* y, int f) {
756
- return euclidean_distance(x->v, y->v, f);
760
+ return euclidean_distance(x->v, y->v, f);
757
761
  }
758
762
  template<typename S, typename T, typename Random>
759
763
  static inline void create_split(const vector<Node<S, T>*>& nodes, int f, size_t s, Random& random, Node<S, T>* n) {
@@ -817,7 +821,7 @@ class AnnoyIndexInterface {
817
821
  // Note that the methods with an **error argument will allocate memory and write the pointer to that string if error is non-NULL
818
822
  virtual ~AnnoyIndexInterface() {};
819
823
  virtual bool add_item(S item, const T* w, char** error=NULL) = 0;
820
- virtual bool build(int q, char** error=NULL) = 0;
824
+ virtual bool build(int q, int n_threads=-1, char** error=NULL) = 0;
821
825
  virtual bool unbuild(char** error=NULL) = 0;
822
826
  virtual bool save(const char* filename, bool prefault=false, char** error=NULL) = 0;
823
827
  virtual void unload() = 0;
@@ -833,7 +837,7 @@ class AnnoyIndexInterface {
833
837
  virtual bool on_disk_build(const char* filename, char** error=NULL) = 0;
834
838
  };
835
839
 
836
- template<typename S, typename T, typename Distance, typename Random>
840
+ template<typename S, typename T, typename Distance, typename Random, class ThreadedBuildPolicy>
837
841
  class AnnoyIndex : public AnnoyIndexInterface<S, T> {
838
842
  /*
839
843
  * We use random projection to build a forest of binary trees of all items.
@@ -850,12 +854,13 @@ protected:
850
854
  const int _f;
851
855
  size_t _s;
852
856
  S _n_items;
853
- Random _random;
854
857
  void* _nodes; // Could either be mmapped, or point to a memory buffer that we reallocate
855
858
  S _n_nodes;
856
859
  S _nodes_size;
857
860
  vector<S> _roots;
858
861
  S _K;
862
+ bool _is_seeded;
863
+ int _seed;
859
864
  bool _loaded;
860
865
  bool _verbose;
861
866
  int _fd;
@@ -863,7 +868,7 @@ protected:
863
868
  bool _built;
864
869
  public:
865
870
 
866
- AnnoyIndex(int f) : _f(f), _random() {
871
+ AnnoyIndex(int f) : _f(f) {
867
872
  _s = offsetof(Node, v) + _f * sizeof(T); // Size of each node
868
873
  _verbose = false;
869
874
  _built = false;
@@ -907,7 +912,7 @@ public:
907
912
 
908
913
  return true;
909
914
  }
910
-
915
+
911
916
  bool on_disk_build(const char* file, char** error=NULL) {
912
917
  _on_disk = true;
913
918
  _fd = open(file, O_RDWR | O_CREAT | O_TRUNC, (int) 0600);
@@ -928,8 +933,8 @@ public:
928
933
  #endif
929
934
  return true;
930
935
  }
931
-
932
- bool build(int q, char** error=NULL) {
936
+
937
+ bool build(int q, int n_threads=-1, char** error=NULL) {
933
938
  if (_loaded) {
934
939
  set_error_from_string(error, "You can't build a loaded index");
935
940
  return false;
@@ -943,21 +948,8 @@ public:
943
948
  D::template preprocess<T, S, Node>(_nodes, _s, _n_items, _f);
944
949
 
945
950
  _n_nodes = _n_items;
946
- while (1) {
947
- if (q == -1 && _n_nodes >= _n_items * 2)
948
- break;
949
- if (q != -1 && _roots.size() >= (size_t)q)
950
- break;
951
- if (_verbose) showUpdate("pass %zd...\n", _roots.size());
952
-
953
- vector<S> indices;
954
- for (S i = 0; i < _n_items; i++) {
955
- if (_get(i)->n_descendants >= 1) // Issue #223
956
- indices.push_back(i);
957
- }
958
951
 
959
- _roots.push_back(_make_tree(indices, true));
960
- }
952
+ ThreadedBuildPolicy::template build<S, T>(this, q, n_threads);
961
953
 
962
954
  // Also, copy the roots into the last segment of the array
963
955
  // This way we can load them faster without reading the whole file
@@ -967,7 +959,7 @@ public:
967
959
  _n_nodes += _roots.size();
968
960
 
969
961
  if (_verbose) showUpdate("has %d nodes\n", _n_nodes);
970
-
962
+
971
963
  if (_on_disk) {
972
964
  if (!remap_memory_and_truncate(&_nodes, _fd,
973
965
  static_cast<size_t>(_s) * static_cast<size_t>(_nodes_size),
@@ -981,7 +973,7 @@ public:
981
973
  _built = true;
982
974
  return true;
983
975
  }
984
-
976
+
985
977
  bool unbuild(char** error=NULL) {
986
978
  if (_loaded) {
987
979
  set_error_from_string(error, "You can't unbuild a loaded index");
@@ -1035,6 +1027,7 @@ public:
1035
1027
  _n_nodes = 0;
1036
1028
  _nodes_size = 0;
1037
1029
  _on_disk = false;
1030
+ _is_seeded = false;
1038
1031
  _roots.clear();
1039
1032
  }
1040
1033
 
@@ -1142,29 +1135,82 @@ public:
1142
1135
  }
1143
1136
 
1144
1137
  void set_seed(int seed) {
1138
+ _is_seeded = true;
1139
+ _seed = seed;
1140
+ }
1141
+
1142
+ void thread_build(int q, int thread_idx, ThreadedBuildPolicy& threaded_build_policy) {
1143
+ Random _random;
1144
+ // Each thread needs its own seed, otherwise each thread would be building the same tree(s)
1145
+ int seed = _is_seeded ? _seed + thread_idx : thread_idx;
1145
1146
  _random.set_seed(seed);
1147
+
1148
+ vector<S> thread_roots;
1149
+ while (1) {
1150
+ if (q == -1) {
1151
+ threaded_build_policy.lock_n_nodes();
1152
+ if (_n_nodes >= 2 * _n_items) {
1153
+ threaded_build_policy.unlock_n_nodes();
1154
+ break;
1155
+ }
1156
+ threaded_build_policy.unlock_n_nodes();
1157
+ } else {
1158
+ if (thread_roots.size() >= (size_t)q) {
1159
+ break;
1160
+ }
1161
+ }
1162
+
1163
+ if (_verbose) showUpdate("pass %zd...\n", thread_roots.size());
1164
+
1165
+ vector<S> indices;
1166
+ threaded_build_policy.lock_shared_nodes();
1167
+ for (S i = 0; i < _n_items; i++) {
1168
+ if (_get(i)->n_descendants >= 1) { // Issue #223
1169
+ indices.push_back(i);
1170
+ }
1171
+ }
1172
+ threaded_build_policy.unlock_shared_nodes();
1173
+
1174
+ thread_roots.push_back(_make_tree(indices, true, _random, threaded_build_policy));
1175
+ }
1176
+
1177
+ threaded_build_policy.lock_roots();
1178
+ _roots.insert(_roots.end(), thread_roots.begin(), thread_roots.end());
1179
+ threaded_build_policy.unlock_roots();
1146
1180
  }
1147
1181
 
1148
1182
  protected:
1149
- void _allocate_size(S n) {
1183
+ void _reallocate_nodes(S n) {
1184
+ const double reallocation_factor = 1.3;
1185
+ S new_nodes_size = std::max(n, (S) ((_nodes_size + 1) * reallocation_factor));
1186
+ void *old = _nodes;
1187
+
1188
+ if (_on_disk) {
1189
+ if (!remap_memory_and_truncate(&_nodes, _fd,
1190
+ static_cast<size_t>(_s) * static_cast<size_t>(_nodes_size),
1191
+ static_cast<size_t>(_s) * static_cast<size_t>(new_nodes_size)) &&
1192
+ _verbose)
1193
+ showUpdate("File truncation error\n");
1194
+ } else {
1195
+ _nodes = realloc(_nodes, _s * new_nodes_size);
1196
+ memset((char *) _nodes + (_nodes_size * _s) / sizeof(char), 0, (new_nodes_size - _nodes_size) * _s);
1197
+ }
1198
+
1199
+ _nodes_size = new_nodes_size;
1200
+ if (_verbose) showUpdate("Reallocating to %d nodes: old_address=%p, new_address=%p\n", new_nodes_size, old, _nodes);
1201
+ }
1202
+
1203
+ void _allocate_size(S n, ThreadedBuildPolicy& threaded_build_policy) {
1150
1204
  if (n > _nodes_size) {
1151
- const double reallocation_factor = 1.3;
1152
- S new_nodes_size = std::max(n, (S) ((_nodes_size + 1) * reallocation_factor));
1153
- void *old = _nodes;
1154
-
1155
- if (_on_disk) {
1156
- if (!remap_memory_and_truncate(&_nodes, _fd,
1157
- static_cast<size_t>(_s) * static_cast<size_t>(_nodes_size),
1158
- static_cast<size_t>(_s) * static_cast<size_t>(new_nodes_size)) &&
1159
- _verbose)
1160
- showUpdate("File truncation error\n");
1161
- } else {
1162
- _nodes = realloc(_nodes, _s * new_nodes_size);
1163
- memset((char *) _nodes + (_nodes_size * _s) / sizeof(char), 0, (new_nodes_size - _nodes_size) * _s);
1164
- }
1205
+ threaded_build_policy.lock_nodes();
1206
+ _reallocate_nodes(n);
1207
+ threaded_build_policy.unlock_nodes();
1208
+ }
1209
+ }
1165
1210
 
1166
- _nodes_size = new_nodes_size;
1167
- if (_verbose) showUpdate("Reallocating to %d nodes: old_address=%p, new_address=%p\n", new_nodes_size, old, _nodes);
1211
+ void _allocate_size(S n) {
1212
+ if (n > _nodes_size) {
1213
+ _reallocate_nodes(n);
1168
1214
  }
1169
1215
  }
1170
1216
 
@@ -1179,7 +1225,7 @@ protected:
1179
1225
  return std::max(f, 1-f);
1180
1226
  }
1181
1227
 
1182
- S _make_tree(const vector<S>& indices, bool is_root) {
1228
+ S _make_tree(const vector<S>& indices, bool is_root, Random& _random, ThreadedBuildPolicy& threaded_build_policy) {
1183
1229
  // The basic rule is that if we have <= _K items, then it's a leaf node, otherwise it's a split node.
1184
1230
  // There's some regrettable complications caused by the problem that root nodes have to be "special":
1185
1231
  // 1. We identify root nodes by the arguable logic that _n_items == n->n_descendants, regardless of how many descendants they actually have
@@ -1189,8 +1235,12 @@ protected:
1189
1235
  return indices[0];
1190
1236
 
1191
1237
  if (indices.size() <= (size_t)_K && (!is_root || (size_t)_n_items <= (size_t)_K || indices.size() == 1)) {
1192
- _allocate_size(_n_nodes + 1);
1238
+ threaded_build_policy.lock_n_nodes();
1239
+ _allocate_size(_n_nodes + 1, threaded_build_policy);
1193
1240
  S item = _n_nodes++;
1241
+ threaded_build_policy.unlock_n_nodes();
1242
+
1243
+ threaded_build_policy.lock_shared_nodes();
1194
1244
  Node* m = _get(item);
1195
1245
  m->n_descendants = is_root ? _n_items : (S)indices.size();
1196
1246
 
@@ -1200,9 +1250,12 @@ protected:
1200
1250
  // Only copy when necessary to avoid crash in MSVC 9. #293
1201
1251
  if (!indices.empty())
1202
1252
  memcpy(m->children, &indices[0], indices.size() * sizeof(S));
1253
+
1254
+ threaded_build_policy.unlock_shared_nodes();
1203
1255
  return item;
1204
1256
  }
1205
1257
 
1258
+ threaded_build_policy.lock_shared_nodes();
1206
1259
  vector<Node*> children;
1207
1260
  for (size_t i = 0; i < indices.size(); i++) {
1208
1261
  S j = indices[i];
@@ -1233,6 +1286,7 @@ protected:
1233
1286
  if (_split_imbalance(children_indices[0], children_indices[1]) < 0.95)
1234
1287
  break;
1235
1288
  }
1289
+ threaded_build_policy.unlock_shared_nodes();
1236
1290
 
1237
1291
  // If we didn't find a hyperplane, just randomize sides as a last option
1238
1292
  while (_split_imbalance(children_indices[0], children_indices[1]) > 0.99) {
@@ -1259,13 +1313,17 @@ protected:
1259
1313
  m->n_descendants = is_root ? _n_items : (S)indices.size();
1260
1314
  for (int side = 0; side < 2; side++) {
1261
1315
  // run _make_tree for the smallest child first (for cache locality)
1262
- m->children[side^flip] = _make_tree(children_indices[side^flip], false);
1316
+ m->children[side^flip] = _make_tree(children_indices[side^flip], false, _random, threaded_build_policy);
1263
1317
  }
1264
1318
 
1265
-
1266
- _allocate_size(_n_nodes + 1);
1319
+ threaded_build_policy.lock_n_nodes();
1320
+ _allocate_size(_n_nodes + 1, threaded_build_policy);
1267
1321
  S item = _n_nodes++;
1322
+ threaded_build_policy.unlock_n_nodes();
1323
+
1324
+ threaded_build_policy.lock_shared_nodes();
1268
1325
  memcpy(_get(item), m, _s);
1326
+ threaded_build_policy.unlock_shared_nodes();
1269
1327
 
1270
1328
  return item;
1271
1329
  }
@@ -1311,7 +1369,7 @@ protected:
1311
1369
  vector<pair<T, S> > nns_dist;
1312
1370
  S last = -1;
1313
1371
  for (size_t i = 0; i < nns.size(); i++) {
1314
- S j = nns[i];
1372
+ S j = nns[i];
1315
1373
  if (j == last)
1316
1374
  continue;
1317
1375
  last = j;
@@ -1330,5 +1388,92 @@ protected:
1330
1388
  }
1331
1389
  };
1332
1390
 
1391
+ class AnnoyIndexSingleThreadedBuildPolicy {
1392
+ public:
1393
+ template<typename S, typename T, typename D, typename Random>
1394
+ static void build(AnnoyIndex<S, T, D, Random, AnnoyIndexSingleThreadedBuildPolicy>* annoy, int q, int n_threads) {
1395
+ AnnoyIndexSingleThreadedBuildPolicy threaded_build_policy;
1396
+ annoy->thread_build(q, 0, threaded_build_policy);
1397
+ }
1398
+
1399
+ void lock_n_nodes() {}
1400
+ void unlock_n_nodes() {}
1401
+
1402
+ void lock_nodes() {}
1403
+ void unlock_nodes() {}
1404
+
1405
+ void lock_shared_nodes() {}
1406
+ void unlock_shared_nodes() {}
1407
+
1408
+ void lock_roots() {}
1409
+ void unlock_roots() {}
1410
+ };
1411
+
1412
+ #ifdef ANNOYLIB_MULTITHREADED_BUILD
1413
+ class AnnoyIndexMultiThreadedBuildPolicy {
1414
+ private:
1415
+ std::shared_timed_mutex nodes_mutex;
1416
+ std::mutex n_nodes_mutex;
1417
+ std::mutex roots_mutex;
1418
+
1419
+ public:
1420
+ template<typename S, typename T, typename D, typename Random>
1421
+ static void build(AnnoyIndex<S, T, D, Random, AnnoyIndexMultiThreadedBuildPolicy>* annoy, int q, int n_threads) {
1422
+ AnnoyIndexMultiThreadedBuildPolicy threaded_build_policy;
1423
+ if (n_threads == -1) {
1424
+ // If the hardware_concurrency() value is not well defined or not computable, it returns 0.
1425
+ // We guard against this by using at least 1 thread.
1426
+ n_threads = std::max(1, (int)std::thread::hardware_concurrency());
1427
+ }
1428
+
1429
+ vector<std::thread> threads(n_threads);
1430
+
1431
+ for (int thread_idx = 0; thread_idx < n_threads; thread_idx++) {
1432
+ int trees_per_thread = q == -1 ? -1 : (int)floor((q + thread_idx) / n_threads);
1433
+
1434
+ threads[thread_idx] = std::thread(
1435
+ &AnnoyIndex<S, T, D, Random, AnnoyIndexMultiThreadedBuildPolicy>::thread_build,
1436
+ annoy,
1437
+ trees_per_thread,
1438
+ thread_idx,
1439
+ std::ref(threaded_build_policy)
1440
+ );
1441
+ }
1442
+
1443
+ for (auto& thread : threads) {
1444
+ thread.join();
1445
+ }
1446
+ }
1447
+
1448
+ void lock_n_nodes() {
1449
+ n_nodes_mutex.lock();
1450
+ }
1451
+ void unlock_n_nodes() {
1452
+ n_nodes_mutex.unlock();
1453
+ }
1454
+
1455
+ void lock_nodes() {
1456
+ nodes_mutex.lock();
1457
+ }
1458
+ void unlock_nodes() {
1459
+ nodes_mutex.unlock();
1460
+ }
1461
+
1462
+ void lock_shared_nodes() {
1463
+ nodes_mutex.lock_shared();
1464
+ }
1465
+ void unlock_shared_nodes() {
1466
+ nodes_mutex.unlock_shared();
1467
+ }
1468
+
1469
+ void lock_roots() {
1470
+ roots_mutex.lock();
1471
+ }
1472
+ void unlock_roots() {
1473
+ roots_mutex.unlock();
1474
+ }
1475
+ };
1476
+ #endif
1477
+
1333
1478
  #endif
1334
1479
  // vim: tabstop=2 shiftwidth=2