91#include <sycl/sycl.hpp>
115constexpr int64_t kHashWgSize = 1024;
117constexpr int64_t kHashBucketCountMultiplier = 2;
120inline uint64_t PackSlot(uint32_t state,
buf_index_t bi, uint32_t fingerprint) {
121 return (
static_cast<uint64_t
>(fingerprint) << 36) |
122 (
static_cast<uint64_t
>(state) << 32) |
static_cast<uint64_t
>(bi);
125inline void UnpackSlot(uint64_t packed,
128 uint32_t& fingerprint) {
129 bi =
static_cast<buf_index_t>(packed & 0xffffffffULL);
130 state =
static_cast<uint32_t
>((packed >> 32) & 0xfULL);
131 fingerprint =
static_cast<uint32_t
>(packed >> 36);
135inline int64_t NextPowerOfTwo(int64_t n) {
136 if (n <= 0)
return 1;
150inline uint64_t HashMix(uint64_t h) {
152 h *= 0xff51afd7ed558ccdULL;
154 h *= 0xc4ceb9fe1a85ec53ULL;
162template <
typename Key,
typename Hash,
typename Eq>
173 const uint64_t hash = HashMix(
hash_fn(key));
174 const int64_t home =
static_cast<int64_t
>(hash & mask);
175 const uint32_t my_fingerprint =
176 static_cast<uint32_t
>((hash >> 16) & 0xfffffffULL);
178 for (int64_t probe = 0; probe <
bucket_count; ++probe) {
179 const int64_t idx = (home + probe) & mask;
184 UnpackSlot(packed, s, bi, fp);
189 const Key* slot_key =
191 if (
eq_fn(*slot_key, key)) {
201template <
typename Key,
typename Hash,
typename Eq>
206 const std::vector<int64_t>& value_dsizes,
208 int64_t wg_size = kHashWgSize);
214 void Insert(
const void* input_keys,
215 const std::vector<const void*>& input_values_soa,
218 int64_t
count)
override;
220 void Find(
const void* input_keys,
223 int64_t
count)
override;
225 void Erase(
const void* input_keys,
227 int64_t
count)
override;
231 void Clear()
override;
233 int64_t
Size()
const override;
240 void Allocate(int64_t capacity)
override;
241 void Free()
override;
264template <
typename Key,
typename Hash,
typename Eq>
266 int64_t init_capacity,
268 const std::vector<int64_t>& value_dsizes,
273 queue_(sy::SYCLContext::GetInstance().GetDefaultQueue(device)) {
274 const int64_t device_max_wg_size =
static_cast<int64_t
>(
276 .get_info<sycl::info::device::max_work_group_size>());
281template <
typename Key,
typename Hash,
typename Eq>
286template <
typename Key,
typename Hash,
typename Eq>
288 if (!occupied_count_) {
294 return static_cast<int64_t
>(
count);
297template <
typename Key,
typename Hash,
typename Eq>
299 if (!non_empty_count_) {
305 return static_cast<int64_t
>(
count);
308template <
typename Key,
typename Hash,
typename Eq>
310 return bucket_count_;
313template <
typename Key,
typename Hash,
typename Eq>
315 utility::LogError(
"Unimplemented");
318template <
typename Key,
typename Hash,
typename Eq>
320 return float(Size()) / float(bucket_count_);
323template <
typename Key,
typename Hash,
typename Eq>
325 const void* input_keys,
326 const std::vector<const void*>& input_values_soa,
330 if (
count == 0)
return;
332 const Key* keys =
static_cast<const Key*
>(input_keys);
333 const int n_values =
static_cast<int>(input_values_soa.size());
337 "SYCL hashmap supports up to 16 value arrays, but got {}.",
343 const void* ptrs[16];
345 for (
int i = 0; i < n_values; ++i) {
346 values_soa.ptrs[i] = input_values_soa[i];
362 const int prev_heap_top = this->buffer_->GetHeapTopIndex();
364 const int new_heap_top = prev_heap_top +
static_cast<int>(
count);
365 queue_.memcpy(buffer_accessor_.heap_top_, &new_heap_top,
sizeof(
int))
370 uint64_t* slot_data = slot_data_;
371 const int64_t bucket_count = bucket_count_;
372 const int64_t capacity = accessor.
capacity_;
373 int* occupied_count = occupied_count_;
374 int* non_empty_count = non_empty_count_;
375 constexpr int kMaxOuterIter = 1 << 20;
379 const int64_t common_block_size = buffer_accessor_.common_block_size_;
381 auto insert_kernel = [=](sycl::nd_item<1>
382 item) [[intel::kernel_args_restrict]] {
383 const int64_t tid = item.get_global_id(0);
384 int my_new_occupied = 0;
385 int my_new_nonempty = 0;
388 sycl::atomic_fence(sycl::memory_order::seq_cst,
389 sycl::memory_scope::device);
391 const Key key = keys[tid];
392 output_buf_indices[tid] = 0;
393 output_masks[tid] =
false;
395 const int64_t mask = bucket_count - 1;
396 const uint64_t hash = HashMix(hash_fn(key));
397 const int64_t home =
static_cast<int64_t
>(hash & mask);
398 const uint32_t my_fingerprint =
399 static_cast<uint32_t
>((hash >> 16) & 0xfffffffULL);
405 const int64_t reserved_index =
406 static_cast<int64_t
>(prev_heap_top) + tid;
408 (reserved_index < capacity)
409 ? accessor.
heap_[reserved_index]
411 bool key_published =
false;
419 Key* slot_key =
static_cast<Key*
>(accessor.
GetKeyPtr(my_bi));
422 for (
int j = 0; j < n_values; ++j) {
423 const int64_t blocks =
425 DISPATCH_DIVISOR_SIZE_TO_BLOCK_T_SYCL(
426 common_block_size, [&]() {
427 using val_block_t = block_t;
429 reinterpret_cast<val_block_t*
>(
431 const val_block_t* src =
432 reinterpret_cast<const val_block_t*
>(
433 values_soa.ptrs[j]) +
435 for (int64_t b = 0; b < blocks; ++b) {
441 sycl::atomic_fence(sycl::memory_order::seq_cst,
442 sycl::memory_scope::device);
443 key_published =
true;
446 bool finished =
false;
449 if (++outer_iter > kMaxOuterIter) {
452 int64_t first_deleted = -1;
453 bool restart =
false;
455 for (int64_t probe = 0; probe < bucket_count; ++probe) {
456 const int64_t idx = (home + probe) & mask;
457 sycl::atomic_ref<uint64_t, sycl::memory_order::relaxed,
458 sycl::memory_scope::device>
461 uint64_t packed = st.load(sycl::memory_order::acquire);
465 UnpackSlot(packed, s, bi, fp);
468 if (fp == my_fingerprint) {
469 sycl::atomic_fence(sycl::memory_order::seq_cst,
470 sycl::memory_scope::device);
471 const Key* slot_key =
static_cast<const Key*
>(
473 if (eq_fn(*slot_key, key)) {
474 output_buf_indices[tid] = bi;
475 output_masks[tid] =
false;
484 if (first_deleted < 0) first_deleted = idx;
488 if (!key_published) {
495 (first_deleted >= 0) ? first_deleted : idx;
496 sycl::atomic_ref<uint64_t, sycl::memory_order::relaxed,
497 sycl::memory_scope::device>
499 uint64_t expected_packed =
501 ? tst.load(sycl::memory_order::acquire)
503 const uint32_t prev_state =
static_cast<uint32_t
>(
504 (expected_packed >> 32) & 0xfULL);
507 tst.compare_exchange_strong(
510 sycl::memory_order::acq_rel,
511 sycl::memory_order::relaxed)) {
512 output_buf_indices[tid] = my_bi;
513 output_masks[tid] =
true;
526 if (finished || !restart) {
539 if (key_published && !output_masks[tid]) {
544 const int wg_occupied = sycl::reduce_over_group(
545 item.get_group(), my_new_occupied, sycl::plus<int>{});
546 const int wg_nonempty = sycl::reduce_over_group(
547 item.get_group(), my_new_nonempty, sycl::plus<int>{});
548 if (item.get_local_id(0) == 0) {
549 if (wg_occupied > 0) {
550 sycl::atomic_ref<int, sycl::memory_order::relaxed,
551 sycl::memory_scope::device>
553 oc.fetch_add(wg_occupied);
555 if (wg_nonempty > 0) {
556 sycl::atomic_ref<int, sycl::memory_order::relaxed,
557 sycl::memory_scope::device>
558 nec(*non_empty_count);
559 nec.fetch_add(wg_nonempty);
564 const int64_t wg_size = wg_size_;
565 const int64_t global_size = ((
count + wg_size - 1) / wg_size) * wg_size;
566 queue_.submit([&](sycl::handler& cgh) {
567 cgh.parallel_for(sycl::nd_range<1>(global_size, wg_size),
572template <
typename Key,
typename Hash,
typename Eq>
577 if (
count == 0)
return;
579 const Key* keys =
static_cast<const Key*
>(input_keys);
581 uint64_t* slot_data = slot_data_;
582 const int64_t bucket_count = bucket_count_;
587 [=](sycl::nd_item<1> item) [[intel::kernel_args_restrict]] {
588 const int64_t tid = item.get_global_id(0);
589 if (tid >=
count)
return;
590 const Key key = keys[tid];
591 const int64_t mask = bucket_count - 1;
592 const uint64_t hash = HashMix(hash_fn(key));
593 const int64_t home =
static_cast<int64_t
>(hash & mask);
594 const uint32_t my_fingerprint =
595 static_cast<uint32_t
>((hash >> 16) & 0xfffffffULL);
599 for (int64_t probe = 0; probe < bucket_count; ++probe) {
600 const int64_t idx = (home + probe) & mask;
601 sycl::atomic_ref<uint64_t, sycl::memory_order::relaxed,
602 sycl::memory_scope::device>
604 uint64_t packed = st.load(sycl::memory_order::acquire);
608 UnpackSlot(packed, s, bi, fp);
614 const Key* slot_key =
615 static_cast<const Key*
>(accessor.
GetKeyPtr(bi));
616 if (eq_fn(*slot_key, key)) {
623 output_masks[tid] = found;
624 output_buf_indices[tid] = found ?
result : 0;
627 const int64_t wg_size = wg_size_;
628 const int64_t global_size = ((
count + wg_size - 1) / wg_size) * wg_size;
629 queue_.submit([&](sycl::handler& cgh) {
630 cgh.parallel_for(sycl::nd_range<1>(global_size, wg_size),
635template <
typename Key,
typename Hash,
typename Eq>
639 if (
count == 0)
return;
641 const Key* keys =
static_cast<const Key*
>(input_keys);
643 uint64_t* slot_data = slot_data_;
644 const int64_t bucket_count = bucket_count_;
645 int* occupied_count = occupied_count_;
649 auto erase_kernel = [=](sycl::nd_item<1>
650 item) [[intel::kernel_args_restrict]] {
651 const int64_t tid = item.get_global_id(0);
652 if (tid >=
count)
return;
653 const Key key = keys[tid];
654 const int64_t mask = bucket_count - 1;
655 const uint64_t hash = HashMix(hash_fn(key));
656 const int64_t home =
static_cast<int64_t
>(hash & mask);
657 const uint32_t my_fingerprint =
658 static_cast<uint32_t
>((hash >> 16) & 0xfffffffULL);
661 for (int64_t probe = 0; probe < bucket_count; ++probe) {
662 const int64_t idx = (home + probe) & mask;
663 sycl::atomic_ref<uint64_t, sycl::memory_order::relaxed,
664 sycl::memory_scope::device>
666 uint64_t packed = st.load(sycl::memory_order::acquire);
670 UnpackSlot(packed, s, bi, fp);
676 const Key* slot_key =
677 static_cast<const Key*
>(accessor.
GetKeyPtr(bi));
678 if (eq_fn(*slot_key, key)) {
679 uint64_t expected_packed = packed;
681 if (st.compare_exchange_strong(
682 expected_packed, deleted_val,
683 sycl::memory_order::acq_rel,
684 sycl::memory_order::relaxed)) {
687 sycl::atomic_ref<int, sycl::memory_order::relaxed,
688 sycl::memory_scope::device>
696 output_masks[tid] = erased;
699 const int64_t wg_size = wg_size_;
700 const int64_t global_size = ((
count + wg_size - 1) / wg_size) * wg_size;
701 queue_.submit([&](sycl::handler& cgh) {
702 cgh.parallel_for(sycl::nd_range<1>(global_size, wg_size),
707template <
typename Key,
typename Hash,
typename Eq>
710 uint64_t* slot_data = slot_data_;
711 const int64_t bucket_count = bucket_count_;
713 int* d_count =
static_cast<int*
>(
715 queue_.memset(d_count, 0,
sizeof(
int)).wait_and_throw();
717 const int64_t kWgSize = wg_size_;
719 auto scan_kernel = [=](sycl::nd_item<1> item) {
720 int64_t idx = item.get_global_id(0);
721 auto group = item.get_group();
723 bool is_occupied =
false;
725 if (idx < bucket_count) {
726 const uint64_t packed = slot_data[idx];
727 uint32_t s =
static_cast<uint32_t
>((packed >> 32) & 0xfULL);
730 bi =
static_cast<buf_index_t>(packed & 0xffffffffULL);
734 int local_val = is_occupied ? 1 : 0;
735 int local_offset = sycl::exclusive_scan_over_group(group, local_val,
738 sycl::reduce_over_group(group, local_val, sycl::plus<int>{});
741 if (item.get_local_id(0) == 0 && group_total > 0) {
742 sycl::atomic_ref<int, sycl::memory_order::relaxed,
743 sycl::memory_scope::device>
745 group_start = counter.fetch_add(group_total);
747 group_start = sycl::group_broadcast(group, group_start, 0);
750 output_indices[group_start + local_offset] = bi;
754 int64_t global_size = ((bucket_count + kWgSize - 1) / kWgSize) * kWgSize;
755 queue_.submit([&](sycl::handler& cgh) {
756 cgh.parallel_for(sycl::nd_range<1>(global_size, kWgSize),
763 return static_cast<int64_t
>(
count);
766template <
typename Key,
typename Hash,
typename Eq>
768 this->buffer_->ResetHeap();
769 queue_.memset(slot_data_, 0, bucket_count_ *
sizeof(uint64_t))
771 if (occupied_count_) {
772 queue_.memset(occupied_count_, 0,
sizeof(
int)).wait_and_throw();
774 if (non_empty_count_) {
775 queue_.memset(non_empty_count_, 0,
sizeof(
int)).wait_and_throw();
779template <
typename Key,
typename Hash,
typename Eq>
781 this->capacity_ = capacity;
782 bucket_count_ = NextPowerOfTwo(
783 std::max<int64_t>(capacity * kHashBucketCountMultiplier, 1));
785 this->buffer_ = std::make_shared<HashBackendBuffer>(
786 this->capacity_, this->key_dsize_, this->value_dsizes_,
788 buffer_accessor_.Setup(*this->buffer_);
791 bucket_count_ *
sizeof(uint64_t), this->device_));
792 queue_.memset(slot_data_, 0, bucket_count_ *
sizeof(uint64_t))
795 occupied_count_ =
static_cast<int*
>(
797 queue_.memset(occupied_count_, 0,
sizeof(
int)).wait_and_throw();
799 non_empty_count_ =
static_cast<int*
>(
801 queue_.memset(non_empty_count_, 0,
sizeof(
int)).wait_and_throw();
804template <
typename Key,
typename Hash,
typename Eq>
806 buffer_accessor_.Shutdown(this->device_);
809 slot_data_ =
nullptr;
811 if (occupied_count_) {
813 occupied_count_ =
nullptr;
815 if (non_empty_count_) {
817 non_empty_count_ =
nullptr;
BitmapEventQueue * queue_
Definition BitmapWindowSystem.cpp:53
Vectorized trivial-object copy block sizes (CUDA and SYCL).
SYCL device properties and (when built) queue manager.
Device-side accessor for SYCL hash-map key/value buffers.
Real target
Definition SurfaceReconstructionPoisson.cpp:267
core::Tensor result
Definition VtkUtils.cpp:76
Definition DeviceHashBackend.h:20
static void MemcpyToHost(void *host_ptr, const void *src_ptr, const Device &src_device, size_t num_bytes)
Same as Memcpy, but with host (CPU:0) as default dst_device.
Definition MemoryManager.cpp:85
static void * Malloc(size_t byte_size, const Device &device)
Definition MemoryManager.cpp:22
static void Free(void *ptr, const Device &device)
Frees previously allocated memory at address ptr on device device.
Definition MemoryManager.cpp:28
Definition SYCLHashBackendBufferAccessor.h:33
void * GetKeyPtr(buf_index_t buf_index) const
Device: USM pointer to the key at buf_index.
Definition SYCLHashBackendBufferAccessor.h:135
void * GetValuePtr(buf_index_t buf_index, int value_idx=0) const
Device: USM pointer to value value_idx at buf_index.
Definition SYCLHashBackendBufferAccessor.h:139
int64_t * value_blocks_per_element_
Blocks per value for vector copy.
Definition SYCLHashBackendBufferAccessor.h:156
static constexpr buf_index_t kInvalidBufIndex
Definition SYCLHashBackendBufferAccessor.h:35
int64_t capacity_
Definition SYCLHashBackendBufferAccessor.h:158
void DeviceFree(buf_index_t buf_index) const
Definition SYCLHashBackendBufferAccessor.h:124
buf_index_t * heap_
Free-slot index stack, length capacity_.
Definition SYCLHashBackendBufferAccessor.h:144
DeviceHashBackend for SYCL devices (algorithm in file header).
Definition SYCLHashBackend.h:202
SYCLHashDeviceLookup< Key, Hash, Eq > GetDeviceLookup() const
Snapshot for device kernels; table must not be mutated while in use.
Definition SYCLHashBackend.h:244
SYCLHashBackend(int64_t init_capacity, int64_t key_dsize, const std::vector< int64_t > &value_dsizes, const Device &device, int64_t wg_size=kHashWgSize)
Definition SYCLHashBackend.h:265
~SYCLHashBackend()
Definition SYCLHashBackend.h:282
void Find(const void *input_keys, buf_index_t *output_buf_indices, bool *output_masks, int64_t count) override
Parallel find a contiguous array of keys.
Definition SYCLHashBackend.h:573
void Erase(const void *input_keys, bool *output_masks, int64_t count) override
Parallel erase a contiguous array of keys.
Definition SYCLHashBackend.h:636
int64_t GetNonEmptyCount() const override
Occupied + deleted slots (rehash guard; see file header).
Definition SYCLHashBackend.h:298
float LoadFactor() const override
Get the current load factor, defined as size / bucket count.
Definition SYCLHashBackend.h:319
void Clear() override
Clear stored map without reallocating memory.
Definition SYCLHashBackend.h:767
int64_t wg_size_
SYCL work-group size for kernels.
Definition SYCLHashBackend.h:259
std::vector< int64_t > BucketSizes() const override
Get the number of entries per bucket.
Definition SYCLHashBackend.h:314
void Free() override
Definition SYCLHashBackend.h:805
sycl::queue queue_
Definition SYCLHashBackend.h:261
void Reserve(int64_t capacity) override
No-op; use HashMap::Reserve for capacity growth.
Definition SYCLHashBackend.h:212
int * occupied_count_
Device live entry count.
Definition SYCLHashBackend.h:256
int64_t bucket_count_
Definition SYCLHashBackend.h:258
void Insert(const void *input_keys, const std::vector< const void * > &input_values_soa, buf_index_t *output_buf_indices, bool *output_masks, int64_t count) override
Parallel insert contiguous arrays of keys and values.
Definition SYCLHashBackend.h:324
SYCLHashBackendBufferAccessor buffer_accessor_
Definition SYCLHashBackend.h:253
int64_t GetActiveIndices(buf_index_t *output_indices) override
Parallel collect all iterators in the hash table.
Definition SYCLHashBackend.h:708
int64_t Size() const override
Get the size (number of valid entries) of the hash map.
Definition SYCLHashBackend.h:287
int64_t GetBucketCount() const override
Get the number of buckets of the hash map.
Definition SYCLHashBackend.h:309
int * non_empty_count_
Device occupied + tombstone count.
Definition SYCLHashBackend.h:257
void Allocate(int64_t capacity) override
Definition SYCLHashBackend.h:780
uint64_t * slot_data_
USM packed slots.
Definition SYCLHashBackend.h:255
uint32_t buf_index_t
Definition HashBackendBuffer.h:49
HashSlotState
Definition SYCLHashBackend.h:107
@ kSlotOccupied
Definition SYCLHashBackend.h:109
@ kSlotDeleted
Definition SYCLHashBackend.h:110
@ kSlotEmpty
Definition SYCLHashBackend.h:108
const char const char value recording_handle imu_sample recording_handle uint8_t size_t data_size k4a_record_configuration_t config target_format k4a_capture_t capture_handle k4a_imu_sample_t imu_sample uint64_t
Definition K4aPlugin.cpp:343
Definition PinholeCameraIntrinsic.cpp:16
Read-only table view for device kernels (see file header).
Definition SYCLHashBackend.h:163
uint64_t * slot_data
USM packed slot array.
Definition SYCLHashBackend.h:164
Hash hash_fn
Key hash functor.
Definition SYCLHashBackend.h:167
Eq eq_fn
Key equality functor.
Definition SYCLHashBackend.h:168
int64_t bucket_count
Power-of-two bucket count.
Definition SYCLHashBackend.h:165
SYCLHashBackendBufferAccessor accessor
Key/value buffer accessor.
Definition SYCLHashBackend.h:166
buf_index_t Find(const Key &key) const
Linear-probe lookup; returns buffer index or -1 if not found.
Definition SYCLHashBackend.h:171