usearch 0.22.2 → 0.22.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.releaserc +2 -1
- package/.vscode/launch.json +5 -1
- package/.vscode/settings.json +3 -1
- package/Cargo.toml +1 -1
- package/README.md +6 -7
- package/conanfile.py +1 -1
- package/cpp/bench.cpp +2 -2
- package/cpp/test.cpp +4 -1
- package/docs/compilation.md +29 -7
- package/docs/cpp/index.rst +3 -3
- package/docs/cpp/reference.rst +13 -3
- package/docs/index.rst +8 -8
- package/docs/java/reference.rst +1 -1
- package/include/usearch/index.hpp +96 -47
- package/include/usearch/index_punned_dense.hpp +258 -128
- package/include/usearch/index_punned_helpers.hpp +150 -40
- package/javascript/README.md +1 -1
- package/package.json +1 -1
- package/pyproject.toml +3 -3
- package/python/lib.cpp +4 -0
- package/wasmer.toml +5 -1
|
@@ -214,6 +214,11 @@ inline std::uint16_t f32_to_f16(float f32) noexcept {
|
|
|
214
214
|
#endif
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/**
|
|
218
|
+
* @brief Numeric type for the IEEE 754 half-precision floating point.
|
|
219
|
+
* If hardware support isn't available, falls back to a hardware
|
|
220
|
+
* agnostic in-software implementation.
|
|
221
|
+
*/
|
|
217
222
|
class f16_bits_t {
|
|
218
223
|
std::uint16_t uint16_{};
|
|
219
224
|
|
|
@@ -309,15 +314,31 @@ struct uuid_t {
|
|
|
309
314
|
std::uint8_t octets[16];
|
|
310
315
|
};
|
|
311
316
|
|
|
317
|
+
/**
|
|
318
|
+
* @brief An STL-based executor or a "thread-pool" for parallel execution.
|
|
319
|
+
* Isn't efficient for small batches, as it recreates the threads on every call.
|
|
320
|
+
*/
|
|
312
321
|
class executor_stl_t {
|
|
313
322
|
std::size_t threads_count_{};
|
|
314
323
|
|
|
315
324
|
public:
|
|
325
|
+
/**
|
|
326
|
+
* @param threads_count The number of threads to be used for parallel execution.
|
|
327
|
+
*/
|
|
316
328
|
executor_stl_t(std::size_t threads_count = 0) noexcept
|
|
317
329
|
: threads_count_(threads_count ? threads_count : std::thread::hardware_concurrency()) {}
|
|
318
330
|
|
|
331
|
+
/**
|
|
332
|
+
* @return Maximum number of threads available to the executor.
|
|
333
|
+
*/
|
|
319
334
|
std::size_t size() const noexcept { return threads_count_; }
|
|
320
335
|
|
|
336
|
+
/**
|
|
337
|
+
* @brief Executes tasks in bulk using the specified thread-aware function.
|
|
338
|
+
* @param tasks The total number of tasks to be executed.
|
|
339
|
+
* @param thread_aware_function The thread-aware function to be called for each thread index and task index.
|
|
340
|
+
* @throws If an exception occurs during execution of the thread-aware function.
|
|
341
|
+
*/
|
|
321
342
|
template <typename thread_aware_function_at>
|
|
322
343
|
void execute_bulk(std::size_t tasks, thread_aware_function_at&& thread_aware_function) noexcept(false) {
|
|
323
344
|
std::vector<std::thread> threads_pool;
|
|
@@ -333,6 +354,11 @@ class executor_stl_t {
|
|
|
333
354
|
threads_pool[thread_idx].join();
|
|
334
355
|
}
|
|
335
356
|
|
|
357
|
+
/**
|
|
358
|
+
* @brief Saturates every available thread with the given workload, until they finish.
|
|
359
|
+
* @param thread_aware_function The thread-aware function to be called for each thread index.
|
|
360
|
+
* @throws If an exception occurs during execution of the thread-aware function.
|
|
361
|
+
*/
|
|
336
362
|
template <typename thread_aware_function_at>
|
|
337
363
|
void execute_bulk(thread_aware_function_at&& thread_aware_function) noexcept(false) {
|
|
338
364
|
std::vector<std::thread> threads_pool;
|
|
@@ -345,14 +371,30 @@ class executor_stl_t {
|
|
|
345
371
|
|
|
346
372
|
#if USEARCH_USE_OPENMP
|
|
347
373
|
|
|
374
|
+
/**
|
|
375
|
+
* @brief An OpenMP-based executor or a "thread-pool" for parallel execution.
|
|
376
|
+
* Is the preferred implementation, when available, and maximum performance is needed.
|
|
377
|
+
*/
|
|
348
378
|
class executor_openmp_t {
|
|
349
379
|
public:
|
|
380
|
+
/**
|
|
381
|
+
* @param threads_count The number of threads to be used for parallel execution.
|
|
382
|
+
*/
|
|
350
383
|
executor_openmp_t(std::size_t threads_count = 0) noexcept {
|
|
351
384
|
omp_set_num_threads(threads_count ? threads_count : std::thread::hardware_concurrency());
|
|
352
385
|
}
|
|
353
386
|
|
|
387
|
+
/**
|
|
388
|
+
* @return Maximum number of threads available to the executor.
|
|
389
|
+
*/
|
|
354
390
|
std::size_t size() const noexcept { return omp_get_num_threads(); }
|
|
355
391
|
|
|
392
|
+
/**
|
|
393
|
+
* @brief Executes tasks in bulk using the specified thread-aware function.
|
|
394
|
+
* @param tasks The total number of tasks to be executed.
|
|
395
|
+
* @param thread_aware_function The thread-aware function to be called for each thread index and task index.
|
|
396
|
+
* @throws If an exception occurs during execution of the thread-aware function.
|
|
397
|
+
*/
|
|
356
398
|
template <typename thread_aware_function_at>
|
|
357
399
|
void execute_bulk(std::size_t tasks, thread_aware_function_at&& thread_aware_function) noexcept(false) {
|
|
358
400
|
#pragma omp parallel for schedule(dynamic)
|
|
@@ -360,6 +402,11 @@ class executor_openmp_t {
|
|
|
360
402
|
thread_aware_function(omp_get_thread_num(), i);
|
|
361
403
|
}
|
|
362
404
|
|
|
405
|
+
/**
|
|
406
|
+
* @brief Saturates every available thread with the given workload, until they finish.
|
|
407
|
+
* @param thread_aware_function The thread-aware function to be called for each thread index.
|
|
408
|
+
* @throws If an exception occurs during execution of the thread-aware function.
|
|
409
|
+
*/
|
|
363
410
|
template <typename thread_aware_function_at>
|
|
364
411
|
void execute_bulk(thread_aware_function_at&& thread_aware_function) noexcept(false) {
|
|
365
412
|
#pragma omp parallel
|
|
@@ -415,18 +462,17 @@ class aligned_allocator_gt {
|
|
|
415
462
|
|
|
416
463
|
using aligned_allocator_t = aligned_allocator_gt<>;
|
|
417
464
|
|
|
418
|
-
#if !defined(USEARCH_DEFINED_WINDOWS)
|
|
419
|
-
|
|
420
465
|
/**
|
|
421
466
|
* @brief Memory-mapping allocator designed for "alloc many, free at once" usage patterns.
|
|
422
|
-
* Thread-safe.
|
|
467
|
+
* Thread-safe, @b except constructors and destructors.
|
|
423
468
|
*
|
|
424
469
|
* Using this memory allocator won't affect your overall speed much, as that is not the bottleneck.
|
|
425
470
|
* However, it can drastically improve memory usage especcially for huge indexes of small vectors.
|
|
426
471
|
*/
|
|
427
472
|
template <std::size_t alignment_ak = 1> class memory_mapping_allocator_gt {
|
|
428
473
|
|
|
429
|
-
static constexpr std::size_t
|
|
474
|
+
static constexpr std::size_t min_capacity() { return 1024 * 1024 * 4; }
|
|
475
|
+
static constexpr std::size_t capacity_multiplier() { return 2; }
|
|
430
476
|
static constexpr std::size_t head_size() {
|
|
431
477
|
/// Pointer to the the previous arena and the size of the current one.
|
|
432
478
|
return divide_round_up<alignment_ak>(sizeof(byte_t*) + sizeof(std::size_t)) * alignment_ak;
|
|
@@ -435,8 +481,33 @@ template <std::size_t alignment_ak = 1> class memory_mapping_allocator_gt {
|
|
|
435
481
|
std::mutex mutex_;
|
|
436
482
|
byte_t* last_arena_ = nullptr;
|
|
437
483
|
std::size_t last_usage_ = head_size();
|
|
438
|
-
std::size_t last_capacity_ =
|
|
484
|
+
std::size_t last_capacity_ = min_capacity();
|
|
485
|
+
std::size_t wasted_space_ = 0;
|
|
439
486
|
|
|
487
|
+
public:
|
|
488
|
+
using value_type = byte_t;
|
|
489
|
+
using size_type = std::size_t;
|
|
490
|
+
using pointer = byte_t*;
|
|
491
|
+
using const_pointer = byte_t const*;
|
|
492
|
+
|
|
493
|
+
memory_mapping_allocator_gt() = default;
|
|
494
|
+
memory_mapping_allocator_gt(memory_mapping_allocator_gt&& other) noexcept
|
|
495
|
+
: last_arena_(exchange(other.last_arena_, nullptr)), last_usage_(exchange(other.last_usage_, 0)),
|
|
496
|
+
last_capacity_(exchange(other.last_capacity_, 0)), wasted_space_(exchange(other.wasted_space_, 0)) {}
|
|
497
|
+
|
|
498
|
+
memory_mapping_allocator_gt& operator=(memory_mapping_allocator_gt&& other) noexcept {
|
|
499
|
+
std::swap(last_arena_, other.last_arena_);
|
|
500
|
+
std::swap(last_usage_, other.last_usage_);
|
|
501
|
+
std::swap(last_capacity_, other.last_capacity_);
|
|
502
|
+
std::swap(wasted_space_, other.wasted_space_);
|
|
503
|
+
return *this;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
~memory_mapping_allocator_gt() noexcept { reset(); }
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* @brief Discards all previously allocated memory buffers.
|
|
510
|
+
*/
|
|
440
511
|
void reset() noexcept {
|
|
441
512
|
byte_t* last_arena = last_arena_;
|
|
442
513
|
while (last_arena) {
|
|
@@ -444,74 +515,113 @@ template <std::size_t alignment_ak = 1> class memory_mapping_allocator_gt {
|
|
|
444
515
|
std::memcpy(&previous_arena, last_arena, sizeof(byte_t*));
|
|
445
516
|
std::size_t current_size;
|
|
446
517
|
std::memcpy(¤t_size, last_arena + sizeof(byte_t*), sizeof(std::size_t));
|
|
518
|
+
#if defined(USEARCH_DEFINED_WINDOWS)
|
|
519
|
+
::VirtualFree(last_arena, 0, MEM_RELEASE);
|
|
520
|
+
#else
|
|
447
521
|
munmap(last_arena, current_size);
|
|
522
|
+
#endif
|
|
448
523
|
last_arena = previous_arena;
|
|
449
524
|
}
|
|
450
525
|
|
|
451
526
|
// Clear the references:
|
|
452
527
|
last_arena_ = nullptr;
|
|
453
528
|
last_usage_ = head_size();
|
|
454
|
-
last_capacity_ =
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
public:
|
|
458
|
-
using value_type = byte_t;
|
|
459
|
-
using size_type = std::size_t;
|
|
460
|
-
using pointer = byte_t*;
|
|
461
|
-
using const_pointer = byte_t const*;
|
|
462
|
-
|
|
463
|
-
memory_mapping_allocator_gt() = default;
|
|
464
|
-
memory_mapping_allocator_gt(memory_mapping_allocator_gt&& other) noexcept
|
|
465
|
-
: last_arena_(other.last_arena_), last_usage_(other.last_usage_), last_capacity_(other.last_capacity_) {}
|
|
466
|
-
memory_mapping_allocator_gt& operator=(memory_mapping_allocator_gt&& other) noexcept {
|
|
467
|
-
std::swap(last_arena_, other.last_arena_);
|
|
468
|
-
std::swap(last_usage_, other.last_usage_);
|
|
469
|
-
std::swap(last_capacity_, other.last_capacity_);
|
|
470
|
-
return *this;
|
|
529
|
+
last_capacity_ = min_capacity();
|
|
530
|
+
wasted_space_ = 0;
|
|
471
531
|
}
|
|
472
532
|
|
|
533
|
+
/**
|
|
534
|
+
* @brief Copy constructor.
|
|
535
|
+
* @note This is a no-op copy constructor since the allocator is not copyable.
|
|
536
|
+
*/
|
|
473
537
|
memory_mapping_allocator_gt(memory_mapping_allocator_gt const&) noexcept {}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* @brief Copy assignment operator.
|
|
541
|
+
* @note This is a no-op copy assignment operator since the allocator is not copyable.
|
|
542
|
+
* @return Reference to the allocator after the assignment.
|
|
543
|
+
*/
|
|
474
544
|
memory_mapping_allocator_gt& operator=(memory_mapping_allocator_gt const&) noexcept {
|
|
475
545
|
reset();
|
|
476
546
|
return *this;
|
|
477
547
|
}
|
|
478
548
|
|
|
479
|
-
|
|
480
|
-
|
|
549
|
+
/**
|
|
550
|
+
* @brief Allocates an @b uninitialized block of memory of the specified size.
|
|
551
|
+
* @param count_bytes The number of bytes to allocate.
|
|
552
|
+
* @return A pointer to the allocated memory block, or `nullptr` if allocation fails.
|
|
553
|
+
*/
|
|
481
554
|
inline byte_t* allocate(std::size_t count_bytes) noexcept {
|
|
482
|
-
|
|
555
|
+
std::size_t extended_bytes = divide_round_up<alignment_ak>(count_bytes) * alignment_ak;
|
|
556
|
+
// Check if the requested allocation size is absurd
|
|
557
|
+
if (extended_bytes > min_capacity())
|
|
558
|
+
return nullptr;
|
|
483
559
|
|
|
484
560
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
485
|
-
if (!last_arena_ || (last_usage_ +
|
|
486
|
-
std::size_t
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
561
|
+
if (!last_arena_ || (last_usage_ + extended_bytes > last_capacity_)) {
|
|
562
|
+
std::size_t new_cap = last_capacity_ * capacity_multiplier();
|
|
563
|
+
#if defined(USEARCH_DEFINED_WINDOWS)
|
|
564
|
+
byte_t* new_arena = (byte_t*)(::VirtualAlloc(NULL, new_cap, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
|
|
565
|
+
if (new_arena == nullptr)
|
|
566
|
+
return nullptr;
|
|
567
|
+
#else
|
|
568
|
+
byte_t* new_arena = (byte_t*)mmap(NULL, new_cap, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
|
|
569
|
+
if (!new_arena)
|
|
570
|
+
return nullptr;
|
|
571
|
+
#endif
|
|
490
572
|
std::memcpy(new_arena, &last_arena_, sizeof(byte_t*));
|
|
491
|
-
std::memcpy(new_arena + sizeof(byte_t*), &
|
|
573
|
+
std::memcpy(new_arena + sizeof(byte_t*), &new_cap, sizeof(std::size_t));
|
|
492
574
|
|
|
575
|
+
wasted_space_ += total_reserved();
|
|
493
576
|
last_arena_ = new_arena;
|
|
494
|
-
last_capacity_ =
|
|
577
|
+
last_capacity_ = new_cap;
|
|
495
578
|
last_usage_ = head_size();
|
|
496
579
|
}
|
|
497
580
|
|
|
498
|
-
|
|
581
|
+
wasted_space_ += extended_bytes - count_bytes;
|
|
582
|
+
return last_arena_ + exchange(last_usage_, last_usage_ + extended_bytes);
|
|
499
583
|
}
|
|
500
584
|
|
|
501
585
|
/**
|
|
502
|
-
* @
|
|
586
|
+
* @brief Returns the amount of memory used by the allocator across all arenas.
|
|
587
|
+
* @return The amount of space in bytes.
|
|
503
588
|
*/
|
|
504
|
-
|
|
505
|
-
|
|
589
|
+
std::size_t total_allocated() const noexcept {
|
|
590
|
+
if (!last_arena_)
|
|
591
|
+
return 0;
|
|
592
|
+
std::size_t total_used = 0;
|
|
593
|
+
std::size_t last_capacity = last_capacity_;
|
|
594
|
+
do {
|
|
595
|
+
total_used += last_capacity;
|
|
596
|
+
last_capacity /= capacity_multiplier();
|
|
597
|
+
} while (last_capacity >= min_capacity());
|
|
598
|
+
return total_used;
|
|
599
|
+
}
|
|
506
600
|
|
|
507
|
-
|
|
601
|
+
/**
|
|
602
|
+
* @brief Returns the amount of wasted space due to alignment.
|
|
603
|
+
* @return The amount of wasted space in bytes.
|
|
604
|
+
*/
|
|
605
|
+
std::size_t total_wasted() const noexcept { return wasted_space_; }
|
|
508
606
|
|
|
509
|
-
|
|
607
|
+
/**
|
|
608
|
+
* @brief Returns the amount of remaining memory already reserved but not yet used.
|
|
609
|
+
* @return The amount of reserved memory in bytes.
|
|
610
|
+
*/
|
|
611
|
+
std::size_t total_reserved() const noexcept { return last_capacity_ - last_usage_; }
|
|
510
612
|
|
|
511
|
-
|
|
613
|
+
/**
|
|
614
|
+
* @warning The very first memory de-allocation discards all the arenas!
|
|
615
|
+
*/
|
|
616
|
+
void deallocate(byte_t* = nullptr, std::size_t = 0) noexcept { reset(); }
|
|
617
|
+
};
|
|
512
618
|
|
|
513
|
-
|
|
619
|
+
using memory_mapping_allocator_t = memory_mapping_allocator_gt<>;
|
|
514
620
|
|
|
621
|
+
/**
|
|
622
|
+
* @brief Utility class used to cast arrays of one scalar type to another,
|
|
623
|
+
* avoiding unnecessary conversions.
|
|
624
|
+
*/
|
|
515
625
|
template <typename from_scalar_at, typename to_scalar_at> struct cast_gt {
|
|
516
626
|
inline bool operator()(byte_t const* input, std::size_t dimensions, byte_t* output) const {
|
|
517
627
|
from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
|
package/javascript/README.md
CHANGED
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -16,7 +16,7 @@ skip = ["*musllinux*", "*i686*", "pp*", "cp36-*"]
|
|
|
16
16
|
|
|
17
17
|
[tool.cibuildwheel.linux]
|
|
18
18
|
before-build = [
|
|
19
|
-
"rm -rf {project}/CMakeCache.txt {project}/build {project}/build_debug {project}/CMakeFiles.txt {project}/_deps",
|
|
19
|
+
"rm -rf {project}/CMakeCache.txt {project}/build {project}/build_debug {project}/CMakeFiles.txt {project}/_deps {project}/.pytest_cache",
|
|
20
20
|
"mkdir -p build/usearch",
|
|
21
21
|
"git submodule update --init --recursive"
|
|
22
22
|
]
|
|
@@ -31,7 +31,7 @@ repair-wheel-command = "auditwheel repair --lib-sdir . -w {dest_dir} {wheel}"
|
|
|
31
31
|
|
|
32
32
|
[tool.cibuildwheel.macos]
|
|
33
33
|
before-build = [
|
|
34
|
-
"rm -rf {project}/CMakeCache.txt {project}/build {project}/build_debug {project}/CMakeFiles.txt {project}/_deps",
|
|
34
|
+
"rm -rf {project}/CMakeCache.txt {project}/build {project}/build_debug {project}/CMakeFiles.txt {project}/_deps {project}/.pytest_cache",
|
|
35
35
|
"mkdir -p build/usearch",
|
|
36
36
|
"git submodule update --init --recursive"
|
|
37
37
|
]
|
|
@@ -41,7 +41,7 @@ repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest
|
|
|
41
41
|
|
|
42
42
|
[tool.cibuildwheel.windows]
|
|
43
43
|
before-build = [
|
|
44
|
-
"rd /s /q {project}\\CMakeCache.txt {project}\\build {project}\\build_debug {project}\\CMakeFiles.txt {project}\\_deps || echo Done",
|
|
44
|
+
"rd /s /q {project}\\CMakeCache.txt {project}\\build {project}\\build_debug {project}\\CMakeFiles.txt {project}\\_deps {project}\\.pytest_cache || echo Done",
|
|
45
45
|
"md build\\usearch",
|
|
46
46
|
"git submodule update --init --recursive"
|
|
47
47
|
]
|
package/python/lib.cpp
CHANGED
|
@@ -687,6 +687,8 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
687
687
|
[](dense_index_py_t& index, label_t label, bool compact, std::size_t threads) -> bool {
|
|
688
688
|
dense_labeling_result_t result = index.remove(label);
|
|
689
689
|
result.error.raise();
|
|
690
|
+
if (!compact)
|
|
691
|
+
return result.completed;
|
|
690
692
|
|
|
691
693
|
if (!threads)
|
|
692
694
|
threads = std::thread::hardware_concurrency();
|
|
@@ -704,6 +706,8 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
704
706
|
std::size_t threads) -> std::size_t {
|
|
705
707
|
dense_labeling_result_t result = index.remove(labels.begin(), labels.end());
|
|
706
708
|
result.error.raise();
|
|
709
|
+
if (!compact)
|
|
710
|
+
return result.completed;
|
|
707
711
|
|
|
708
712
|
if (!threads)
|
|
709
713
|
threads = std::thread::hardware_concurrency();
|
package/wasmer.toml
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name="unum/usearch"
|
|
3
|
-
version="0.
|
|
3
|
+
version="0.22.3"
|
|
4
4
|
description="Smaller & Faster Single-File Vector Search Engine from Unum"
|
|
5
|
+
license="Apache-2.0"
|
|
6
|
+
readme="README.md"
|
|
7
|
+
repository="https://github.com/unum-cloud/usearch"
|
|
8
|
+
homepage="https://unum-cloud.github.io/usearch/javascript"
|
|
5
9
|
|
|
6
10
|
[[module]]
|
|
7
11
|
name="index"
|