usearch 0.18.6 → 0.19.0

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.
@@ -112,12 +112,50 @@ namespace usearch {
112
112
  using f64_t = double;
113
113
  using f32_t = float;
114
114
  using byte_t = char;
115
+ enum class b1x8_t : unsigned char {};
116
+
117
+ enum class metric_kind_t : std::uint8_t {
118
+ unknown_k = 0,
119
+ // Classics:
120
+ ip_k = 'i',
121
+ cos_k = 'c',
122
+ l2sq_k = 'e',
123
+
124
+ // Custom:
125
+ pearson_k = 'p',
126
+ haversine_k = 'h',
127
+
128
+ // Sets:
129
+ jaccard_k = 'j',
130
+ hamming_k = 'b',
131
+ tanimoto_k = 't',
132
+ sorensen_k = 's',
133
+ };
134
+
135
+ enum class scalar_kind_t : std::uint8_t {
136
+ unknown_k = 0,
137
+ f64_k,
138
+ f32_k,
139
+ f16_k,
140
+ f8_k,
141
+ b1x8_k,
142
+ };
143
+
144
+ template <typename scalar_at> scalar_kind_t common_scalar_kind() noexcept {
145
+ if (std::is_same<scalar_at, f32_t>())
146
+ return scalar_kind_t::f32_k;
147
+ if (std::is_same<scalar_at, f64_t>())
148
+ return scalar_kind_t::f64_k;
149
+ if (std::is_same<scalar_at, b1x8_t>())
150
+ return scalar_kind_t::b1x8_k;
151
+ return scalar_kind_t::unknown_k;
152
+ }
115
153
 
116
154
  template <typename at> at angle_to_radians(at angle) noexcept { return angle * at(3.14159265358979323846) / at(180); }
117
155
 
118
156
  template <typename at> at square(at value) noexcept { return value * value; }
119
157
 
120
- template <std::size_t multiple_ak> inline std::size_t divide_round_up(std::size_t num) noexcept {
158
+ template <std::size_t multiple_ak> std::size_t divide_round_up(std::size_t num) noexcept {
121
159
  return (num + multiple_ak - 1) / multiple_ak;
122
160
  }
123
161
 
@@ -225,6 +263,8 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct ip_
225
263
  using result_t = result_at;
226
264
  using result_type = result_t;
227
265
 
266
+ inline metric_kind_t kind() const noexcept { return metric_kind_t::ip_k; }
267
+ inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
228
268
  inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
229
269
 
230
270
  inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t dim) const noexcept {
@@ -254,6 +294,8 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct cos
254
294
  using result_t = result_at;
255
295
  using result_type = result_t;
256
296
 
297
+ inline metric_kind_t kind() const noexcept { return metric_kind_t::cos_k; }
298
+ inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
257
299
  inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
258
300
 
259
301
  inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t dim) const noexcept {
@@ -283,6 +325,8 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct l2s
283
325
  using result_t = result_at;
284
326
  using result_type = result_t;
285
327
 
328
+ inline metric_kind_t kind() const noexcept { return metric_kind_t::l2sq_k; }
329
+ inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
286
330
  inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
287
331
 
288
332
  inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t dim) const noexcept {
@@ -312,6 +356,8 @@ template <typename scalar_at = std::uint64_t, typename result_at = std::size_t>
312
356
  using result_type = result_t;
313
357
  static_assert(std::is_unsigned<scalar_t>::value, "Hamming distance requires unsigned integral words");
314
358
 
359
+ inline metric_kind_t kind() const noexcept { return metric_kind_t::hamming_k; }
360
+ inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
315
361
  inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
316
362
 
317
363
  inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t words) const noexcept {
@@ -342,6 +388,8 @@ template <typename scalar_at = std::uint64_t, typename result_at = float> struct
342
388
  static_assert(std::is_unsigned<scalar_t>::value, "Tanimoto distance requires unsigned integral words");
343
389
  static_assert(std::is_floating_point<result_t>::value, "Tanimoto distance will be a fraction");
344
390
 
391
+ inline metric_kind_t kind() const noexcept { return metric_kind_t::tanimoto_k; }
392
+ inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
345
393
  inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
346
394
 
347
395
  inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t words) const noexcept {
@@ -374,6 +422,8 @@ template <typename scalar_at = std::uint64_t, typename result_at = float> struct
374
422
  static_assert(std::is_unsigned<scalar_t>::value, "Sorensen-Dice distance requires unsigned integral words");
375
423
  static_assert(std::is_floating_point<result_t>::value, "Sorensen-Dice distance will be a fraction");
376
424
 
425
+ inline metric_kind_t kind() const noexcept { return metric_kind_t::sorensen_k; }
426
+ inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
377
427
  inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
378
428
 
379
429
  inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t words) const noexcept {
@@ -407,6 +457,8 @@ template <typename scalar_at = std::int32_t, typename result_at = float> struct
407
457
  using result_type = result_t;
408
458
  static_assert(!std::is_floating_point<scalar_t>::value, "Jaccard distance requires integral scalars");
409
459
 
460
+ inline metric_kind_t kind() const noexcept { return metric_kind_t::jaccard_k; }
461
+ inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
410
462
  inline result_t operator()(view_t a, view_t b) const noexcept {
411
463
  return operator()(a.data(), b.data(), a.size(), b.size());
412
464
  }
@@ -436,6 +488,8 @@ template <typename scalar_at = float, typename result_at = float> struct pearson
436
488
  using result_t = result_at;
437
489
  using result_type = result_t;
438
490
 
491
+ inline metric_kind_t kind() const noexcept { return metric_kind_t::pearson_k; }
492
+ inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
439
493
  inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
440
494
 
441
495
  inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t dim) const noexcept {
@@ -472,6 +526,9 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct hav
472
526
  using result_type = result_t;
473
527
  static_assert(!std::is_integral<scalar_t>::value, "Latitude and longitude must be floating-node");
474
528
 
529
+ inline metric_kind_t kind() const noexcept { return metric_kind_t::haversine_k; }
530
+ inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
531
+
475
532
  inline result_t operator()(scalar_t const* a, scalar_t const* b) const noexcept {
476
533
  result_t lat_a = a[0], lon_a = a[1];
477
534
  result_t lat_b = b[0], lon_b = b[1];
@@ -964,9 +1021,9 @@ constexpr std::size_t default_allocator_entry_bytes() { return 64; }
964
1021
 
965
1022
  /**
966
1023
  * @brief The "magic" sequence helps infer the type of the file.
967
- * USearch indexes start with the "unumusearch" string.
1024
+ * USearch indexes start with the "usearch" string.
968
1025
  */
969
- constexpr char const* default_magic() { return "unumusearch"; }
1026
+ constexpr char const* default_magic() { return "usearch"; }
970
1027
 
971
1028
  /**
972
1029
  * @brief Configuration settings for the index construction.
@@ -1026,59 +1083,56 @@ struct search_config_t {
1026
1083
  bool exact = false;
1027
1084
  };
1028
1085
 
1029
- enum class metric_kind_t : std::uint8_t {
1030
- unknown_k = 0,
1031
- // Classics:
1032
- ip_k = 'i',
1033
- cos_k = 'c',
1034
- l2sq_k = 'e',
1035
-
1036
- // Custom:
1037
- pearson_k = 'p',
1038
- haversine_k = 'h',
1039
-
1040
- // Sets:
1041
- jaccard_k = 'j',
1042
- hamming_k = 'b',
1043
- tanimoto_k = 't',
1044
- sorensen_k = 's',
1045
- };
1046
-
1047
1086
  using file_header_t = byte_t[64];
1048
1087
 
1049
1088
  /**
1050
1089
  * @brief Serialized binary representations of the USearch index start with metadata.
1051
1090
  * Metadata is parsed into a `file_head_t`, containing the USearch package version,
1052
1091
  * and the properties of the index.
1092
+ *
1093
+ * It uses: 13 bytes for file versioning, 22 bytes for structural information = 35 bytes.
1094
+ * The following 24 bytes contain binary size of the graph, of the vectors, and the checksum,
1095
+ * leaving 5 bytes at the end vacant.
1053
1096
  */
1054
1097
  struct file_head_t {
1055
1098
 
1056
- using magic_t = char[11];
1099
+ // Versioning: 7 + 2 * 3 = 13 bytes
1100
+ using magic_t = char[7];
1057
1101
  using version_major_t = std::uint16_t;
1058
1102
  using version_minor_t = std::uint16_t;
1059
1103
  using version_patch_t = std::uint16_t;
1060
1104
 
1105
+ // Structural: 1 * 6 + 8 * 2 = 22 bytes
1061
1106
  using connectivity_t = std::uint8_t;
1062
1107
  using max_level_t = std::uint8_t;
1063
1108
  using vector_alignment_t = std::uint8_t;
1064
- using bytes_per_label_t = std::uint16_t;
1065
- using bytes_per_id_t = std::uint16_t;
1109
+ using bytes_per_label_t = std::uint8_t;
1110
+ using bytes_per_id_t = std::uint8_t;
1066
1111
  using size_t = std::uint64_t;
1067
1112
  using entry_idx_t = std::uint64_t;
1068
1113
 
1114
+ // Versioning:
1069
1115
  char const* magic;
1070
1116
  misaligned_ref_gt<version_major_t> version_major;
1071
1117
  misaligned_ref_gt<version_minor_t> version_minor;
1072
1118
  misaligned_ref_gt<version_patch_t> version_patch;
1119
+
1120
+ // Structural:
1073
1121
  misaligned_ref_gt<metric_kind_t> metric;
1074
1122
  misaligned_ref_gt<connectivity_t> connectivity;
1075
1123
  misaligned_ref_gt<max_level_t> max_level;
1076
1124
  misaligned_ref_gt<vector_alignment_t> vector_alignment;
1077
1125
  misaligned_ref_gt<bytes_per_label_t> bytes_per_label;
1078
1126
  misaligned_ref_gt<bytes_per_id_t> bytes_per_id;
1127
+ misaligned_ref_gt<scalar_kind_t> scalar_kind;
1079
1128
  misaligned_ref_gt<size_t> size;
1080
1129
  misaligned_ref_gt<entry_idx_t> entry_idx;
1081
1130
 
1131
+ // Additional:
1132
+ misaligned_ref_gt<size_t> bytes_for_graphs;
1133
+ misaligned_ref_gt<size_t> bytes_for_vectors;
1134
+ misaligned_ref_gt<size_t> bytes_checksum;
1135
+
1082
1136
  file_head_t(byte_t* ptr) noexcept
1083
1137
  : magic((char const*)exchange(ptr, ptr + sizeof(magic_t))),
1084
1138
  version_major(exchange(ptr, ptr + sizeof(version_major_t))),
@@ -1088,8 +1142,55 @@ struct file_head_t {
1088
1142
  max_level(exchange(ptr, ptr + sizeof(max_level_t))),
1089
1143
  vector_alignment(exchange(ptr, ptr + sizeof(vector_alignment_t))),
1090
1144
  bytes_per_label(exchange(ptr, ptr + sizeof(bytes_per_label_t))),
1091
- bytes_per_id(exchange(ptr, ptr + sizeof(bytes_per_id_t))), size(exchange(ptr, ptr + sizeof(size_t))),
1092
- entry_idx(exchange(ptr, ptr + sizeof(entry_idx_t))) {}
1145
+ bytes_per_id(exchange(ptr, ptr + sizeof(bytes_per_id_t))),
1146
+ scalar_kind(exchange(ptr, ptr + sizeof(scalar_kind_t))), size(exchange(ptr, ptr + sizeof(size_t))),
1147
+ entry_idx(exchange(ptr, ptr + sizeof(entry_idx_t))), bytes_for_graphs(exchange(ptr, ptr + sizeof(size_t))),
1148
+ bytes_for_vectors(exchange(ptr, ptr + sizeof(size_t))), bytes_checksum(exchange(ptr, ptr + sizeof(size_t))) {}
1149
+ };
1150
+
1151
+ struct file_head_result_t {
1152
+
1153
+ using magic_t = file_head_t::magic_t;
1154
+ using version_major_t = file_head_t::version_major_t;
1155
+ using version_minor_t = file_head_t::version_minor_t;
1156
+ using version_patch_t = file_head_t::version_patch_t;
1157
+
1158
+ using connectivity_t = file_head_t::connectivity_t;
1159
+ using max_level_t = file_head_t::max_level_t;
1160
+ using vector_alignment_t = file_head_t::vector_alignment_t;
1161
+ using bytes_per_label_t = file_head_t::bytes_per_label_t;
1162
+ using bytes_per_id_t = file_head_t::bytes_per_id_t;
1163
+ using size_t = file_head_t::size_t;
1164
+ using entry_idx_t = file_head_t::entry_idx_t;
1165
+
1166
+ // Versioning:
1167
+ version_major_t version_major;
1168
+ version_minor_t version_minor;
1169
+ version_patch_t version_patch;
1170
+
1171
+ // Structural:
1172
+ metric_kind_t metric;
1173
+ connectivity_t connectivity;
1174
+ max_level_t max_level;
1175
+ vector_alignment_t vector_alignment;
1176
+ bytes_per_label_t bytes_per_label;
1177
+ bytes_per_id_t bytes_per_id;
1178
+ scalar_kind_t scalar_kind;
1179
+ size_t size;
1180
+ entry_idx_t entry_idx;
1181
+
1182
+ // Additional:
1183
+ size_t bytes_for_graphs;
1184
+ size_t bytes_for_vectors;
1185
+ size_t bytes_checksum;
1186
+
1187
+ error_t error;
1188
+
1189
+ explicit operator bool() const noexcept { return !error; }
1190
+ file_head_result_t failed(char const* message) noexcept {
1191
+ error = message;
1192
+ return std::move(*this);
1193
+ }
1093
1194
  };
1094
1195
 
1095
1196
  static_assert(sizeof(file_header_t) == 64, "File header should be exactly 64 bytes");
@@ -1103,6 +1204,24 @@ using return_type_gt =
1103
1204
  typename std::result_of<metric_at(args_at...)>::type;
1104
1205
  #endif
1105
1206
 
1207
+ /// @brief OS-specific to wrap open file-descriptors/handles.
1208
+ #if defined(USEARCH_DEFINED_WINDOWS)
1209
+ struct viewed_file_t {
1210
+ HANDLE file_handle{};
1211
+ HANDLE mapping_handle{};
1212
+ void* ptr{};
1213
+ size_t length{};
1214
+ explicit operator bool() const noexcept { return mapping_handle != nullptr; }
1215
+ };
1216
+ #else
1217
+ struct viewed_file_t {
1218
+ int file_descriptor{};
1219
+ void* ptr{};
1220
+ size_t length{};
1221
+ explicit operator bool() const noexcept { return file_descriptor != 0; }
1222
+ };
1223
+ #endif
1224
+
1106
1225
  /**
1107
1226
  * @brief Approximate Nearest Neighbors Search index using the
1108
1227
  * Hierarchical Navigable Small World graph algorithm.
@@ -1168,18 +1287,17 @@ using return_type_gt =
1168
1287
  template <typename metric_at = ip_gt<float>, //
1169
1288
  typename label_at = std::size_t, //
1170
1289
  typename id_at = std::uint32_t, //
1171
- typename scalar_at = float, //
1172
1290
  typename allocator_at = std::allocator<char>, //
1173
1291
  typename point_allocator_at = allocator_at> //
1174
1292
  class index_gt {
1175
1293
  public:
1176
1294
  using metric_t = metric_at;
1177
- using scalar_t = scalar_at;
1178
1295
  using label_t = label_at;
1179
1296
  using id_t = id_at;
1180
1297
  using allocator_t = allocator_at;
1181
1298
  using point_allocator_t = point_allocator_at;
1182
1299
 
1300
+ using scalar_t = typename metric_t::scalar_t;
1183
1301
  using vector_view_t = span_gt<scalar_t const>;
1184
1302
  using distance_t = return_type_gt<metric_t, vector_view_t, vector_view_t>;
1185
1303
 
@@ -1390,7 +1508,7 @@ class index_gt {
1390
1508
  allocator_t allocator_{};
1391
1509
  point_allocator_t point_allocator_{};
1392
1510
  precomputed_constants_t pre_{};
1393
- int viewed_file_descriptor_{};
1511
+ viewed_file_t viewed_file_{};
1394
1512
 
1395
1513
  usearch_align_m mutable std::atomic<std::size_t> capacity_{};
1396
1514
  usearch_align_m mutable std::atomic<std::size_t> size_{};
@@ -1415,7 +1533,7 @@ class index_gt {
1415
1533
  std::size_t max_level() const noexcept { return static_cast<std::size_t>(max_level_); }
1416
1534
  index_config_t const& config() const noexcept { return config_; }
1417
1535
  index_limits_t const& limits() const noexcept { return limits_; }
1418
- bool is_immutable() const noexcept { return viewed_file_descriptor_ != 0; }
1536
+ bool is_immutable() const noexcept { return bool(viewed_file_); }
1419
1537
 
1420
1538
  /**
1421
1539
  * @section Exceptions
@@ -1424,8 +1542,8 @@ class index_gt {
1424
1542
  explicit index_gt(index_config_t config = {}, metric_t metric = {}, allocator_t allocator = {},
1425
1543
  point_allocator_t point_allocator = {}) noexcept
1426
1544
  : config_(config), limits_(0, 0), metric_(metric), allocator_(std::move(allocator)),
1427
- point_allocator_(std::move(point_allocator)), pre_(precompute_(config)), viewed_file_descriptor_(0),
1428
- size_(0u), max_level_(-1), entry_id_(0u), nodes_(nullptr), nodes_mutexes_(), contexts_(nullptr) {}
1545
+ point_allocator_(std::move(point_allocator)), pre_(precompute_(config)), size_(0u), max_level_(-1),
1546
+ entry_id_(0u), nodes_(nullptr), nodes_mutexes_(), contexts_(nullptr) {}
1429
1547
 
1430
1548
  /**
1431
1549
  * @brief Clones the structure with the same hyper-parameters, but without contents.
@@ -1476,6 +1594,7 @@ class index_gt {
1476
1594
  context_allocator_t{}.deallocate(exchange(contexts_, nullptr), limits_.threads());
1477
1595
  limits_ = index_limits_t{0, 0};
1478
1596
  capacity_ = 0;
1597
+ reset_view_();
1479
1598
  }
1480
1599
 
1481
1600
  /**
@@ -1487,7 +1606,7 @@ class index_gt {
1487
1606
  std::swap(metric_, other.metric_);
1488
1607
  std::swap(allocator_, other.allocator_);
1489
1608
  std::swap(pre_, other.pre_);
1490
- std::swap(viewed_file_descriptor_, other.viewed_file_descriptor_);
1609
+ std::swap(viewed_file_, other.viewed_file_);
1491
1610
  std::swap(max_level_, other.max_level_);
1492
1611
  std::swap(entry_id_, other.entry_id_);
1493
1612
  std::swap(nodes_, other.nodes_);
@@ -1854,7 +1973,7 @@ class index_gt {
1854
1973
  */
1855
1974
  std::size_t memory_usage(std::size_t allocator_entry_bytes = default_allocator_entry_bytes()) const noexcept {
1856
1975
  std::size_t total = 0;
1857
- if (viewed_file_descriptor_ == 0) {
1976
+ if (!viewed_file_) {
1858
1977
  stats_t s = stats();
1859
1978
  total += s.allocated_bytes;
1860
1979
  total += s.nodes * allocator_entry_bytes;
@@ -1895,17 +2014,23 @@ class index_gt {
1895
2014
  */
1896
2015
  serialization_result_t save(char const* file_path) const noexcept {
1897
2016
 
2017
+ // Make sure we have right to write to that file
1898
2018
  serialization_result_t result;
2019
+ std::FILE* file = std::fopen(file_path, "wb");
2020
+ if (!file)
2021
+ return result.failed(std::strerror(errno));
2022
+
2023
+ // Prepare the header with metadata
1899
2024
  file_header_t state_buffer{};
1900
2025
  std::memset(state_buffer, 0, sizeof(file_header_t));
1901
2026
  file_head_t state{state_buffer};
1902
2027
  std::memcpy(state_buffer, default_magic(), std::strlen(default_magic()));
1903
2028
 
1904
- // Check compatibility
2029
+ // Mark compatibility
1905
2030
  state.version_major = USEARCH_VERSION_MAJOR;
1906
2031
  state.version_minor = USEARCH_VERSION_MINOR;
1907
2032
  state.version_patch = USEARCH_VERSION_PATCH;
1908
- state.metric = metric_kind_t::unknown_k;
2033
+ state.metric = metric_.kind();
1909
2034
 
1910
2035
  // Describe state
1911
2036
  state.connectivity = config_.connectivity;
@@ -1913,13 +2038,25 @@ class index_gt {
1913
2038
  state.vector_alignment = config_.vector_alignment;
1914
2039
  state.bytes_per_label = sizeof(label_t);
1915
2040
  state.bytes_per_id = sizeof(id_t);
2041
+ state.scalar_kind = metric_.scalar_kind();
1916
2042
  state.size = size_;
1917
2043
  state.entry_idx = entry_id_;
1918
2044
 
1919
- std::FILE* file = std::fopen(file_path, "wb");
1920
- if (!file)
1921
- return result.failed(std::strerror(errno));
2045
+ // Augment with metadata
2046
+ std::size_t graphs_bytes = 0;
2047
+ std::size_t vectors_bytes = 0;
2048
+ for (std::size_t i = 0; i != state.size; ++i) {
2049
+ node_t node = node_with_id_(i);
2050
+ std::size_t node_bytes = node_bytes_(node);
2051
+ std::size_t node_vector_bytes = node_vector_bytes_(node);
2052
+ graphs_bytes += node_bytes - node_vector_bytes;
2053
+ vectors_bytes += node_vector_bytes;
2054
+ }
2055
+ state.bytes_for_graphs = graphs_bytes;
2056
+ state.bytes_for_vectors = vectors_bytes;
2057
+ state.bytes_checksum = 0;
1922
2058
 
2059
+ // Perform serialization
1923
2060
  auto write_chunk = [&](void* begin, std::size_t length) {
1924
2061
  std::size_t written = std::fwrite(begin, length, 1, file);
1925
2062
  if (!written) {
@@ -2027,7 +2164,7 @@ class index_gt {
2027
2164
  }
2028
2165
 
2029
2166
  std::fclose(file);
2030
- viewed_file_descriptor_ = 0;
2167
+ reset_view_();
2031
2168
  return {};
2032
2169
  }
2033
2170
 
@@ -2038,14 +2175,38 @@ class index_gt {
2038
2175
  */
2039
2176
  serialization_result_t view(char const* file_path) noexcept {
2040
2177
  serialization_result_t result;
2178
+
2041
2179
  #if defined(USEARCH_DEFINED_WINDOWS)
2042
- return result.failed("Memory-mapping is not yet available for Windows");
2180
+
2181
+ HANDLE file_handle =
2182
+ CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
2183
+ if (file_handle == INVALID_HANDLE_VALUE)
2184
+ return result.failed("Opening file failed!");
2185
+
2186
+ size_t file_length = GetFileSize(file_handle, 0);
2187
+ HANDLE mapping_handle = CreateFileMapping(file_handle, 0, PAGE_READONLY, 0, 0, 0);
2188
+ if (mapping_handle == 0) {
2189
+ CloseHandle(file_handle);
2190
+ return result.failed("Mapping file failed!");
2191
+ }
2192
+
2193
+ byte_t* file = (byte_t*)MapViewOfFile(mapping_handle, FILE_MAP_READ, 0, 0, file_length);
2194
+ if (file == 0) {
2195
+ CloseHandle(mapping_handle);
2196
+ CloseHandle(file_handle);
2197
+ return result.failed("View the map failed!");
2198
+ }
2199
+ viewed_file_.file_handle = file_handle;
2200
+ viewed_file_.mapping_handle = mapping_handle;
2201
+ viewed_file_.ptr = file;
2202
+ viewed_file_.length = file_length;
2203
+ #else
2204
+
2205
+ #if defined(USEARCH_DEFINED_LINUX)
2206
+ int descriptor = open(file_path, O_RDONLY | O_NOATIME);
2043
2207
  #else
2044
- int open_flags = O_RDONLY;
2045
- #if __linux__
2046
- open_flags |= O_NOATIME;
2208
+ int descriptor = open(file_path, O_RDONLY);
2047
2209
  #endif
2048
- int descriptor = open(file_path, open_flags);
2049
2210
 
2050
2211
  // Estimate the file size
2051
2212
  struct stat file_stat;
@@ -2061,16 +2222,20 @@ class index_gt {
2061
2222
  close(descriptor);
2062
2223
  return result.failed(std::strerror(errno));
2063
2224
  }
2225
+ viewed_file_.file_descriptor = descriptor;
2226
+ viewed_file_.ptr = file;
2227
+ viewed_file_.length = file_stat.st_size;
2228
+ #endif // Platform specific code
2064
2229
 
2065
2230
  // Read the header
2066
2231
  {
2067
2232
  file_head_t state{file};
2068
2233
  if (state.bytes_per_label != sizeof(label_t)) {
2069
- close(descriptor);
2234
+ reset_view_();
2070
2235
  return result.failed("Incompatible label type!");
2071
2236
  }
2072
2237
  if (state.bytes_per_id != sizeof(id_t)) {
2073
- close(descriptor);
2238
+ reset_view_();
2074
2239
  return result.failed("Incompatible ID type!");
2075
2240
  }
2076
2241
 
@@ -2103,14 +2268,27 @@ class index_gt {
2103
2268
  max_level_ = (std::max)(max_level_, level);
2104
2269
  }
2105
2270
 
2106
- viewed_file_descriptor_ = descriptor;
2107
2271
  return {};
2108
- #endif // POSIX
2109
2272
  }
2110
2273
 
2111
2274
  #pragma endregion
2112
2275
 
2113
2276
  private:
2277
+ void reset_view_() noexcept {
2278
+ if (!viewed_file_)
2279
+ return;
2280
+ #if defined(USEARCH_DEFINED_WINDOWS)
2281
+ UnmapViewOfFile(viewed_file_.ptr);
2282
+ CloseHandle(viewed_file_.mapping_handle);
2283
+ CloseHandle(viewed_file_.file_handle);
2284
+
2285
+ #else
2286
+ munmap(viewed_file_.ptr, viewed_file_.length);
2287
+ close(viewed_file_.file_descriptor);
2288
+ #endif
2289
+ viewed_file_ = {};
2290
+ }
2291
+
2114
2292
  inline static precomputed_constants_t precompute_(index_config_t const& config) noexcept {
2115
2293
  precomputed_constants_t pre;
2116
2294
  pre.connectivity_max_base = config.connectivity * base_level_multiple_();
@@ -2135,7 +2313,7 @@ class index_gt {
2135
2313
 
2136
2314
  void node_free_(std::size_t id) noexcept {
2137
2315
 
2138
- if (viewed_file_descriptor_ != 0)
2316
+ if (viewed_file_)
2139
2317
  return;
2140
2318
 
2141
2319
  node_t& node = nodes_[id];
@@ -2444,6 +2622,47 @@ class index_gt {
2444
2622
  }
2445
2623
  };
2446
2624
 
2625
+ /**
2626
+ * @brief Extracts metadata from pre-constructed index on disk, without loading it
2627
+ * or mapping the whole binary file.
2628
+ */
2629
+ inline file_head_result_t index_metadata(char const* file_path) noexcept {
2630
+
2631
+ file_head_result_t result;
2632
+ file_header_t state_buffer{};
2633
+ std::FILE* file = std::fopen(file_path, "rb");
2634
+ if (!file)
2635
+ return result.failed(std::strerror(errno));
2636
+
2637
+ // Read the header
2638
+ std::size_t read = std::fread(&state_buffer[0], sizeof(file_header_t), 1, file);
2639
+ std::fclose(file);
2640
+ if (!read)
2641
+ return result.failed(std::strerror(errno));
2642
+
2643
+ // Parse and validate the MIME type
2644
+ file_head_t state{state_buffer};
2645
+ if (std::strncmp(state.magic, default_magic(), std::strlen(default_magic())) != 0)
2646
+ return result.failed("Wrong MIME type!");
2647
+
2648
+ result.version_major = state.version_major;
2649
+ result.version_minor = state.version_minor;
2650
+ result.version_patch = state.version_patch;
2651
+ result.metric = state.metric;
2652
+ result.connectivity = state.connectivity;
2653
+ result.max_level = state.max_level;
2654
+ result.vector_alignment = state.vector_alignment;
2655
+ result.bytes_per_label = state.bytes_per_label;
2656
+ result.bytes_per_id = state.bytes_per_id;
2657
+ result.scalar_kind = state.scalar_kind;
2658
+ result.size = state.size;
2659
+ result.entry_idx = state.entry_idx;
2660
+ result.bytes_for_graphs = state.bytes_for_graphs;
2661
+ result.bytes_for_vectors = state.bytes_for_vectors;
2662
+ result.bytes_checksum = state.bytes_checksum;
2663
+ return result;
2664
+ }
2665
+
2447
2666
  } // namespace usearch
2448
2667
  } // namespace unum
2449
2668