-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.json
More file actions
1 lines (1 loc) · 328 KB
/
index.json
File metadata and controls
1 lines (1 loc) · 328 KB
1
[{"content":" still draft, to be updated\n实现了一个dynamic_map的retrieve_all 访存算子,经典过滤问题,predict为true的元素拷贝到out数组,重点是需要维护一个 atomic 的out index, https://developer.nvidia.com/blog/cuda-pro-tip-optimized-filtering-warp-aggregated-atomics/, 翻译:https://zhuanlan.zhihu.com/p/581078557\nhost端:\ntemplate \u0026lt;typename Key, typename Value, cuda::thread_scope Scope, typename Allocator\u0026gt; template \u0026lt;typename KeyOut, typename ValueOut\u0026gt; std::pair\u0026lt;KeyOut, ValueOut\u0026gt; dynamic_map\u0026lt;Key, Value, Scope, Allocator\u0026gt;::retrieve_all( KeyOut keys_out, ValueOut values_out, cudaStream_t stream) const { auto constexpr block_size = 128; auto constexpr stride = 1; auto const capacity = get_capacity(); auto grid_size = (capacity + stride * block_size - 1) / (stride * block_size); std::vector\u0026lt;size_t\u0026gt; submap_cap_prefix(submaps_.size()); std::inclusive_scan(submaps_.begin(), submaps_.end(), submap_cap_prefix.begin(), [](auto const\u0026amp; sum, auto const\u0026amp; submap) { return sum + submap-\u0026gt;get_capacity(); }, (size_t)0); thrust::device_vector\u0026lt;size_t\u0026gt; submap_cap_prefix_d(submap_cap_prefix); // 复用alloc_(用于slots_的alloc_)会比直接cudaMalloc快一个数量级,不需要重新分配内存 // 单纯cudaMalloc会触发GPU driver/runtime 的 allocation 初始化、页表建立等 using temp_allocator_type = typename std::allocator_traits\u0026lt;Allocator\u0026gt;::template rebind_alloc\u0026lt;char\u0026gt;; auto temp_allocator = temp_allocator_type{alloc_}; auto d_num_out = reinterpret_cast\u0026lt;unsigned long long*\u0026gt;( std::allocator_traits\u0026lt;temp_allocator_type\u0026gt;::allocate(temp_allocator, sizeof(unsigned long long))); CUCO_CUDA_TRY(cudaMemsetAsync(d_num_out, 0, sizeof(unsigned long long), stream)); detail::retrieve_all\u0026lt;block_size\u0026gt;\u0026lt;\u0026lt;\u0026lt;grid_size, block_size, 0, stream\u0026gt;\u0026gt;\u0026gt;( keys_out, values_out, submap_views_.data().get(), submaps_.size(), capacity, d_num_out, submap_cap_prefix_d.data().get(), empty_key_sentinel_, erased_key_sentinel_); size_t h_num_out; CUCO_CUDA_TRY( cudaMemcpyAsync(\u0026amp;h_num_out, d_num_out, sizeof(size_t), cudaMemcpyDeviceToHost, stream)); CUCO_CUDA_TRY(cudaStreamSynchronize(stream)); CUCO_CUDA_TRY(cudaFree(d_num_out)) return {keys_out + h_num_out, values_out + h_num_out}; } naive实现,一个全局atomic template \u0026lt;uint32_t block_size, typename OutputIt, typename viewT, typename PrefixT, typename Key\u0026gt; CUCO_KERNEL void retrieve_all(OutputIt keys_out, OutputIt values_out, viewT* submap_views, uint32_t num_submaps, uint64_t capacity, unsigned long long* d_num_out, PrefixT* prefix_sum, Key empty_key_sentinel, Key erased_key_sentinel) { auto tid = blockDim.x * blockIdx.x + threadIdx.x; auto stride = blockDim.x * gridDim.x; for (; tid \u0026lt; capacity; tid += stride) { uint32_t submap_idx = 0; uint32_t submap_offset = tid; // prefix_sum长度一般就10以内,不需要二分之类的操作 while (tid \u0026gt;= prefix_sum[submap_idx] \u0026amp;\u0026amp; submap_idx \u0026lt; num_submaps ) ++submap_idx; if (submap_idx \u0026gt; 0) { submap_offset = tid - prefix_sum[submap_idx - 1]; } auto const \u0026amp;current_slot = submap_views[submap_idx].get_slots()[submap_offset]; Key const existing_key = current_slot.first.load(cuda::std::memory_order_relaxed); auto const is_filled = not(cuco::detail::bitwise_compare(existing_key, empty_key_sentinel) or cuco::detail::bitwise_compare(existing_key, erased_key_sentinel)); if (is_filled) { auto idx = atomicAdd(d_num_out, static_cast\u0026lt;unsigned long long\u0026gt;(1)); auto value = current_slot.second.load(cuda::std::memory_order_relaxed); keys_out[idx] = existing_key; values_out[idx] = value; } } } block内atomicAdd + 全局atomicAdd // 一个block内用一个__shared__ local_count表示这个block中predict为true的数量 // local_pos表示当前线程在该block内第几个predict为true template \u0026lt;uint32_t block_size, typename OutputIt, typename viewT, typename PrefixT, typename Key\u0026gt; CUCO_KERNEL void retrieve_all(OutputIt keys_out, OutputIt values_out, viewT* submap_views, uint32_t num_submaps, uint64_t capacity, unsigned long long* d_num_out, PrefixT* prefix_sum, Key empty_key_sentinel, Key erased_key_sentinel) { auto tid = blockDim.x * blockIdx.x + threadIdx.x; auto stride = blockDim.x * gridDim.x; __shared__ unsigned int local_count; if (threadIdx.x == 0) { local_count = 0; } __syncthreads(); for (; tid \u0026lt; capacity; tid += stride) { uint32_t submap_idx = 0; uint32_t submap_offset = tid; while (tid \u0026gt;= prefix_sum[submap_idx] \u0026amp;\u0026amp; submap_idx \u0026lt; num_submaps ) ++submap_idx; if (submap_idx \u0026gt; 0) { submap_offset = tid - prefix_sum[submap_idx - 1]; } auto const \u0026amp;current_slot = submap_views[submap_idx].get_slots()[submap_offset]; Key const existing_key = current_slot.first.load(cuda::std::memory_order_relaxed); auto const is_filled = not(cuco::detail::bitwise_compare(existing_key, empty_key_sentinel) or cuco::detail::bitwise_compare(existing_key, erased_key_sentinel)); unsigned int local_pos = 0; if (is_filled) { local_pos = atomicAdd_block(\u0026amp;local_count, 1); } __syncthreads(); if (threadIdx.x == 0) { local_count = atomicAdd(d_num_out, local_count); } __syncthreads(); if (is_filled) { auto value = current_slot.second.load(cuda::std::memory_order_relaxed); keys_out[local_count + local_pos] = existing_key; values_out[local_count + local_pos] = value; } } } // 类似原理,但是用cub::BlockScan实现 template \u0026lt;uint32_t block_size, typename OutputIt, typename viewT, typename PrefixT, typename Key\u0026gt; CUCO_KERNEL void retrieve_all(OutputIt keys_out, OutputIt values_out, viewT* submap_views, uint32_t num_submaps, uint64_t capacity, unsigned long long* d_num_out, PrefixT* prefix_sum, Key empty_key_sentinel, Key erased_key_sentinel) { using BlockScan = cub::BlockScan\u0026lt;unsigned int, block_size\u0026gt;; // Shared memory __shared__ typename BlockScan::TempStorage scan_temp_storage; __shared__ unsigned int block_base; auto tid = blockDim.x * blockIdx.x + threadIdx.x; auto stride = blockDim.x * gridDim.x; for (; tid \u0026lt; capacity; tid += stride) { // Compute submap index and offset uint32_t submap_idx = 0; uint32_t submap_offset = tid; while (tid \u0026gt;= prefix_sum[submap_idx] \u0026amp;\u0026amp; submap_idx \u0026lt; num_submaps) ++submap_idx; if (submap_idx \u0026gt; 0) { submap_offset = tid - prefix_sum[submap_idx - 1]; } auto const\u0026amp; current_slot = submap_views[submap_idx].get_slots()[submap_offset]; Key const existing_key = current_slot.first.load(cuda::std::memory_order_relaxed); // Check key validity bool is_filled = not(cuco::detail::bitwise_compare(existing_key, empty_key_sentinel) || cuco::detail::bitwise_compare(existing_key, erased_key_sentinel)); // Perform block-wide exclusive scan to compute local write index unsigned int local_idx = 0; unsigned int total_valid = 0; BlockScan(scan_temp_storage).ExclusiveSum(is_filled ? 1u : 0u, local_idx, total_valid); // Block leader calculates global offset if (threadIdx.x == 0) { block_base = atomicAdd(d_num_out, total_valid); } __syncthreads(); if (is_filled) { auto value = current_slot.second.load(cuda::std::memory_order_relaxed); keys_out[block_base + local_idx] = existing_key; values_out[block_base + local_idx] = value; } } } 3. warp-aggregated atomics: warp(or cooperative group)粒度atomicAdd + block内atomicAdd+全局atomicAdd ```cpp template \u0026lt;uint32_t block_size, typename OutputIt, typename viewT, typename PrefixT, typename Key\u0026gt; CUCO_KERNEL void retrieve_all(OutputIt keys_out, OutputIt values_out, viewT* submap_views, uint32_t num_submaps, uint64_t capacity, unsigned long long* d_num_out, PrefixT* prefix_sum, Key empty_key_sentinel, Key erased_key_sentinel) { auto tid = blockDim.x * blockIdx.x + threadIdx.x; auto stride = blockDim.x * gridDim.x; __shared__ unsigned int block_count; __shared__ unsigned int block_base; if (threadIdx.x == 0) { block_count = 0; block_base = 0; } __syncthreads(); unsigned int local_idx = 0; for (; tid \u0026lt; capacity; tid += stride) { uint32_t submap_idx = 0; uint32_t submap_offset = tid; while (tid \u0026gt;= prefix_sum[submap_idx] \u0026amp;\u0026amp; submap_idx \u0026lt; num_submaps ) ++submap_idx; if (submap_idx \u0026gt; 0) { submap_offset = tid - prefix_sum[submap_idx - 1]; } auto const \u0026amp;current_slot = submap_views[submap_idx].get_slots()[submap_offset]; Key const existing_key = current_slot.first.load(cuda::std::memory_order_relaxed); auto const is_filled = not(cuco::detail::bitwise_compare(existing_key, empty_key_sentinel) or cuco::detail::bitwise_compare(existing_key, erased_key_sentinel)); unsigned mask = __ballot_sync(0xffffffff, is_filled); int lane = threadIdx.x \u0026amp; 0x1f; int warp_prefix = __popc(mask \u0026amp; ((1u \u0026lt;\u0026lt; lane) - 1)); if (is_filled) local_idx = warp_prefix; unsigned int warp_vote = __popc(mask); unsigned int warp_base = 0; if (lane == 0 \u0026amp;\u0026amp; warp_vote) { warp_base = atomicAdd_block(\u0026amp;block_count, warp_vote); } warp_base = __shfl_sync(0xffffffff, warp_base, 0); __syncthreads(); if (threadIdx.x == 0) { block_base = atomicAdd(d_num_out, block_count); } __syncthreads(); if (is_filled) { auto value = current_slot.second.load(cuda::std::memory_order_relaxed); keys_out[block_base + warp_base + local_idx] = existing_key; values_out[block_base + warp_base + local_idx] = value; } } } // 类似的,但是用cooperative group实现,实测还是tile_size=32最快,和warp没区别 // 写起来更modern一点 template \u0026lt;uint32_t block_size, uint32_t tile_size, typename OutputIt, typename viewT, typename PrefixT, typename Key\u0026gt; CUCO_KERNEL void retrieve_all(OutputIt keys_out, OutputIt values_out, viewT* submap_views, uint32_t num_submaps, uint64_t capacity, unsigned long long* d_num_out, PrefixT* prefix_sum, Key empty_key_sentinel, Key erased_key_sentinel) { auto tile = cg::tiled_partition\u0026lt;tile_size\u0026gt;(cg::this_thread_block()); auto block = cg::this_thread_block(); auto tid = blockDim.x * blockIdx.x + threadIdx.x; auto stride = blockDim.x * gridDim.x; __shared__ unsigned int block_count; __shared__ unsigned int block_base; if (threadIdx.x == 0) { block_count = 0; block_base = 0; } block.sync(); for (; tid \u0026lt; capacity; tid += stride) { uint32_t submap_idx = 0; uint32_t submap_offset = tid; while (tid \u0026gt;= prefix_sum[submap_idx] \u0026amp;\u0026amp; submap_idx \u0026lt; num_submaps ) ++submap_idx; if (submap_idx \u0026gt; 0) { submap_offset = tid - prefix_sum[submap_idx - 1]; } auto const \u0026amp;current_slot = submap_views[submap_idx].get_slots()[submap_offset]; Key const existing_key = current_slot.first.load(cuda::std::memory_order_relaxed); auto const is_filled = not(cuco::detail::bitwise_compare(existing_key, empty_key_sentinel) or cuco::detail::bitwise_compare(existing_key, erased_key_sentinel)); unsigned int tile_mask = tile.ballot(is_filled); unsigned int tile_rank = tile.thread_rank(); unsigned int tile_vote = __popc(tile_mask); unsigned int tile_prefix = __popc(tile_mask \u0026amp; ((1u \u0026lt;\u0026lt; tile_rank) - 1)); unsigned int tile_base = 0; if (tile_rank == 0 \u0026amp;\u0026amp; tile_mask) { tile_base = atomicAdd_block(\u0026amp;block_count, tile_vote); } tile_base = tile.shfl(tile_base, 0); block.sync(); if (block.thread_rank() == 0) { block_base = atomicAdd(d_num_out, block_count); } block.sync(); if (is_filled) { auto value = current_slot.second.load(cuda::std::memory_order_relaxed); keys_out[block_base + tile_base + tile_prefix] = existing_key; values_out[block_base + tile_base + tile_prefix] = value; } } } 实测2/3速度差不多,在插入1亿数据后(实际总cap达到2亿),\u0026lt;key, value\u0026gt;都是cuda::atomic\u0026lt;int64_t\u0026gt;的情况下,retrieve_all cost 3ms左右;\n","permalink":"https://caaatch22.github.io/posts/cuco/","summary":"\u003cblockquote\u003e\n\u003cp\u003estill draft, to be updated\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"实现了一个dynamic_map的retrieve_all\"\u003e实现了一个dynamic_map的retrieve_all\u003c/h3\u003e\n\u003cblockquote\u003e\n\u003cp\u003e访存算子,经典过滤问题,predict为true的元素拷贝到out数组,重点是需要维护一个 atomic 的out index, \u003ca href=\"https://developer.nvidia.com/blog/cuda-pro-tip-optimized-filtering-warp-aggregated-atomics/\"\u003ehttps://developer.nvidia.com/blog/cuda-pro-tip-optimized-filtering-warp-aggregated-atomics/\u003c/a\u003e, 翻译:https://zhuanlan.zhihu.com/p/581078557\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003ehost端:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etemplate\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e Key, \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e Value, cuda\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ethread_scope Scope, \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e Allocator\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etemplate\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e KeyOut, \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e ValueOut\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003epair\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eKeyOut, ValueOut\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e dynamic_map\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eKey, Value, Scope, Allocator\u003cspan style=\"color:#f92672\"\u003e\u0026gt;::\u003c/span\u003eretrieve_all(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e KeyOut keys_out, ValueOut values_out, cudaStream_t stream) \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econstexpr\u003c/span\u003e block_size \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e128\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econstexpr\u003c/span\u003e stride \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e capacity \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e get_capacity();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e grid_size \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (capacity \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e stride \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e block_size \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e) \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e (stride \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e block_size);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003evector\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003esize_t\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e submap_cap_prefix(submaps_.size());\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003einclusive_scan(submaps_.begin(), submaps_.end(), submap_cap_prefix.begin(),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e [](\u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e sum, \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e submap) { \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e sum \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e submap\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eget_capacity(); }, \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e (size_t)\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e thrust\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003edevice_vector\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003esize_t\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e submap_cap_prefix_d(submap_cap_prefix);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// 复用alloc_(用于slots_的alloc_)会比直接cudaMalloc快一个数量级,不需要重新分配内存\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e// 单纯cudaMalloc会触发GPU driver/runtime 的 allocation 初始化、页表建立等\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eusing\u003c/span\u003e temp_allocator_type \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eallocator_traits\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eAllocator\u003cspan style=\"color:#f92672\"\u003e\u0026gt;::\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etemplate\u003c/span\u003e rebind_alloc\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003echar\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e temp_allocator \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e temp_allocator_type{alloc_};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e d_num_out \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ereinterpret_cast\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eallocator_traits\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003etemp_allocator_type\u003cspan style=\"color:#f92672\"\u003e\u0026gt;::\u003c/span\u003eallocate(temp_allocator, \u003cspan style=\"color:#66d9ef\"\u003esizeof\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e)));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CUCO_CUDA_TRY(cudaMemsetAsync(d_num_out, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003esizeof\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e), stream));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e detail\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eretrieve_all\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eblock_size\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u0026lt;\u0026lt;\u0026lt;\u003c/span\u003egrid_size, block_size, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e, stream\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u0026gt;\u0026gt;\u003c/span\u003e(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e keys_out, values_out, submap_views_.data().get(), submaps_.size(), \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e capacity, d_num_out, submap_cap_prefix_d.data().get(), \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e empty_key_sentinel_, erased_key_sentinel_);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e size_t h_num_out;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CUCO_CUDA_TRY(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e cudaMemcpyAsync(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003eh_num_out, d_num_out, \u003cspan style=\"color:#66d9ef\"\u003esizeof\u003c/span\u003e(size_t), cudaMemcpyDeviceToHost, stream));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CUCO_CUDA_TRY(cudaStreamSynchronize(stream));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e CUCO_CUDA_TRY(cudaFree(d_num_out))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e {keys_out \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e h_num_out, values_out \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e h_num_out};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003col\u003e\n\u003cli\u003enaive实现,一个全局atomic\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etemplate\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e block_size,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e OutputIt,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e viewT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e PrefixT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e Key\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCUCO_KERNEL \u003cspan style=\"color:#66d9ef\"\u003evoid\u003c/span\u003e retrieve_all(OutputIt keys_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e OutputIt values_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e viewT\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e submap_views,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e num_submaps,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint64_t\u003c/span\u003e capacity,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e d_num_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e PrefixT\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e prefix_sum,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key empty_key_sentinel,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key erased_key_sentinel)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e tid \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e blockDim.x \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e blockIdx.x \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e threadIdx.x;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e stride \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e blockDim.x \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e gridDim.x;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e (; tid \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e capacity; tid \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e stride) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e submap_idx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e submap_offset \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e tid;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// prefix_sum长度一般就10以内,不需要二分之类的操作\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e (tid \u003cspan style=\"color:#f92672\"\u003e\u0026gt;=\u003c/span\u003e prefix_sum[submap_idx] \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e submap_idx \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e num_submaps ) \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003esubmap_idx;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (submap_idx \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e submap_offset \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e tid \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e prefix_sum[submap_idx \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003ecurrent_slot \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e submap_views[submap_idx].get_slots()[submap_offset];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e existing_key \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e current_slot.first.load(cuda\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ememory_order_relaxed);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e is_filled \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e not(cuco\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003edetail\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ebitwise_compare(existing_key, empty_key_sentinel) or\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e cuco\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003edetail\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ebitwise_compare(existing_key, erased_key_sentinel));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (is_filled) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e idx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e atomicAdd(d_num_out, \u003cspan style=\"color:#66d9ef\"\u003estatic_cast\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e value \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e current_slot.second.load(cuda\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ememory_order_relaxed);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e keys_out[idx] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e existing_key;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e values_out[idx] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e value;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003col start=\"2\"\u003e\n\u003cli\u003eblock内atomicAdd + 全局atomicAdd\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 一个block内用一个__shared__ local_count表示这个block中predict为true的数量\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// local_pos表示当前线程在该block内第几个predict为true\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etemplate\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e block_size,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e OutputIt,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e viewT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e PrefixT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e Key\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCUCO_KERNEL \u003cspan style=\"color:#66d9ef\"\u003evoid\u003c/span\u003e retrieve_all(OutputIt keys_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e OutputIt values_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e viewT\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e submap_views,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e num_submaps,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint64_t\u003c/span\u003e capacity,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e d_num_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e PrefixT\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e prefix_sum,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key empty_key_sentinel,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key erased_key_sentinel)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e tid \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e blockDim.x \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e blockIdx.x \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e threadIdx.x;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e stride \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e blockDim.x \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e gridDim.x;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e __shared__ \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e local_count;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (threadIdx.x \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e local_count \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e __syncthreads();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e (; tid \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e capacity; tid \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e stride) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e submap_idx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e submap_offset \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e tid;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e (tid \u003cspan style=\"color:#f92672\"\u003e\u0026gt;=\u003c/span\u003e prefix_sum[submap_idx] \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e submap_idx \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e num_submaps ) \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003esubmap_idx;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (submap_idx \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e submap_offset \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e tid \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e prefix_sum[submap_idx \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003ecurrent_slot \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e submap_views[submap_idx].get_slots()[submap_offset];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e existing_key \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e current_slot.first.load(cuda\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ememory_order_relaxed);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e is_filled \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e not(cuco\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003edetail\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ebitwise_compare(existing_key, empty_key_sentinel) or\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e cuco\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003edetail\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ebitwise_compare(existing_key, erased_key_sentinel));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e local_pos \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (is_filled) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e local_pos \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e atomicAdd_block(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003elocal_count, \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e __syncthreads();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (threadIdx.x \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e local_count \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e atomicAdd(d_num_out, local_count);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e __syncthreads();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (is_filled) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e value \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e current_slot.second.load(cuda\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ememory_order_relaxed);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e keys_out[local_count \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e local_pos] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e existing_key;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e values_out[local_count \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e local_pos] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e value;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 类似原理,但是用cub::BlockScan实现\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etemplate\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e block_size,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e OutputIt,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e viewT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e PrefixT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e Key\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCUCO_KERNEL \u003cspan style=\"color:#66d9ef\"\u003evoid\u003c/span\u003e retrieve_all(OutputIt keys_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e OutputIt values_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e viewT\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e submap_views,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e num_submaps,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint64_t\u003c/span\u003e capacity,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e d_num_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e PrefixT\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e prefix_sum,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key empty_key_sentinel,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key erased_key_sentinel)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eusing\u003c/span\u003e BlockScan \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e cub\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eBlockScan\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e, block_size\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Shared memory\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e __shared__ \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e BlockScan\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eTempStorage scan_temp_storage;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e __shared__ \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e block_base;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e tid \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e blockDim.x \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e blockIdx.x \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e threadIdx.x;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e stride \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e blockDim.x \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e gridDim.x;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e (; tid \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e capacity; tid \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e stride) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Compute submap index and offset\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e submap_idx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e submap_offset \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e tid;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e (tid \u003cspan style=\"color:#f92672\"\u003e\u0026gt;=\u003c/span\u003e prefix_sum[submap_idx] \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e submap_idx \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e num_submaps) \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003esubmap_idx;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (submap_idx \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e submap_offset \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e tid \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e prefix_sum[submap_idx \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e current_slot \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e submap_views[submap_idx].get_slots()[submap_offset];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e existing_key \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e current_slot.first.load(cuda\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ememory_order_relaxed);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Check key validity\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ebool\u003c/span\u003e is_filled \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e not(cuco\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003edetail\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ebitwise_compare(existing_key, empty_key_sentinel) \u003cspan style=\"color:#f92672\"\u003e||\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e cuco\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003edetail\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ebitwise_compare(existing_key, erased_key_sentinel));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Perform block-wide exclusive scan to compute local write index\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e local_idx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e total_valid \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e BlockScan(scan_temp_storage).ExclusiveSum(is_filled \u003cspan style=\"color:#f92672\"\u003e?\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1u\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0u\u003c/span\u003e, local_idx, total_valid);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// Block leader calculates global offset\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (threadIdx.x \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e block_base \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e atomicAdd(d_num_out, total_valid);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e __syncthreads();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (is_filled) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e value \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e current_slot.second.load(cuda\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ememory_order_relaxed);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e keys_out[block_base \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e local_idx] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e existing_key;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e values_out[block_base \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e local_idx] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e value;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#ae81ff\"\u003e3.\u003c/span\u003e warp\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eaggregated atomics: warp(or cooperative group)\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e粒度\u003c/span\u003eatomicAdd \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e block内atomicAdd\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e全局\u003c/span\u003eatomicAdd\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e```\u003c/span\u003ecpp\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etemplate\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e block_size,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e OutputIt,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e viewT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e PrefixT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e Key\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCUCO_KERNEL \u003cspan style=\"color:#66d9ef\"\u003evoid\u003c/span\u003e retrieve_all(OutputIt keys_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e OutputIt values_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e viewT\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e submap_views,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e num_submaps,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint64_t\u003c/span\u003e capacity,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e d_num_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e PrefixT\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e prefix_sum,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key empty_key_sentinel,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key erased_key_sentinel)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e tid \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e blockDim.x \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e blockIdx.x \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e threadIdx.x;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e stride \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e blockDim.x \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e gridDim.x;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e __shared__ \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e block_count;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e __shared__ \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e block_base;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (threadIdx.x \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e block_count \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e block_base \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e __syncthreads();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e local_idx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e (; tid \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e capacity; tid \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e stride) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e submap_idx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e submap_offset \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e tid;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e (tid \u003cspan style=\"color:#f92672\"\u003e\u0026gt;=\u003c/span\u003e prefix_sum[submap_idx] \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e submap_idx \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e num_submaps ) \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003esubmap_idx;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (submap_idx \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e submap_offset \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e tid \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e prefix_sum[submap_idx \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003ecurrent_slot \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e submap_views[submap_idx].get_slots()[submap_offset];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e existing_key \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e current_slot.first.load(cuda\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ememory_order_relaxed);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e is_filled \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e not(cuco\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003edetail\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ebitwise_compare(existing_key, empty_key_sentinel) or\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e cuco\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003edetail\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ebitwise_compare(existing_key, erased_key_sentinel));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e mask \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e __ballot_sync(\u003cspan style=\"color:#ae81ff\"\u003e0xffffffff\u003c/span\u003e, is_filled);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e lane \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e threadIdx.x \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0x1f\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e warp_prefix \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e __popc(mask \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e ((\u003cspan style=\"color:#ae81ff\"\u003e1u\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e lane) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (is_filled) local_idx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e warp_prefix;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e warp_vote \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e __popc(mask);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e warp_base \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (lane \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e warp_vote) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e warp_base \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e atomicAdd_block(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003eblock_count, warp_vote);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e warp_base \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e __shfl_sync(\u003cspan style=\"color:#ae81ff\"\u003e0xffffffff\u003c/span\u003e, warp_base, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e __syncthreads();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (threadIdx.x \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e block_base \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e atomicAdd(d_num_out, block_count);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e __syncthreads();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (is_filled) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e value \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e current_slot.second.load(cuda\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ememory_order_relaxed);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e keys_out[block_base \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e warp_base \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e local_idx] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e existing_key;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e values_out[block_base \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e warp_base \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e local_idx] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e value;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 类似的,但是用cooperative group实现,实测还是tile_size=32最快,和warp没区别\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 写起来更modern一点\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etemplate\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e block_size,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e tile_size,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e OutputIt,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e viewT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e PrefixT,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e Key\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eCUCO_KERNEL \u003cspan style=\"color:#66d9ef\"\u003evoid\u003c/span\u003e retrieve_all(OutputIt keys_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e OutputIt values_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e viewT\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e submap_views,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e num_submaps,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint64_t\u003c/span\u003e capacity,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003elong\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e d_num_out,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e PrefixT\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e prefix_sum,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key empty_key_sentinel,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key erased_key_sentinel)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e tile \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e cg\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003etiled_partition\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003etile_size\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e(cg\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ethis_thread_block());\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e block \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e cg\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ethis_thread_block();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e tid \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e blockDim.x \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e blockIdx.x \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e threadIdx.x;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e stride \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e blockDim.x \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e gridDim.x;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e __shared__ \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e block_count;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e __shared__ \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e block_base;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (threadIdx.x \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e block_count \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e block_base \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e block.sync();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e (; tid \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e capacity; tid \u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e stride) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e submap_idx \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003euint32_t\u003c/span\u003e submap_offset \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e tid;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ewhile\u003c/span\u003e (tid \u003cspan style=\"color:#f92672\"\u003e\u0026gt;=\u003c/span\u003e prefix_sum[submap_idx] \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e submap_idx \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e num_submaps ) \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003esubmap_idx;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (submap_idx \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e submap_offset \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e tid \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e prefix_sum[submap_idx \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003ecurrent_slot \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e submap_views[submap_idx].get_slots()[submap_offset];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Key \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e existing_key \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e current_slot.first.load(cuda\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ememory_order_relaxed);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e is_filled \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e not(cuco\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003edetail\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ebitwise_compare(existing_key, empty_key_sentinel) or\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e cuco\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003edetail\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ebitwise_compare(existing_key, erased_key_sentinel));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e tile_mask \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e tile.ballot(is_filled);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e tile_rank \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e tile.thread_rank();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e tile_vote \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e __popc(tile_mask);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e tile_prefix \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e __popc(tile_mask \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e ((\u003cspan style=\"color:#ae81ff\"\u003e1u\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e tile_rank) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eunsigned\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e tile_base \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (tile_rank \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e tile_mask) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e tile_base \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e atomicAdd_block(\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003eblock_count, tile_vote);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e tile_base \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e tile.shfl(tile_base, \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e block.sync();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (block.thread_rank() \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e block_base \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e atomicAdd(d_num_out, block_count);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e block.sync();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (is_filled) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e value \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e current_slot.second.load(cuda\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003ememory_order_relaxed);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e keys_out[block_base \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e tile_base \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e tile_prefix] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e existing_key;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e values_out[block_base \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e tile_base \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e tile_prefix] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e value;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e实测2/3速度差不多,在插入1亿数据后(实际总cap达到2亿),\u003ccode\u003e\u0026lt;key, value\u0026gt;\u003c/code\u003e都是\u003ccode\u003ecuda::atomic\u0026lt;int64_t\u0026gt;\u003c/code\u003e的情况下,retrieve_all cost 3ms左右;\u003c/p\u003e","title":"cuCollections"},{"content":"Overview 模型量化(quantization)指的是用更少的bit表示模型参数,从而减少模型的大小,加速推理过程的技术。\n一种常见的量化方式是线性量化(linear quantization),也叫仿射量化(affine quantization)。其实就是按比例将tensor(一般为fp32)放缩到 $2^{bitwidth}$ 的范围内,比如8bit等。我们很容易给出量化公式: $$ r = s(q - z) $$ 其中,r(real value)值得是量化前的值,q(quantized value)是量化后的值,s(scale)是放缩比例,z(zero point)相当于是一个偏移量。\n如何求出$s$和$z$呢?一种简单且常见的方式是通过最大最小值来估计,即:\n$$ s = \\frac{r_{max} - r_{min}}{q_{max} - q_{min}} $$ $r_{max}$就是这个tensor的最大值,$r_{min}$是最小值,$q_{max}$和$q_{min}$是我们指定的量化后的最大最小值。如下图所示: 有了scale, 容易得到 $z = q_{min} - \\frac{r_{min}}{s}$。在实际操作中,z一般会被round到最近的整数$z = round(q_{min} - \\frac{r_{min}}{s})$(有很多不同的round规则,这个有具体实现决定)。\n得到量化方程: $$ q = clip(round(\\frac{r}{s}) + z, q_{min}, q_{max}) $$\n代码示意如下:(实际会用pytorch已有的quantize api或者其他推理框架)\ndef get_quantized_range(bitwidth): quantized_max = (1 \u0026lt;\u0026lt; (bitwidth - 1)) - 1 quantized_min = -(1 \u0026lt;\u0026lt; (bitwidth - 1)) return quantized_min, quantized_max def linear_quantize(fp_tensor, bitwidth, scale, zero_point, dtype=torch.int8) -\u0026gt; torch.Tensor: rounded_tensor = torch.round(fp_tensor / scale).to(dtype) shifted_tensor = rounded_tensor + zero_point quantized_min, quantized_max = get_quantized_range(bitwidth) quantized_tensor = shifted_tensor.clamp_(quantized_min, quantized_max) return quantized_tensor 上述过程被称为非对称量化(asymmetric quantization)。\n还有一种对称量化(symmetric quantization),它基于以下事实:常见的训练好的模型参数几乎总是关于0对称的。如下图所示:\n基于这个观察,我们常将zero point设置为0,并让 $q_{min} = -q_{max}$ 。这样,我们就可以简化量化公式为: $$ s = \\frac{r_{max}}{q_{max}},\\ \\ z = 0 \\ q = clip(round(\\frac{r}{s}), -q_{max}, q_{max}), q_{max} = 2^{bitwidth - 1} - 1 $$ 这也是TensorRT等框架中常用的量化方式。\n进一步的,当我们进行推理的过程中,对于一个全连接层,设$Y = WX$(暂不考虑bias),对左右两边都进行量化得到:\n$$ \\begin{align*} S_{Y}(q_{Y} - z_{Y}) \u0026amp;= S_{W}(q_{W} - z_{W})\\cdot S_{X}(q_{X} - z_{x}) \\newline q_{Y} \u0026amp;= \\frac{S_{W}S_{X}}{S_{Y}}(q_{W} - z_{W})(q_{x} - z_{x}) + z_{Y} \\newline q_{Y} \u0026amp;= \\frac{S_{W}S_{X}}{S_{Y}}(q_{W}q_{x} - q_{W}z_{x} - z_{W}q_{x} + z_{W}z_{x}) + z_{Y} \\newline q_{Y} \u0026amp;= \\frac{S_{W}S_{X}}{S_{Y}}(q_{W}q_{X} - z_{X}q_{W}) + z_{Y}\\ \\ (assume\\ z_{W} = 0) \\newline \\end{align*} $$\n类似的,有bias的时候可以得到: $$ q_{Y} = \\frac{S_{W}S_{X}}{S_{Y}}(q_{W}q_{X} - Q_{bias}) + z_{Y} \\ \\ \\ (Q_{bias} = q_{b} - z_{X}q_{W}) $$ 根据卷积的线性可加性,我们也能有类似的结论: 推理过程和全连接层类似,也可参见tinyml课程的lab2作业。\n上式中,和 $W, b$ 相关的量化参数都可以容易被提前计算(训练好后对整个网络进行量化,记录所有weight, bias的量化值)。但对于$q_{X}, z_{X}$这种和输入相关的值,理论上来说是需要真正在设备上做推理的时候在能知道。但是如果我们每次做推理来一个input我们都对其做一个量化,这个开销是没法接受的。所以人们会用一个cablibration dataset,在做推理之前,在这个数据集上对所有的输入进行量化并得到一些量化参数,比如,cablibration dataset上的最大最小值,scale, zero point, 以及$S_{Y}$等(具体过程见下文中的PTQ和QAT),以此当作推理时input的量化参数。\n上述式子还存在两个问题\n$\\frac{S_{W}S_{X}}{S_{Y}}$是一个浮点数,我们不能让它参与计算。根据经验,这个结果一般都在(0, 1)之间,所以可以表示成$2^{-n}M_{0}$, 其中$M_{0}$是一个整数,$n$是一个非负整数。这样我们就可以记录两个整数来代替浮点数。可以参见gemmlowp的实现 $q_{W} \\cdot q_{X}$ 是很可能溢出 8bit的,所以其结果一般也会用32bit int表示(具体可能有不同的实现),所以我们一般也先将$Q_{bias}$量化为32bit int,便于与其结果相乘。最后需要对结果在量化为8bit int。 其他量化方法 minmax量化($s = \\frac{r_{max}-r_{min}}{q_{max} - q_{min}}$)有一个问题,也就是它容易受outlier的点的影响,这对模型参数的量化其实影响还好,因为参数的分布基本是对称的。但是对activations的结果就不一样了,所以又衍生出了几种量化方式。还记得上文说过,为了在模型真正部署之前得到对input, activations的量化参数,我们会在一个cablibration dataset进行训练。在这个过程中,我们可以通过取平均或者*指数移动平均(exponential moving averages)*的方式获取 $r_{max}, r_{min}$ ,从而减少outlier的影响。 $$ \\hat{r}^{(t)}{max, min} = \\alpha \\cdot r^{(t)}{max, min} + (1 - \\alpha) \\cdot \\hat{r}^{(t-1)}_{max, min} $$\nTensorRT对activations的量化其实也是通过minmax方法,但是这个minmax是在一定阈值之内的minmax。 那么如何确定这个阈值呢?我们肯定希望最小化量化前后数据分布的信息损失,这也是KL散度的思想。操作过程如下:\n对于模型的每一层:\n计算activations的直方图 选取多种threshold计算梁化后的分布与原分布的KL散度 选取最小KL散度对应的threshold 整个过程在典型的工作负载下大概需要几分钟。 一些选取的threshold结果如下: 量化粒度 我们可以对每个tensor进行量化,也即对每个tensor都有一个scale和zero point。但人们发现,同一个tensor的不同channel的分布是很不一样的: 所以一个更细粒度的选择是进行per-channel量化,即对每个channel都有一个scale和zero point。\nPost-training quantization(PTQ) PTQ的过程比较简单,就是在训练后对模型进行量化。在这个过程前,我们还会用cablibration dataset来估计一些量化参数。流程如下: 简单的pytorch代码如下:\nclass QuantizedSimpleNet(nn.Module): def __init__(self, hidden_size_1=100, hidden_size_2=100): super(QuantizedSimpleNet,self).__init__() self.quant = torch.quantization.QuantStub() self.linear1 = nn.Linear(28*28, hidden_size_1) self.linear2 = nn.Linear(hidden_size_1, hidden_size_2) self.linear3 = nn.Linear(hidden_size_2, 10) self.relu = nn.ReLU() self.dequant = torch.quantization.DeQuantStub() def forward(self, img): x = img.view(-1, 28*28) x = self.quant(x) x = self.relu(self.linear1(x)) x = self.relu(self.linear2(x)) x = self.linear3(x) x = self.dequant(x) return x net_quantized = QuantizedSimpleNet().to(device) # Copy weights from unquantized model net_quantized.load_state_dict(net.state_dict()) net_quantized.eval() net_quantized.qconfig = torch.ao.quantization.default_qconfig net_quantized = torch.ao.quantization.prepare(net_quantized) # Insert observers print(net_quantized) # QuantizedVerySimpleNet( # (quant): QuantStub( # (activation_post_process): MinMaxObserver(min_val=inf, max_val=-inf) # ) # (linear1): Linear( # in_features=784, out_features=100, bias=True # (activation_post_process): MinMaxObserver(min_val=inf, max_val=-inf) # ) # (linear2): Linear( # in_features=100, out_features=100, bias=True # (activation_post_process): MinMaxObserver(min_val=inf, max_val=-inf) # ) # (linear3): Linear( # in_features=100, out_features=10, bias=True # (activation_post_process): MinMaxObserver(min_val=inf, max_val=-inf) # ) # (relu): ReLU() # (dequant): DeQuantStub() # ) # 这次的测试实际上是做cablibration test(net_quantized) print(net_quantized) # QuantizedVerySimpleNet( # (quant): QuantStub( # (activation_post_process): MinMaxObserver(min_val=-0.4242129623889923, max_val=2.821486711502075) # ) # (linear1): Linear( # in_features=784, out_features=100, bias=True # (activation_post_process): MinMaxObserver(min_val=-53.58397674560547, max_val=34.898128509521484) # ) # (linear2): Linear( # in_features=100, out_features=100, bias=True # (activation_post_process): MinMaxObserver(min_val=-24.331275939941406, max_val=26.62542152404785) # ) # (linear3): Linear( # in_features=100, out_features=10, bias=True # (activation_post_process): MinMaxObserver(min_val=-28.273700714111328, max_val=20.937761306762695) # ) # (relu): ReLU() # (dequant): DeQuantStub() # ) net_quantized = torch.ao.quantization.convert(net_quantized) print(net_quantized) # QuantizedVerySimpleNet( # (quant): Quantize(scale=tensor([0.0256]), zero_point=tensor([17]), dtype=torch.quint8) # (linear1): QuantizedLinear(in_features=784, out_features=100, scale=0.6967094540596008, zero_point=77, qscheme=torch.per_tensor_affine) # (linear2): QuantizedLinear(in_features=100, out_features=100, scale=0.40123382210731506, zero_point=61, qscheme=torch.per_tensor_affine) # (linear3): QuantizedLinear(in_features=100, out_features=10, scale=0.3874918520450592, zero_point=73, qscheme=torch.per_tensor_affine) # (relu): ReLU() # (dequant): DeQuantize() # ) 在实际的部署中,一般不会用pytorch的量化模块。根据你所需要的后端,选择tensorrt, onnxruntime, openvino, ncnn等框架的量化模块。\nQauntization-aware training(QAT) qat顾名思义,指的是开模型训练的前就将模型进行量化,从而训练出来的误差更接近“量化误差”。但人们经过广泛的时间发现,将一个训练好的模型在量化后进行微调,要比量化模型在从零训练的准确率更高。所以现在的qat的training一般指的是微调量化模型。 在pytorch中的流程基本和PTQ差不多,就是训练时改成\nnet.train() net_quantized = torch.ao.quantization.prepare_qat(net) # Insert observers 但是其原理不同,因为涉及到了反向传播的过程。QAT的流程如下: 在qat的过程中,我们会保存一份fp32的weight副本,这是用来更新梯度的(若只有梁化后的int8 weight,我们每次的梯度变化会在round的时候被归零,例如,weight是3.2, quantized weight是3,这次反向传播的时候需要剪去0.1,如果quantized weight - 0.1在round之后还是3,所以我们需要保留原来的fp32 weight, 不断累积梯度更新,当累计一定值后,quantized weight就会被更新了)。\n在反向传播过程中,模型需要对每个权重和输入计算损失函数的梯度。这里出现了一个问题:我们之前定义的量化操作的导数是什么?实际上量化得到的结果是离散值,所以导数应该在任何地方都是0, $\\frac{\\partial Q(W)}{\\partial W} = 0$,如果我们这个设置,网络什么也学不到因为梯度得不到更新。 一个典型的解决方案是使用Straight-through Estimator,简称STE,来近似这个梯度。STE简单的将量化操作的导数设置为1,即$\\frac{\\partial Q(W)}{\\partial W} = 1$。这样,我们就可以在反向传播的过程中更新梯度了。为什么能设置成1呢,因为实际上这是一个阶梯函数,如下图所示: reference tinyml quantization Quantizing deep convolutional networks for efficient inference: A whitepaper gemmlowp关于量化的示例 8-bit inference with TensorRT ","permalink":"https://caaatch22.github.io/posts/tinyml-quantization/","summary":"\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003e模型量化(quantization)指的是用更少的bit表示模型参数,从而减少模型的大小,加速推理过程的技术。\u003c/p\u003e\n\u003cp\u003e一种常见的量化方式是线性量化(linear quantization),也叫仿射量化(affine quantization)。其实就是按比例将tensor(一般为fp32)放缩到 $2^{bitwidth}$ 的范围内,比如8bit等。我们很容易给出量化公式:\n$$\nr = s(q - z)\n$$\n其中,r(real value)值得是量化前的值,q(quantized value)是量化后的值,s(scale)是放缩比例,z(zero point)相当于是一个偏移量。\u003c/p\u003e\n\u003cp\u003e如何求出$s$和$z$呢?一种简单且常见的方式是通过最大最小值来估计,即:\u003c/p\u003e\n\u003cp\u003e$$\ns = \\frac{r_{max} - r_{min}}{q_{max} - q_{min}}\n$$\n$r_{max}$就是这个tensor的最大值,$r_{min}$是最小值,$q_{max}$和$q_{min}$是我们指定的量化后的最大最小值。如下图所示:\n\u003cimg loading=\"lazy\" src=\"/img/tinyml/quantization/quantization.png\" alt=\"image\" /\u003e\n\u003c/p\u003e\n\u003cp\u003e有了scale, 容易得到 $z = q_{min} - \\frac{r_{min}}{s}$。在实际操作中,z一般会被round到最近的整数$z = round(q_{min} - \\frac{r_{min}}{s})$(有很多不同的round规则,这个有具体实现决定)。\u003c/p\u003e\n\u003cp\u003e得到量化方程:\n$$\nq = clip(round(\\frac{r}{s}) + z, q_{min}, q_{max})\n$$\u003c/p\u003e\n\u003cp\u003e代码示意如下:(实际会用pytorch已有的quantize api或者其他推理框架)\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eget_quantized_range\u003c/span\u003e(bitwidth):\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e quantized_max \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e (bitwidth \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e)) \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e quantized_min \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e (bitwidth \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e quantized_min, quantized_max\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003edef\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003elinear_quantize\u003c/span\u003e(fp_tensor, bitwidth, scale, zero_point, dtype\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003etorch\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eint8) \u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003e torch\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eTensor:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e rounded_tensor \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e torch\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eround(fp_tensor \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e scale)\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eto(dtype)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e shifted_tensor \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e rounded_tensor \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e zero_point\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e quantized_min, quantized_max \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e get_quantized_range(bitwidth)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e quantized_tensor \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e shifted_tensor\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003eclamp_(quantized_min, quantized_max)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e quantized_tensor\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e上述过程被称为非对称量化(asymmetric quantization)。\u003c/p\u003e","title":"TinyMl —— 模型量化(quantization)"},{"content":" 最近正在学习 MIT 6.5940, 韩松老师的课,做deep learning compression的应该都只知道。课程分为三个部分,efficient inference, domain-specific optimization, efficient training。有完整的课件,视频和实验。最后一个lab是将llama2部署在个人电脑上,非常有意思(谁不想要个自己的大模型呢)。其余lab也都可以白嫖google colab的gpu\nIntroduction 正式介绍pruning and sparsity之前,我们先来聊聊为什么要做model compression这个事情。 Today\u0026rsquo;s Model is Too Big!\n随着Large language model的出现,如GPT-3,如今的模型参数量已经达到了上百billion,别说训练,我们甚至无法在一个gpu上对其进行推理。更别提如果我们想要将其部署在其他边缘设备上。\n所以当前在做inference之前,一般都会有个model-compression的过程,包括pruning(剪枝),quantization(量化),distillation(蒸馏)等。这些方法都是为了减少模型的大小,加速推理过程。这些方法也被广泛地集成到了各种加速卡,gpu中。例如nv的A100就支持structured sparsity([N:M]形式的,具体含义下文会详细介绍)。\nEfficiency Metrics 我们再来看看一些 efficiency metrics,这也是我们在做inference过程中需要考虑的指标:\nMemory-Related Metrics # parameters model size total/peak #activations Computation-Related Metrics MACs FLOP, FLOPs # parameters 下表是一些常见结构的参数数量:\nModel #Parameters Linear Layer(FC) $feature_{in} * feature_{out}$ Conv Layer $c_{i} * c_{o} * k_{h} * k_{w} $ Grouped Conv Layer $c_{i} * c_{o} * k_{h} * k_{w} / g$ Depthwise Conv Layer $c_{o} * k_{h} * k_{w}$ 其中,Grouped Conv指的是将输入在channel维度进行分组,然后分别进行卷积,最后concatenate。Depthwise Conv是分组个数 $g$ 等于输入channel数的情况。\n除了这些weight外,还有bias以及norm相关的参数。 例如,对于batchnorm层,我们需要两个参数$\\gamma, \\beta$以及runing mean和running variance。 $$ y = \\gamma \\frac{x - \\mu}{\\sqrt{\\sigma^2 + \\epsilon}} + \\beta $$\n这都是针对infernece的情况,对于training,还需要考虑一些额外的参数,例如momentum,以及保存梯度等等。\nmodel size $$ Model Size = \\# parameters * bitwidth $$\n举个例子,AlexNet有$61M$个参数,如果我们用32bit的float来表示,那么model size就是$61M * 4byte = 224MB$。但如果我们用8-bit来表示每个weight,那么model size就是$61M * 1byte = 61MB$。\n这就是quantization的一个应用,通过减少bitwidth来减少model size。\ntotal/peak #activations #activations 是模型在推理时在内存中需要存储的中间结果,这也可能成为内存瓶颈。如下图所示: 该图展示了MCUNet(一个专门用在IoT设备上的模型)的参数量和activations数量对比resnet的减少。可以看出,参数的减少量十分显著,但是# activations不降反增,和param的占比是一个数量级的。所以我们若想在边缘设备上部署模型,需要考虑activations的数量。\nMAC MAC的含义是 Multiply-Accumulate operation。例如,一个gemm(genearl matrix multiplication)的$MACs = m * n * k$(对于m * k的矩阵和k * n的矩阵相乘)。深度学习中几乎90%的时间都在做gemm,一个conv2d操作也可以由im2col转换为gemm操作。\n以下是常见的一些layer的MACs计算:\nlayer MACs(batch size = 1) Linear $feature_{in} * feature_{out}$ Conv $c_{i} * k_{h} * k_{w} * h_{o} * w_{o} * c_{o}$ grouped conv $c_{i}/g * k_{h} * k_{w} * h_{o} * w_{o}* c_{o} $ depthwise conv $c_{o} * k_{h} * k_{w} * h_{o} * w_{o}$ (简单解释一下conv2d的MACs:对于每个输出特征图上的每个像素点,都需要进行 $c_i \\times k_h \\times k_w $ 次乘法运算和累加操作(假设每个卷积核和输入通道的对应部分做完整个卷积运算)。因为输出特征图有 $c_o$ 个通道,每个通道有 $h_o \\times w_o$ 个像素点,所以总共的MACs数就是上面公式中的乘积。)\nFLOP and FLOPs FLOP的意思是 Floating Point Operations,是指浮点运算的次数。FLOPs是指每秒的FLOP数量。 一般一个MAC对应两个FLOP,一乘一加。\nEnergy Consumption 对于边缘设备,我们还需要考虑能耗的问题。 图中可以看出,从内存中取数操作的能耗是运算的200倍以上。\nneural network pruning 那么,到底什么是 模型剪枝(model pruning) 呢?顾名思义,就是将模型中的一些参数去掉,或者更加细粒度的,我们可以把某些参数与参数之间的连接去掉。还有一个常用的概念是 稀疏度(sparsity) ,指的是剪枝后的参数占原有参数的比例。例如,如果我们剪去了90%的参数,那么sparsity就是0.1。\n注意:剪去某个参数并不意味着单纯将其设置为0。因为我们剪枝的目的是减少内存使用以及加速推理,若只是讲某个$W$矩阵中的一些值变成0,并不能达到减少内存的目的。我们需要将剪枝后的参数$W_{p}$用特定的方式存储(例如,稀疏矩阵)以及结合特定的优化后的运算才能减少内存使用并加速。当然,从正确性的角度来说,将某些参数设置为0可以得到一样的结果。所以我们可以在确定 sparsity 时先将一定的参数置为0,然后再通过fine-tuning来确定剪去这么多的参数是否会影响模型最终的效果。\n减去一定的参数后,肯定会对我们的模型效果造成影响。这时候我们需要进行fine-tuning调整参数分布,具体会在后文展开。\n形式化的定义prune: pruning granularity 我们有不同的剪枝粒度,from unstructured to structured。\n对于一个FC layer,其实就是将一个2d matrix进行剪枝: 如图,细粒度的、不规则的剪枝一般能有更高的sparsity,但是却难以加速(就像上文说的,如果只是将一些参数设置为0,不会有任何内存与速度上的收益)。而粗粒度的剪枝,就可以不需要改变原有的矩阵结构获得更少的内存使用以及加速,但一般sparsity会高一些(同等accuracy下)。\n对于卷积层,我们有更多的剪枝粒度: 也是从完全不规则,到有一定的pattern,到vector-level,kernel-level再到剪去一个通道。优缺点如同上面分析的一般。 对于最fine-grained的剪枝,许多模型都可以达到剪去90%以上而不影响精度。 对于有一定pattern的剪枝,就需要设计特定的存储方式或者运算方式来加速。 例如下图的[N:M]剪枝(每M个参数剪去N个)就可以通过只存非0权重,以及一个下标数组的方式减少内存占用。nvdia的Ampere系列GPU就在其Tensor core内融合了这种优化,达到了2倍的加速效果 对于conv的剪枝,我们还用的一个不需要硬件特定实现的方法就是直接进行channel-level pruning,也就是直接减去特定的通道。\npros: 直接的加速,无需特定硬件 cons: 更少的模型压缩比 不同的卷积层的sparsity又该如何确定呢?是所有卷积层的sparsity都设置的相同还是各不相同?有什么样的标准呢?我们会在在pruning ratio这里会讲到。 pruning criterion 如何确定剪去的权重呢?常见的有magnitude-based pruning,scaling-based pruning等\nMagnitude-based pruning Learning Structured Sparsity in Deep Neural Networks Wen et al., NeurIPS 2016\n只得就是通过权重的重要程度来进行剪枝。直觉上看,对于一个激活函数$y = ReLU(10x_0 - 8x_1 + 0.1x_2 )$,肯定是$0.1$这个权重对其结果影响最小,可以将其剪去。 依此定义重要度$Importance = |W|$,直接按照数值的大小进行剪枝。当然,我们也可以将其拓展到任意范数: $$ Importance = |W|p = (\\sum{i} |W_i|^p)^{\\frac{1}{p}} $$ 只不过最常用的还是l1范数,因为这引入的额外的计算量较小。\nScaling-based pruning Learning Efficient Convolutional Networks through Network Slimming Liu et al., ICCV 2017\nscaling是针对卷积的一种剪枝策略,我们对卷积操作后的每个通道都用一个 scaling factor 进行缩放。这个scaling factor就和其他参数一样是一个需要训练的参数。 我们在以scaling factor的大小代表这一channel的重要性,从而图中一样进行剪枝。事实上,我们并不需要额外的scaling factor,因为我们的batchnorm层有自带了一个scaling factor: $\\gamma$ ($z = \\gamma \\frac{x - \\mu}{\\sqrt{\\sigma^2 + \\epsilon}} + \\beta$)。我们可以直接用这个$\\gamma$来进行剪枝。\nSecond-Order-based-pruning Optimal Brain Damage LeCun et al., NeurIPS 1989\n前面两种剪枝标准都是启发式的,通过定义参数的重要性进行剪枝。我们还可以通过 最小化剪枝前后的损失函数 来进行剪枝。 $$ \\delta L = L(\\mathbf{x}; \\mathbf{W}) - L(\\mathbf{x}; \\mathbf{W}P = \\mathbf{W} - \\delta\\mathbf{W}) = \\sum{i} g_{i}\\delta w_{i} + \\frac{1}{2}\\sum_{i} h_{ii}\\delta w_{i}^2 + \\frac{1}{2}\\sum_{i \\neq j} h_{ij}\\delta w_{i}\\delta w_{j} + O(|\\delta\\mathbf{W}|^3) \\ where\\ \\ g_i = \\frac{\\partial L}{\\partial w_i}, h_{ij} = \\frac{\\partial^2 L}{\\partial w_i \\partial w_j} $$ 这是LeCun在1989年时候的工作,它假设:\n目标函数L是近似二次的,所以最后一项可忽略 这个神经网络的训练已经收敛了,这表明一阶导数(梯度)接近零,所以第一项 $(\\sum_{i}g_{i}\\delta w_{i})$ 也可忽略 删除每个参数引起的误差是独立的:这允许我们忽略混合二阶导数项 ,因为这些项描述的是参数之间的相互作用。 因此,我们可以得到: $$ \\delta L_i = L(x; W) - L(x; W_p| w_i = 0) \\approx \\frac{1}{2}h_{ii} w_i^2 $$ 也就是$importance_{w_i} = |\\delta L| = \\frac{1}{2}h_{ii}w_{i}^2$,这就是一个二阶导数的剪枝标准。但是,由于$h_{ii}$ 是Hessian matrix,计算量过大,所以一般不会使用。\npercentage-of-Zero-Based pruning Network Trimming: A Data-Driven Neuron Pruning Approach towards Efficient Deep Architectures Hu et al.,ArXiv 2017\n非常简单粗暴的对与Channel-level pruning的方法。由于Relu会产生0,我们直接定义每个通道的重要程度为其 Average Percentage of Zeros(APoZ)。选取最小APoZ的通道进行剪枝。\nRegression-based pruning Channel Pruning for Accelerating Very Deep Neural Networks He et al., ICCV 2017\nregression-based pruning对于某一个layer进行操作,希望能够最小化重构误差。 令输入的feature map的channel数为$c$, 卷积核$W$的权重为 $n× c × k_h×k_w$, 卷积核每次卷积会在一个像素点上生成一个$N×n$的输出矩阵$Y$,其中$N$为batch_num,这里暂时不考虑bias项。要将$c$修剪为$c′$.同时最小化reconstruction error,这个优化问题是 $$ \\begin{aligned} \u0026amp; \\underset{\\beta, W}{\\text{arg min}} \u0026amp; \u0026amp; \\frac{1}{2N} | Y - \\sum_{i=1}^{C} \\beta_i X_i W_i^T |_F^2 \\ \u0026amp; \\text{subject to} \u0026amp; \u0026amp; | \\beta |_0 \\leq C' \\end{aligned} $$\n其中$X_{i}$是一个$N \\times k_h k_w$的矩阵裁剪自输入$X$。求解这个问题是NP难的,这里首先将问题用l1范数松弛为\n$$ \\begin{aligned} \u0026amp; \\underset{\\beta, W}{\\text{arg min}} \u0026amp; \u0026amp; \\frac{1}{2N} \\left| Y - \\sum_{i=1}^{C} \\beta_i X_i W_i^T \\right|_F^2 + \\lambda \\left| \\beta \\right|_1 \\ \u0026amp; \\text{subject to} \u0026amp; \u0026amp; \\left| \\beta \\right|_0 \\leq C\u0026rsquo;, ; \\forall i, \\left| W_i \\right|_F = 1 \\end{aligned} $$ 同时限制$||Wi||F=1$,然后在以下两个步骤中迭代\n首先锁定$W$ ,求解$\\beta$, 作为channel selection问题,这变成了零范数的LASSO regression。代码中可以知道作者是使用sklearn的Lasso regression函数做的 锁定$\\beta$,问题变成了$\\underset{W\u0026rsquo;}{\\text{arg min}} \\left| Y - X\u0026rsquo;(W\u0026rsquo;)^T \\right|_F^2 $本质上是一个线性回归。 Pruning Ratio 对于不同的layer之间,怎么确定每个layer应该剪去多少的权重而不会过多的影响精度呢?\nsensitivity of each layer 一种直观的方法就是查看每个layer对权重的敏感度各是多少,具体操作如下:\n选定一个layer $L_i$ prune $li$ with ratio $r_i \\in {0.1, 0.2, \u0026hellip;, 0.9}$(or other strides) 对每个ratio观察精度下降$\\Delta Acc_r^i$ 对所有layer重复上述操作 图中是VGG-11在CIFAR-10数据集上的不同层的pruning ratio对精度的影响。选定一个threshold后就可以依据这个对不同layer选取不同的pruning ratio。\nautomatic pruning 上面的方法是一种手动的方法,并且没有考虑不同层之间的相互影响。 一个有意思的工作来自ECCV 2018, AMC: AutoML for Model Compression and Acceleration on Mobile Devices, 用了强化学习的方法来选取不同的pruning-ratio,效果很好。不过目前没看懂,后面有机会再补充。\nFine-tuning/Training 说了这么多pruing方法,具体的操作流程如下: 通常我们都会迭代式的再inference阶段进行pruning,例如,先采用50%的sparsity进行推理,得到精度误差后fine-tune一次,训练出新的weights分布,然后不断加大sparsity,不断重新fine-tune。如上图所示\nSystem \u0026amp; Hardware Support for Sparsity 在pruning granularity的时候我们说到,若想要做到细粒度的剪枝,就需要特定的硬件支持。 韩老师自己的一篇论文[EIE](https://arxiv.org/pdf/1602.01528.pdfEIE: Efficient Inference Engine on Compressed Deep Neural Network)就支持对任意粒度的剪枝进行加速。由于我不是做硬件的,暂时没有仔细研究实现。\n英伟达的Amper系列GPU也支持对于structured sparsity的加速,例如[N:M]剪枝。 架构图如下: ","permalink":"https://caaatch22.github.io/posts/tinyml-pruning/","summary":"\u003cblockquote\u003e\n\u003cp\u003e最近正在学习 \u003ca href=\"https://hanlab.mit.edu/courses/2023-fall-65940\"\u003eMIT 6.5940\u003c/a\u003e, \u003ca href=\"https://hanlab.mit.edu/songhan\"\u003e韩松\u003c/a\u003e老师的课,做deep learning compression的应该都只知道。课程分为三个部分,\u003cstrong\u003eefficient inference, domain-specific optimization, efficient training\u003c/strong\u003e。有完整的课件,视频和实验。最后一个lab是将llama2部署在个人电脑上,非常有意思(谁不想要个自己的大模型呢)。其余lab也都可以白嫖google colab的gpu\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"introduction\"\u003eIntroduction\u003c/h2\u003e\n\u003cp\u003e正式介绍pruning and sparsity之前,我们先来聊聊为什么要做model compression这个事情。\n\u003cimg loading=\"lazy\" src=\"/img/tinyml/pruning/todays-model-size.png\" alt=\"todays-model-size\" /\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eToday\u0026rsquo;s Model is Too Big!\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e随着Large language model的出现,如GPT-3,如今的模型参数量已经达到了上百billion,别说训练,我们甚至无法在一个gpu上对其进行推理。更别提如果我们想要将其部署在其他边缘设备上。\u003c/p\u003e\n\u003cp\u003e所以当前在做inference之前,一般都会有个model-compression的过程,包括pruning(剪枝),quantization(量化),distillation(蒸馏)等。这些方法都是为了减少模型的大小,加速推理过程。这些方法也被广泛地集成到了各种加速卡,gpu中。例如nv的A100就支持structured sparsity([N:M]形式的,具体含义下文会详细介绍)。\u003c/p\u003e\n\u003ch3 id=\"efficiency-metrics\"\u003eEfficiency Metrics\u003c/h3\u003e\n\u003cp\u003e我们再来看看一些 efficiency metrics,这也是我们在做inference过程中需要考虑的指标:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eMemory-Related Metrics\n\u003cul\u003e\n\u003cli\u003e# parameters\u003c/li\u003e\n\u003cli\u003emodel size\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003etotal/peak #activations\u003c/li\u003e\n\u003cli\u003eComputation-Related Metrics\n\u003cul\u003e\n\u003cli\u003eMACs\u003c/li\u003e\n\u003cli\u003eFLOP, FLOPs\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"-parameters\"\u003e# parameters\u003c/h4\u003e\n\u003cp\u003e下表是一些常见结构的参数数量:\u003c/p\u003e\n\u003ctable\u003e\n \u003cthead\u003e\n \u003ctr\u003e\n \u003cth\u003eModel\u003c/th\u003e\n \u003cth\u003e#Parameters\u003c/th\u003e\n \u003c/tr\u003e\n \u003c/thead\u003e\n \u003ctbody\u003e\n \u003ctr\u003e\n \u003ctd\u003eLinear Layer(FC)\u003c/td\u003e\n \u003ctd\u003e$feature_{in} * feature_{out}$\u003c/td\u003e\n \u003c/tr\u003e\n \u003ctr\u003e\n \u003ctd\u003eConv Layer\u003c/td\u003e\n \u003ctd\u003e$c_{i} * c_{o} * k_{h} * k_{w} $\u003c/td\u003e\n \u003c/tr\u003e\n \u003ctr\u003e\n \u003ctd\u003eGrouped Conv Layer\u003c/td\u003e\n \u003ctd\u003e$c_{i} * c_{o} * k_{h} * k_{w} / g$\u003c/td\u003e\n \u003c/tr\u003e\n \u003ctr\u003e\n \u003ctd\u003eDepthwise Conv Layer\u003c/td\u003e\n \u003ctd\u003e$c_{o} * k_{h} * k_{w}$\u003c/td\u003e\n \u003c/tr\u003e\n \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e其中,Grouped Conv指的是将输入在channel维度进行分组,然后分别进行卷积,最后concatenate。Depthwise Conv是分组个数 $g$ 等于输入channel数的情况。\u003c/p\u003e","title":"TinyMl —— 模型剪枝(pruning)"},{"content":" 本系列笔记主要参考了 \u0026ldquo;Programming massively parallel processors\u0026quot;这本书,以及网上相关资料;不会特别详细,当作个人整理的面经\nCUDA软件架构 CUDA从软件层面上提供了三层结构包括grid, block和thread。每个kernal内启动的所有线程在一个grid内。启动kernel时指定\u0026laquo;\u0026lt;dimGrid, dimBlock\u0026raquo;\u0026gt;,都是一个dim3结构。\ngridDim的最大值范围: (x,y,z): (2^31 - 1, 65535, 65535) blockDim的最大值范围:(x,y,z): (1024, 1024, 64) 且还要同时满足:一个block内的threads数量不能超过1024(从kepler开始)。即:$ blockDim.x * blockDim.y * blockDim.z \u0026lt;= 1024 $ CUDA内存架构 变量声明 所在内存 作用域 生命周期 kernel内除了array的变量 register thread grid kernel内的array 变量 local thread grid __shared__ 修饰的kernel内的变量 shared block grid __device__ 修饰的全局变量 global grid application __device__ __constant__ 修饰的全局变量 constant grid application 其中 寄存器是GPU上运行速度最快的内存空间,延迟为1个时钟周期。 接下来是共享内存,共享内存是GPU上可受用户控制的一级缓存 。共享内存类似于CPU的缓存,不过与CPU的缓存不同,GPU的共享内存可以有CUDA内核直接编程控制。延迟为1~32个时钟周期。 local memory实际上就在global memory上,只是通过编译器处理成私有的、每个线程独立的一块内存区域。一般一个kernal内的数组会被处理成local memory。延迟和global memory类似。 还有texture memeory,但是和科学计算相关不大。 CUDA硬件结构 一个GPU可以看作是SM(streaming multiprocessor)的集合,每个SM包含多个SP(streaming processor,或者现在一般叫CUDA cores)。 例如,在一个A100中,一共有108个SMs,每个SM有64个CUDA cores。 从软件调度上,一个grid对应整个GPU(多个SM),block对应SM,warp/thread级别对应一个CUDA core。 一个block内的threads一定被分配到同一个SM中,但有可能多个block都被分配到同一个SM中。(且可能同时分给一个SM超出硬件cores的线程,A100为例子 2048 threads == 32warps,只是真正同时运行某些warp,其余的可以用作hidding latency等) 每个SM上都有一个control,一个shared_memory。软件上可以通过__syncthreads()同步一个block内的所有线程。\nSIMT(single instruction, multiple threads) CUDA以 warp(线程束) 的形式组织线程,一般每32个线程组成一个warp,warp内的线程是SIMT(single instruction, multiple threads)的,Instructions are issued at the warp level.即一个warp内线程的指令的PC总是相同的 (实际上,Volta架构前,warp内的所有线程共享PC和stack;Volta架构后,warp内的不同线程有独立的PS,stack用于做Converge Optimizer) 如果我们创建的block线程数不是32的倍数会怎么样?会自动补到32的倍数。\n其他 zero-overhead scheduling CUDA中所说的零开销调度是什么意思呢?\n一般来说,我们都会让一个SM上分配的warp数超出他能同时处理的warp数;这样,当有一个warp的指令是长延迟操作时(比如访问global memory),可以调度另一个不必等待的warp。当有足够多的warp时,硬件随时可以找到能够执行的warp,充分利用硬件资源。warp选取不会引入多余的执行时间,这被称为“零开销线程调度”。(这也是为什么GPUs不想CPUs一样,用大面积的芯片做缓存和分支预测) 还有一个原因在于,与CPU需要将寄存器存到内存中做上下文切换不同的是,GPU会将所有执行状态保存至寄存器上(每个SM内的寄存器数量非常庞大,见A100架构图),从而减小上下文切换(这里指切换warp开销)。\nroofline model Control divergence function declarations global: CPU/GPU都可以call, 运行在GPU上 device: 运行在GPU上,可以被其他__device__或__global__ call host: 运行在CPU上,可以被其他CPU call(可省略) 可以同时__host__ __device__修饰一个函数表示在CPU or GPU上运行\nperf 平时如何进行kernel的优化,会用到哪些工具?\n首先,要优化kernel函数需要先了解GPU硬件的构造。其次,需要熟悉常见的profiler工具,主要包括Nsight System和Nsight Compute。\n在优化的手段和方向上主要关注几个点:\n使用异步API:如cudaMemcpyAsync可让GPU操作与CPU操作并行,CPU忙完后调用cudaStreamSynchronize,cudaEventWait等操作等待GPU任务完成。 优化内存与显存传输效率 使用Pinned(page-locked) Memory提高传输速度 通过在不同的Stream里同时分别执行kernel调用及数据传输,使数据传输与运算并行。(注意default stream的坑) 尽量将小的数据在GPU端合成大块数据后传输 优化Kernel访存效率 提高Global Memory访存效率 对Global Memory的访存需要注意合并访存(coalesced )。 warp的访存合并后,起始地址及访存大小对齐到32字节 尽量避免跨步访存 CUDA 8.0及以上的设备可以通过编程控制L2的访存策略提高L2命中率。 提高Shared Memory的访存效率 shared memory由32个bank组成 每个bank每时钟周期的带宽为4字节 连续的4字节单元映射到连续的bank。如0-3字节在bank0,4-7字节在bank1……字节128-131字节在bank0 若warp中不同的线程访问相同的bank,则会发生bank冲突(bank conflict),bank冲突时,warp的一条访存指令会被拆分为n条不冲突的访存请求,降低shared memory的有效带宽。所以需要尽量避免bank冲突。 CUDA 11.0以上可以使用async-copy feature 一些并行算法 TODO: reduce, gemm, transpose, softmax, layernor\n#define TILE_WIDTH 16 __global__ void matrixMulKernel(float* M, float* N, float* P, int Width) { __shared__ float Mds[TILE_WIDTH][TILE_WIDTH]; __shared__ float Nds[TILE_WIDTH][TILE_WIDTH]; int bx = blockIdx.x, by = blockIdx.y; int tx = threadIdx.x, ty = threadIdx.y; int row = by * TILE_WIDTH + ty; int col = bx * TILE_WIDTH + tx; float value = 0.0f; for (size_t i = 0; i \u0026lt; Width / TILE_WIDTH; i ++) { Mds[ty][tx] = M[row * Width + i * TILE_WIDTH + tx]; Nds[ty][tx] = M[(i * TILE_WIDTH + ty) * Width + col]; __syncthreads(); for (size_t k = 0; k \u0026lt; TILE_WIDTH; k ++) { value += Mds[ty][k] * Nds[k][tx]; } __syncthreads(); } P[row * Width + col] = value; } ","permalink":"https://caaatch22.github.io/posts/cuda/","summary":"\u003cblockquote\u003e\n\u003cp\u003e本系列笔记主要参考了 \u0026ldquo;Programming massively parallel processors\u0026quot;这本书,以及网上相关资料;不会特别详细,当作个人整理的面经\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"cuda软件架构\"\u003eCUDA软件架构\u003c/h2\u003e\n\u003cp\u003eCUDA从软件层面上提供了三层结构包括grid, block和thread。每个kernal内启动的所有线程在一个grid内。启动kernel时指定\u0026laquo;\u0026lt;dimGrid, dimBlock\u0026raquo;\u0026gt;,都是一个dim3结构。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003egridDim的最大值范围: (x,y,z): (2^31 - 1, 65535, 65535)\u003c/li\u003e\n\u003cli\u003eblockDim的最大值范围:(x,y,z): (1024, 1024, 64)\u003c/li\u003e\n\u003cli\u003e且还要同时满足:一个block内的threads数量不能超过1024(从kepler开始)。即:$ blockDim.x * blockDim.y * blockDim.z \u0026lt;= 1024 $\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/CUDA/CUDA-software-arch.png\" alt=\"CUDA software architecture\" /\u003e\n\u003c/p\u003e\n\u003ch2 id=\"cuda内存架构\"\u003eCUDA内存架构\u003c/h2\u003e\n\u003ctable\u003e\n \u003cthead\u003e\n \u003ctr\u003e\n \u003cth\u003e变量声明\u003c/th\u003e\n \u003cth\u003e所在内存\u003c/th\u003e\n \u003cth\u003e作用域\u003c/th\u003e\n \u003cth\u003e生命周期\u003c/th\u003e\n \u003c/tr\u003e\n \u003c/thead\u003e\n \u003ctbody\u003e\n \u003ctr\u003e\n \u003ctd\u003ekernel内除了array的变量\u003c/td\u003e\n \u003ctd\u003eregister\u003c/td\u003e\n \u003ctd\u003ethread\u003c/td\u003e\n \u003ctd\u003egrid\u003c/td\u003e\n \u003c/tr\u003e\n \u003ctr\u003e\n \u003ctd\u003ekernel内的array 变量\u003c/td\u003e\n \u003ctd\u003elocal\u003c/td\u003e\n \u003ctd\u003ethread\u003c/td\u003e\n \u003ctd\u003egrid\u003c/td\u003e\n \u003c/tr\u003e\n \u003ctr\u003e\n \u003ctd\u003e\u003ccode\u003e__shared__\u003c/code\u003e 修饰的kernel内的变量\u003c/td\u003e\n \u003ctd\u003eshared\u003c/td\u003e\n \u003ctd\u003eblock\u003c/td\u003e\n \u003ctd\u003egrid\u003c/td\u003e\n \u003c/tr\u003e\n \u003ctr\u003e\n \u003ctd\u003e\u003ccode\u003e__device__\u003c/code\u003e 修饰的全局变量\u003c/td\u003e\n \u003ctd\u003eglobal\u003c/td\u003e\n \u003ctd\u003egrid\u003c/td\u003e\n \u003ctd\u003eapplication\u003c/td\u003e\n \u003c/tr\u003e\n \u003ctr\u003e\n \u003ctd\u003e\u003ccode\u003e__device__ __constant__\u003c/code\u003e 修饰的全局变量\u003c/td\u003e\n \u003ctd\u003econstant\u003c/td\u003e\n \u003ctd\u003egrid\u003c/td\u003e\n \u003ctd\u003eapplication\u003c/td\u003e\n \u003c/tr\u003e\n \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cul\u003e\n\u003cli\u003e其中 \u003cstrong\u003e寄存器是GPU上运行速度最快的内存空间\u003c/strong\u003e,延迟为1个时钟周期。\u003c/li\u003e\n\u003cli\u003e接下来是共享内存,\u003cstrong\u003e共享内存是GPU上可受用户控制的一级缓存\u003c/strong\u003e 。共享内存类似于CPU的缓存,不过与CPU的缓存不同,GPU的共享内存可以有CUDA内核直接编程控制。延迟为1~32个时钟周期。\u003c/li\u003e\n\u003cli\u003elocal memory实际上就在global memory上,只是通过编译器处理成私有的、每个线程独立的一块内存区域。一般一个kernal内的数组会被处理成local memory。延迟和global memory类似。\u003c/li\u003e\n\u003cli\u003e还有texture memeory,但是和科学计算相关不大。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"cuda硬件结构\"\u003eCUDA硬件结构\u003c/h2\u003e\n\u003cp\u003e一个GPU可以看作是SM(streaming multiprocessor)的集合,每个SM包含多个SP(streaming processor,或者现在一般叫CUDA cores)。\n\u003cimg loading=\"lazy\" src=\"/img/CUDA/CUDA-GPU.png\" alt=\"CUDA GPU\" /\u003e\n\u003c/p\u003e","title":"CUDA编程模型"},{"content":"1.3 Copying as a Fallback\n1.4 const Return Values const std::string get_value();不再是guideline,因为const disables 移动语义 例如:\nconst std::string getValue(); std::vector\u0026lt;std::string\u0026gt; coll; coll.push_back(getValue()); // copies(because the return value is const) const std::string getValue(); // BAD: disables move semantics for return values const std::string\u0026amp; getRef(); // OK const std::string* getPtr(); // OK Summary Move semantics allows us to optimize the copying of objects, where we no longer need the value. It can be used implicitly (for unnamed temporary objects or local return values) or explicitly (with std::move()). std::move() means I no longer need this value here. It marks the object as movable. An object marked with std::move() is not (partially) destroyed (the destructor still will be called). By declaring a function with a non-const rvalue reference (such as std::string\u0026amp;\u0026amp;), you define an interface where the caller semantically claims that it no longer needs the passed value. The implementer of the function can use this information to optimize its task by “stealing” the value or do any other modification with the passed argument. Usually, the implementer also has to ensure that the passed argument is in a valid state after the call. Moved-from objects of the C++ standard library are still valid objects, but you no longer know their value. Copy semantics is used as a fallback for move semantics (if copy semantics is supported). If there is no implementation taking an rvalue reference, any implementation taking an ordinary const lvalue reference (such as const std::string\u0026amp;) is used. This fallback is then used even if the object is explicitly marked with std::move(). Calling std::move() for a const object usually has no effect. If you return by value (not by reference), do not declare the return value as a whole to be const. Moved-from objects Valid but Unspecified State\nfoo(std::move(s)); // keeps s in a valid but unclear state std::cout \u0026lt;\u0026lt; s \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; // OK (don’t know which value is written) std::cout \u0026lt;\u0026lt; s.size() \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; // OK (writes current number of characters) std::cout \u0026lt;\u0026lt; s[0] \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; // ERROR (potentially undefined behavior) std::cout \u0026lt;\u0026lt; s.front() \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; // ERROR (potentially undefined behavior) s = \u0026#34;new value\u0026#34;; // OK 有什么用:\nstd::vector\u0026lt;std::string\u0026gt; allRows; std::string row; while (std::getline(myStream, row)) { // read next line into row allRows.push_back(std::move(row)); // and move it to somewhere } struct Customer { std::string name; double height; }; using Customers = soa_vector\u0026lt;Customer\u0026gt;; Customers.emplace_back(\u0026#34;example_name\u0026#34;, 173.6); std::tuple\u0026lt;std::string\u0026amp;, double\u0026amp; height\u0026gt; Customers[0]; ","permalink":"https://caaatch22.github.io/posts/back2basics-cpp-move-sematic/","summary":"\u003cp\u003e1.3 Copying as a Fallback\u003c/p\u003e\n\u003cp\u003e1.4 \u003ccode\u003econst\u003c/code\u003e Return Values\nconst std::string get_value();不再是guideline,因为\u003ccode\u003econst\u003c/code\u003e disables 移动语义\n例如:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estring getValue();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003evector\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003estd\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estring\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e coll;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecoll.push_back(getValue()); \u003cspan style=\"color:#75715e\"\u003e// copies(because the return value is const)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estring getValue(); \u003cspan style=\"color:#75715e\"\u003e// BAD: disables move semantics for return values\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estring\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e getRef(); \u003cspan style=\"color:#75715e\"\u003e// OK\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003estring\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e getPtr(); \u003cspan style=\"color:#75715e\"\u003e// OK\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"summary\"\u003eSummary\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003eMove semantics allows us to optimize the copying of objects, where we no longer need the value. It can be used implicitly (for unnamed temporary objects or local return values) or explicitly (with std::move()).\u003c/li\u003e\n\u003cli\u003estd::move() means I no longer need this value here. It marks the object as movable. An object marked with std::move() is not (partially) destroyed (the destructor still will be called).\u003c/li\u003e\n\u003cli\u003eBy declaring a function with a non-const rvalue reference (such as std::string\u0026amp;\u0026amp;), you define an interface where the caller semantically claims that it no longer needs the passed value. The implementer of the function can use this information to optimize its task by “stealing” the value or do any other modification with the passed argument. Usually, the implementer also has to ensure that the passed argument is in a valid state after the call.\u003c/li\u003e\n\u003cli\u003eMoved-from objects of the C++ standard library are still valid objects, but you no longer know their value.\u003c/li\u003e\n\u003cli\u003eCopy semantics is used as a fallback for move semantics (if copy semantics is supported). If there is no implementation taking an rvalue reference, any implementation taking an ordinary const lvalue reference (such as const std::string\u0026amp;) is used. This fallback is then used even if the object is explicitly marked with std::move().\u003c/li\u003e\n\u003cli\u003eCalling std::move() for a const object usually has no effect.\u003c/li\u003e\n\u003cli\u003eIf you return by value (not by reference), do not declare the return value as a whole to be const.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eMoved-from objects\n\u003cstrong\u003eValid but Unspecified State\u003c/strong\u003e\u003c/p\u003e","title":"Back to Basics -- c++ move sematics"},{"content":" H\u0026amp;P那本关于分支预测的部分比较简短且表述有点晦涩,(顺便吐槽一下第五版的中文翻译,建议看英文原版)本文主要参考超标量处理器设计,国人写的,用语符合习惯,强烈推荐!\nMotivation 在处理器中,除了cache之外,另一个重要的内容就是分支预测,它和cache一起左右处理器的性能。以SPECint95作为benchmark,完美的cache和BP(branch-predictor)能使IPC提高两倍左右: 图片来自论文SSMT。当然,这是21世纪之前的结果了。现代处理器分支预测普遍能达到97%~98%以上的精度,在多数浮点benchmark中基本都是99%的准确率。\n为什么需要这么高的精度呢? 一般情况下,分支指令的占比通常在 15% 到 30% 之间。对于经典五级流水线无分支预测cpu,一个branch会造成一次stall;而对于现代的superscalar且流水线级数远高于5的(一般是二十级以上)cpu,其misprediction penalty是 $M * N$ 的(M = fetch group内指令数, N = branch resolution latency,就是决定分支最终是否跳转需要多少周期)。如下图所示: 我们再做一个定量实验: 假设我们有一个\n$ N = 20 (20\\ pipe stages), W = 5 (5\\ wide fetch) $ 1 out of 5 instructions is a branch Each 5 instruction-block ends with a branch 的CPU,那么我们取出500条指令需要多少个周期呢?\n100% 预测正确率 100 个时钟周期 (all instructions fetched on the correct path) 无额外工作 99% 预测正确率 100 (correct path) + 20 (wrong path) = 120 个时钟周期 20% 额外指令被取出 98% 预测正确率 100 (correct path) + 20 * 2 (wrong path) = 140 个时钟周期 40% 额外指令被取出 95% 预测正确率 100 (correct path) + 20 * 5 (wrong path) = 200 个时钟周期 100% 额外指令被取出 可以看出,分支预测失败在现代的超标量多流水线cpu中的penalty被极大的放大了。所以分支预测的正确性就显得额外重要。\nTaken/Not Taken 静态分支预测 branch delay slots 这是一种软硬件结合的静态分支预测方法,准确的说这不算是一种预测的方法。它要求编译器找出和分支是否发生无关的指令(例如,分支前的指令且没有data dependance)重排到分支指令之后,这样无论分支是否发生,后面的指令都需要执行,以此解决stall的问题。早期的MIPS cpu会采用这种设计。不幸的是,随着cpu的流水线层数越来越多,编译器必须找到N条与该分支指令无关的指令(N为决定分支最终是否跳转需要多少周期)才能喂满delay slots达到没有stall的效果。且随着superscalar架构的流行,这种设计在硬件上也有许多难以解决的冲突,因此最终离开了历史舞台。\nalways Taken or always Not Taken 预测分支总是发生/不发生是最简单的分支预测方法。此外,我们还有这样的规律可以利用: 这是因为在循环中,分支backward跳转发生的几率是很大的。那我们或许可以在此事实上建立一个Backward Branch Taken, Forward Branch Not Taken的预测方法。\nAlways Predict Not-Taken Basically o nothing, simple to implement Know fall-through PC in Fetch Poor Accuracy, especially on backward branches Always Predict Taken Difficult to implement because don’t know target until Decode Poor accuracy on if-then-else Backward Branch Taken, Forward Branch Not Taken Better Accuracy Difficult to implement because don’t know target until Decode 这些方法都能显著提高分支预测的成功率,但是远远还达不到95%以上的准确率。\n动态分支预测 动态分支预测中的动态,按我的理解,指的是根据历史分支跳转情况调整接下来的分支预测方向。\n2-bit predictor 一个最intuitive的方式就是我们用上一次分支是否跳转来预测当前分支是否跳转。注意,这里的上一次指的是 上次执行该分支指令时是否跳转,而并不是依据上一条分支来预测本分支。 实现方式也比较简单:我们将所有分支指令(的地址)都对应一个bit,来表示上次跳转的结果。具体细节可以看后面2-bit predictor\n这种预测方式至少在循环中能有不错的结果——若循环m次,则missprediction为$\\frac{1}{m} \\ or\\ \\frac{2}{m}$(如果第一次默认为不跳转那么第一次循环会被预测错误)。但是在某种极端情况下,它的分支正确率为0%\nfor (int i = 0; i \u0026lt; n; i ++) { if (a[i] % 2 == 0) { // \u0026lt;--- branch A do something; } else { do something else; } } 对于branch A 这个分支,若a数组中的元素为奇偶交替出现,且$a[0]$为奇数,则实际跳转为$TNTNTNTNTN\u0026hellip; (T:taken, N: not taken)$;不幸的是,我们将用于该分支的预测的bit初始化为0,那么我们会预测为$NTNTNTNT\u0026hellip;$ 其失败率将为100%!\n为了避免这种问题,人们发明了2-bit predictor——每个分支的历史信息用两个bit来维护。其状态图如下: 其实2-bit predictor很类似于LRU-k的思想,对于抖动的程序有更好的效果:比如对于$TTTNTTT$的分支,2-bit predictor会容忍其中的Not Taken,只是将状态由Strongly Taken转变为Weakly Taken,但在$N$发生的下一次仍然会预测为$T$。所以它就可以有效地防止在$TNTNTNTNTN\u0026hellip;$的情况下失败率为100%。 另外,对于多重循环:\nfor (int i = 0; i \u0026lt; n; i ++) { for (int j = 0; j \u0026lt; m; j ++) { do something; } } 2-bit predictor 对内层循环的准确率就是$\\frac{1}{m}$的(除了第一次外层循环),这也优于用1-bit predictor的情况。\n我们在来考虑如何实现2-bit predictor: 最直接的方法是我们对每条指令(即便他不是分支指令)都要有一个独立的predictor,那么在32位机器中这就要求存储空间 $2^{30} * 2 bit \\approx 0.25 GB$,这么大的额外的硅芯面积显然是无法接受的。 我们可以用下图所示来存储2-bit predictor的值: 图中的PHT(pattern history table)存放的就是所有PC值对应的2-bit predictor。我们只用了PC值当中的k位来对PHT进行寻址,因此PHT的大小就是$2^{k} \\times 2bit$的。但使用这种方式来寻址PHT,必然就会导致那k-bit相同的PC映射到相同的PHT entry上,这种情况称为aliasing,aliasing会对分支预测的准确度产生影响。这就是设计者得做出的trade-off了,增大k能提高准确率但需要消耗更多硬件。我们还可以将PC的经过某种哈希后再取k位,这样能减少碰撞概率。\n我们按照从用1-bit来记录历史跳转情况到用2-bit记录的思路,是否可以用n-bit来记录,以使得我们的预测跳转部件对程序跳转抖动更加容忍呢?大量的测试表明,再往上加bit的数量已经无益于准确率的提高了。 2-bit predictor是90年代比较主流的分支预测方法,4K-entry BHT, 2 bits/entry 已经基本能达到 80%~90% 左右的正确率了。\nLocal History Branch Prediction 我们现在考虑一种带有“循环节”的分支跳转模式:$TTNNTTNNTTNN\u0026hellip;$,我们的2-bit predictor没法“观察”到这种规律并加以利用。那该如何解决呢?我们可以对 每一条分支使用一个寄存器来记录该分支在过去的历史状态,只要这个历史状态很有规律,我们就能利用它,这样的寄存器称为分支历史寄存器(Branch History Register, BHR)。我们用一个n-bit的BHR记录一条指令过去n次的执行情况,对一个BHR,用多个2-bit predictor捕捉规律。\n上图中,我们用一个4-bit的BHR分支记录历史,经过足够多次的预热,当我们的BHR从1011变成0001时,对应的2-bit predictor从11变成了00。这样就捕捉了图中TTTTNTTTTN...的循环规律。 引入BHR增加了硬件的复杂性,对于BHR需要预热/训练的次数,取决于BHR的位数,位数越多需要训练次数越多,但也能预测更大的循环节的分支。为了减少硬件开销,我们也可以只用PC的k个bit来对应一个BHR,如下图所示: 这也会带来不同PC的分支可能被映射到同意BHR的问题,我们可以通过将PC进行哈希、PC与BHR拼接或异或后映射到PHT等方法提升准确度。Again,这也会增加硬件的复杂性。\nGlobal History Branch Prediction 我们通过BHR的设计已经把对同一条分支指令的动态预测处理到极致了(对于没有循环节规律的分支,我们还有办法预测吗?这是不是很像经典的机器学习问题?事实上,早在2000年,就已经提出了用感知机进行动态分支预测的方法,效果也很不错)。\n于是人们又想,分支预测除了本分支历史结果有关,是否还和其他分支有关呢?借助这样的启发式规则,发明了基于全局历史的分支预测方法。对于某一条分支指令,全局指的就是其他分支指令。考虑下面一段代码:\nif(a == 2) a = 0; if(b == 2) b = 0; if(a != bb) { ... }; 分析这一段代码,容易发现当第一条、第二条分支指令不执行时(即操作a = 0、b = 0),第三条指令一定会执行。如何记录全局分支呢?与局部历史分支预测类似的,我们用一个寄存器来记录全局历史分支跳转情况,这个寄存器称为 Global History Register(GHR)。与BHR不同的是,我们不需要对所有PC都记录一个历史分支跳转的值,而只需要一个全局的GHR就可以。一个简单的基于全局历史的分支预测如下: 事实上Global branch predictor对比2-bit predictor在small predictor sizes(我的理解是GHR的位数很少)的时候表现更差(见McFarling93) 这主要是因为不同的分支经常有这相同的“全局历史”,特别是当GHR位数较少时,他们容易被映射到相同的2-bit predictor上。\nTournament Predictors: Adaptively Combining Local and Global Predictors 显然,我们可以想办法结合 Global History Branch Prediction和 Local History Branch Prediction。Tournament Predictor就是一个经典的例子: 我们用一个selector来选择使用Global predictor的结果还是用Loacal Predictor的结果。具体这个selector应该如何实现呢?其基本思想就是用一个类似于2-bit predictor的东西来训练选哪个predictor: 根据以往的分支预测结果训练selector。\nSOTA: TAGE 很多现代CPU都使用了基于 TAGE(TAgged GEometric History Length Branch Prediction) 的思想的分支预测器。这种方法早在2006年就已提出。此后TAGE及其变种连续蝉联CBP(Championship Branch Prediction)比赛的冠军(是的,竟然还有个专门做分支预测的比赛)。\n如图所示,其基本思想是:由多个Global predictor组成,每个global predictor由PC和GHR的不同长度的值映射到BHT表中得到。在此基础上增加了tag和预测结果进行比较。给出的结果于具有最长分支历史且具有匹配标记的预测器。P(0)始终匹配,因为它不使用tag,在P(1)到P(n)中没有任何一个匹配时,使用P(0)。具体细节可以看原论文。\n跳转地址 分支预测除了需要预测该次跳转是否发生,还有一个重要问题就是我们需要预测出target address。 分支的跳转可以分为直接跳转和间接跳转。直接跳转的目标地址以立即数的形式存在指令中,那么我们就能在译码时得到预测的地址,但是由于现代处理器流水线更长,取指就可能被分成多段,所以我们需要在已得到PC时就预测跳转结果地址。对于间接跳转的分支,目标地址存在通用寄存器中,会更加难以预测。下文只简单介绍直接跳转时的 target address 预测,对于间接跳转的地址预测,可以参考《超标量处理器设计》4.3.2\n直接跳转 分为两种情况:\n分支指令不发生跳转: $target\\ addr = cur\\ PC + sizeof(fetch\\ group)$ 分支指令发生跳转: $target\\ addr = cur\\ PC + SignExd(offset)$ 第一种情况我们可以在上述 Taken/Not Taken 得到结果后,若是NT,则马上得到target addr. 而对于第二种情况,我们取指完才能得到target addr,这是不可以接受的。所以我们要在刚知道PC时就进行结果预测。如何预测呢?其实就是用一个cache来记录。由于一个进程的分支指令的地址是不会改变的,即PC为分支的指令一直都是分支指令,那么我们只要将其记录结果下来,就可以在之后的预测中在取得PC时候就通过cache得到target addr 。这里cache被称作BTB(Branch Target Buffer)。 实现也与普通的cache没有多大区别。\nreference 超标量处理器设计 姚永斌 H\u0026amp;P Computer Architecture: A Quantitative Approach coursera上Princeton的Computer Architecture 高级体系结构课程,介绍了超标量乱序多发射结构cpu等 ","permalink":"https://caaatch22.github.io/posts/branch-prediction/","summary":"\u003cblockquote\u003e\n\u003cp\u003eH\u0026amp;P那本关于分支预测的部分比较简短且表述有点晦涩,(顺便吐槽一下第五版的中文翻译,建议看英文原版)本文主要参考\u003ca href=\"https://book.douban.com/subject/26293546/\"\u003e超标量处理器设计\u003c/a\u003e,国人写的,用语符合习惯,强烈推荐!\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch1 id=\"motivation\"\u003eMotivation\u003c/h1\u003e\n\u003cp\u003e在处理器中,除了cache之外,另一个重要的内容就是分支预测,它和cache一起左右处理器的性能。以SPECint95作为benchmark,完美的cache和BP(branch-predictor)能使IPC提高两倍左右:\n\u003cimg loading=\"lazy\" src=\"/img/branch-prediction/IPC-enhance-with-perfect-BP-Cache.png\" alt=\"\" /\u003e\n\u003c/p\u003e\n\u003cp\u003e图片来自论文\u003ca href=\"https://course.ece.cmu.edu/~ece742/f12/lib/exe/fetch.php?media=chappell_ssmt99.pdf\"\u003eSSMT\u003c/a\u003e。当然,这是21世纪之前的结果了。现代处理器分支预测普遍能达到97%~98%以上的精度,在多数浮点benchmark中基本都是99%的准确率。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e为什么需要这么高的精度呢?\u003c/strong\u003e 一般情况下,分支指令的占比通常在 \u003cstrong\u003e15% 到 30%\u003c/strong\u003e 之间。对于经典五级流水线无分支预测cpu,一个branch会造成一次stall;而对于现代的superscalar且流水线级数远高于5的(一般是二十级以上)cpu,其misprediction penalty是 $M * N$ 的(M = fetch group内指令数, N = branch resolution latency,就是决定分支最终是否跳转需要多少周期)。如下图所示:\n\u003cimg loading=\"lazy\" src=\"/img/branch-prediction/mispredict-penalty.png\" alt=\"\" /\u003e\n\n我们再做一个定量实验:\n假设我们有一个\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e$ N = 20 (20\\ pipe stages), W = 5 (5\\ wide fetch) $\u003c/li\u003e\n\u003cli\u003e1 out of 5 instructions is a branch\u003c/li\u003e\n\u003cli\u003eEach 5 instruction-block ends with a branch\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e的CPU,那么我们取出500条指令需要多少个周期呢?\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e100% 预测正确率\n\u003cul\u003e\n\u003cli\u003e100 个时钟周期 (all instructions fetched on the correct path)\u003c/li\u003e\n\u003cli\u003e无额外工作\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e99% 预测正确率\n\u003cul\u003e\n\u003cli\u003e100 (correct path) + 20 (wrong path) = 120 个时钟周期\u003c/li\u003e\n\u003cli\u003e20% 额外指令被取出\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e98% 预测正确率\n\u003cul\u003e\n\u003cli\u003e100 (correct path) + 20 * 2 (wrong path) = 140 个时钟周期\u003c/li\u003e\n\u003cli\u003e40% 额外指令被取出\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e95% 预测正确率\n\u003cul\u003e\n\u003cli\u003e100 (correct path) + 20 * 5 (wrong path) = 200 个时钟周期\u003c/li\u003e\n\u003cli\u003e100% 额外指令被取出\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e可以看出,分支预测失败在现代的超标量多流水线cpu中的penalty被极大的放大了。所以分支预测的正确性就显得额外重要。\u003c/p\u003e","title":"Computer Architecture —— 分支预测"},{"content":" 介绍 什么是内存模型(Memory Model)呢?这里介绍的内存模型并非C++对象的内存排布模型,而是一个非编程语言层面的概念。我们知道在C++11中,标准引入了 std::atomic\u0026lt;\u0026gt;原子对象,同时还引入了\nmemory_order_relaxed memory_order_consume memory_order_acquire memory_order_release memory_order_acq_rel memory_order_seq_cst 这六种 memory order。引入可以让我们进行无锁编程,而如果你想要更高性能的程序,你就必须深挖这六种内存模型的含义并正确应用。(当然,在不显式指明memory order的情况下,你能保证获得正确的代码,但存在性能损失)\n内存模型 在介绍C++ memory order之前,我们先回答另一个问题。你的计算机执行的程序就是你写的程序吗? —— 显然不是的。\n原因也很简单,为了更高效的执行指令,编译器、CPU结构、缓存及其他硬件系统都会对指令进行增删,修改,重排。但要回答具体进行了什么样的修改,又是一个极其复杂的问题。或者说,整个现代体系结构,就是在保证程序正确性的前提下利用各种手段对程序优化。我们可以粗略的将其分成几个部分:\nsource code order: 程序员在源代码中指定的顺序 program code order: 基本上可以看成汇编/机器码的顺序,它可以由编译器优化后得到 execution code order: CPU执行指令顺序也不见得与汇编相同,不同CPU在执行相同机器码时任然存在优化空间。 perceived order/physical order: 最终的执行顺序。即便CPU按照某种确定指令执行,物理时间上的执行顺序仍然可能不同。例如,在超标量CPU中,一次可以fetch and decode多个指令,这些指令之间的物理执行顺序就是不确定的;由于不同层级缓存之间延时不同,以及缓存之间的通信需要等带来的不确定的执行顺序等 上图简要说明了你的源代码可能经历的优化步骤。\n这些优化的一个主要原因在于 掩盖memory access操作与CPU执行速度上的巨大鸿沟。如果没有cache,CPU每个访存指令都需要stall一两百个时钟周期,这是不可接受的。但是引入cache的同时又会带来 cache coherence等问题,这也是造成x初始为0,两个线程同时执行 x++,而x最终不一定为 2的元凶。而一个内存模型则对上述并发程序的同一块内存进行了一定的限制,它给出了在并发程序下,任意一组写操作时,可能读到的值。 不同体系结构(x86, arm, power\u0026hellip;)通过不同的内存模型来保证程序的正确性。\nbonus question: 不同等级的cache latency?\nanswer: l1: 1ns, l2: 5ns, l3: 50~100ns, main memory: 200ns\nSequential Consistency(SC) SC是最严格的内存模型,也被称作non-weak memory model。在该模型下,多线程程序执行的可做如下分析:对于每一步,随机选择一个线程,并执行该线程执行中的下一步(例如,按程序或编译的顺序)。重复这个过程,直到整个程序终止。这实际上等效于按照(程序或编译的)顺序执行所有线程的所有步骤,并以某种方式交错它们,从而产生所有步骤的单一总顺序。SC不允许重新排列线程的步骤。因此,每当访问对象时,都会检索该顺序中存储在对象中的最后一个值。(注意,内存模型中说的重新排列与编译器层面无关,编译器自然是可以讲没有data dependance的读写操作进行重排的,只要保证程序的正确性即可。内存模型中的重排指的是在硬件执行阶段,由于cache hierarchy等引发的一些问题导致指令物理执行顺序被改变)。\n也就是说,我们可以抽象出一个简单的内存结构: 在这种结构中,我们隐藏了cache与store buffer的存在,或者说SC协议允许我们无视这两个硬件。 那么,我们对于下面表格中的问题就有确定的答案:\nThread 1 Thread 2 main x = 1 y = 1 x = 0, y = 0 y\u0026rsquo; = y x\u0026rsquo; = x Spawn thread 1, 2; Wait for threads 显然,在SC模型中最终的结果只可能是:\nx = 1, y = 1; x = 0, y = 1; x = 1, y = 0; 而不会出现 x = 0; y = 0的情况。\nSC模型很美好,分析起来简单,心智负担小。但是为了保证顺序一致性,他付出了一定的性能代价。所以,并没有哪个成熟的体系结构真正使用SC模型。现代体系结构纷纷为了性能而做出了不同程度的妥协。\nx86-TSO x86-TSO(Total Store Order),它比SC更弱,但仍是现代CPU约束最强的内存模型其中之一。tso的架构可以用以下抽象来代替: 注意,这并非真实的x86的架构(毕竟连cache都没有),只是x86-tso模型保证我们得到这样的抽象。这里的store buffer(或者叫write buffer)也不一定对应着硬件上的store buffer。它也可以是cache hierarchy的一部分,他们之间有一致性协议进行约束来保证上图中的效果。\nTSO模型最重要的几个特性:\nstore buffer是FIFO的,读取线程必须读取其自身最近缓冲的写操作,如果有的话,读取的地址与该写操作一致。否则,读取操作将从共享内存中满足。 mfence 指令会清空该hardware thread的store buffer 要执行一个带锁的指令,线程必须首先获取全局锁。在指令结束时,它会清空自己的store buffer并释放锁。当一个线程持有锁时,其他线程无法读取。这基本上意味着带锁的指令强制实现了顺序一致性。 线程的可以在任何时间传播到共享内存中,除非另一个线程持有锁。 在TSO下,对于上述表格中的问题,则可能出现 x = 0, y = 0的结果:x = 1与 y = 1都被放到store buffer上而未被flush到shared memory中。\n也就是说: x86-TSO does not permit local reordering except of reads after writes to different addresses.\nthread 1, thread 2 可能会 thread 1, thread 2 write a, write b ----\u0026gt; read b, read a read b, read a write a, write b 要解决这个问题也很简单,在write后加上mfence指令即可,它会将store buffer中的存储刷到shared memory中。 这也是TSO比SC理论性能更高的原因,它舍弃了一定的正确性,来减少每次写操作都flush store buffer的开销。\nFun Fact: intel和amd从没承认过他们的x86一定符合x86-tso内存模型,但是他们进行过黑盒测试,结果证明了这一点。(就连设计者都难以reason出理论上的结果,而是通过测试证明的)\nARM and POWER Arm and power则有着更加宽松的内存模型。为了理解这样一台机器的行为,我们可以认为每个hardware thread都拥有自己的内存副本,如下图所示。所有内存副本和它们的interconnection(即除了线程以外的一切)的集合通常被称为storage subsystem。一个线程的写操作可能以任何顺序传播到其他线程,并且不同地址的写操作的传播可以任意交错,除非它们受到屏障或缓存一致性的限制。也可以将屏障视为从执行它们的硬件线程传播到每个其他线程的操作。 由于每个线程都有自己的子存储系统,它们之间的同步就需要fence进行保障。ARM和POWER提供了barrier(fence)指令(分别是 dbm 和 sync)来约束下面几种顺序:\nRead/Read之间fence:保证他们按照program order执行 Read/Write屏障: 确保在写操作被提交(因此传播并对其他人可见)之前,读操作被满足并提交。 Write/Write屏障:确保第一个写操作在第二个写操作被提交之前被提交并传播到所有其他线程。 Write/Read屏障:确保在读操作被满足之前,写操作已被提交并传播到所有其他线程。 POWER架构还提供了一个额外的“轻量级同步”指令,称为 lwsync,它比sync指令更弱,也因此可能更快。具体作用不在此处赘述。 除了屏障之外,这些体系结构还提供以下依赖关系来强制顺序:\nAddress dependency:当第一条指令读取的值用于计算第二条指令的地址时,从一个读操作到程序顺序后的读或写之间存在地址依赖。 Control dependency:当第一条指令读取的值用于计算在第二条指令之前的程序顺序条件分支的条件时,从一个读操作到程序顺序后的读/写之间存在控制依赖。 Data dependency:当第一条指令读取的值用于计算由第二条指令写入的值时,从一个读操作到程序顺序后的写之间存在数据依赖。 在ARM和POWER处理器中,read-to-read的control dependency力度较小,因为它们可以在条件分支之前进行推测性执行,从而在第一次读取之前满足第二次读取。为了增加read-to-Read的控制依赖的影响力,可以在条件分支和第二次读取之间添加一个ISB(ARM)或isync(POWER)指令。 相反,read-to-write的control dependency具有一定的影响力:在分支被提交之前,写操作不会被其他任何线程看到,因此也不会在第一次读取的值固定之前被看到。 总结一下,从一个读取到另一个读取,如果存在address dependency 或带有 ISB/isync 的控制依赖,将阻止第二个读取在第一个读取之前被满足,而纯粹的control dependency则不会。从读取到写入,地址、控制或数据依赖都将阻止写入在读取的值固定之前对任何其他线程可见。\nC++ memory order 对于 weak memory model,特别是上面介绍的ARM的内存模型,想要精细的控制程序的允许重排的程度需要非常大的心智负担。(光是不同体系结构,不同等级的fence指令就令人望而却步)。因此C++11标准提供的六种memory order就是从语言层面来约束最终希望达到的对程序被优化程度的限制。现在我们再看这几种memory order就比较清晰了。(以下说法并不严谨,但是作为程序员的take away完全足够了。\nMemory Order Explaination memory_order_relaxed 表示这个R/W操作除了原子性外没有任何其他限制,他可能会被重排到程序的任何位置(当然,编译器不会允许将他重排到同一个线程对同一个原子变量写操作的前面,这违背了正确性) memory_order_consume (只用于读) 后面依赖此原子变量的访存指令不允许重排至此条指令之前。 注意,当前标准中的memory_order_consume是没有实际用处的, 一般情况下不要使用 memory_order_acuqire (只用于读) 后面访存指令不允许重排至此条指令之前 memory_order_release (只用于写) 前面访存指令不允许重排至此条指令之后。当此条指令的结果对其他线程可见后,之前的所有指令都可见 memory_order_acq_rel acquire + release语意 memory_order_seq_cst 满足sequential_consistency内存模型 在默认情况下,std::atomic\u0026lt;\u0026gt;相关函数总是选用 std::memory_order_seq_cst,它能有效地帮我们避免错误。但是,我们既然都使用 atomic而不是 mutex了,自然是对性能有较高要求。 从上图中可以看出,memory_order_relaxed的写性能和 non-atomic几乎没有差别,而 seq_cst则要慢许多。\n另一方面,使用不同等级的 memory order也能更好的表达你的代码的意图:\nrelaxed-model 最典型的例子就是一个线程安全的计数器。\nstd::atomic\u0026lt;size_t\u0026gt; counter; counter.fetch_add(1, std::memory_order_relaxed); 你只是单纯的记录某件事情发生了几次。常用的还有智能指针中的引用计数的递增递减等\nacuqire-release model 如果代码改成:counter.fetch_add(1, std::memory_order_release);那么这个counter就极有可能是某个数组的下标:你对某一个数组append了一个数,然后你才将其release(发布),告诉系统不允许将你对数组的append操作重排到counter增加之后;这样在其他线程中,其他线程就无法因为counter没更新而占有你已经append的位置。\nacuqire-release 经常成对出现,因为他们共同表示了这样的一个模型: 图中 {a, b}是我们需要在不同线程之间同步的值,那么线程1准备好 {a, b}后,release(发布) x,即 x.store(1, memory_order_release),这时我们保证了 {a, b}值的更新一定是 x = 1的时候可见的(因为{a, b}的更新操作不会被重排到x.store之后);那么在读线程,我们就可以根据 x 判断 {a, b}是否被更新:\nwhile(x.load(memory_order_acquire) != 1) ; [a\u0026#39;, b\u0026#39;] = {a, b}; // acuire语义保证了这句话不会被重排到x.load之前 还有就是acq_sel_model了,这三种模型就是我们用std::atomic最常用的三种memory order.\n其他 你可能已经注意到了,X86-TSO内存模型非常严格,它已经为我们提供了 all load are acquire-loads, all stores are release-stores all read-modify-write operations are acquire-release 也就是说x86平台下不存在真正的memory-order-relexed\n为什么atomic\u0026lt;\u0026gt;比mutex更高效?最终不都是依赖于硬件提供的 barrier 以及 CAS, test and set等low level primitive吗? atomic 做的事情:原子指令修改内存,内存栅栏保障修改可见,必要时锁总线。 mutex 大致做的事情:短暂原子 CAS(compare and set) 自旋如果未成功上锁,futex(\u0026amp;lock, FUTEX_WAIT\u0026hellip; ) 退避进入阻塞等待直到 lock 值变化时唤醒。futex 在设计上期望做到如果无争用,则可以不进内核态,不进内核态的 fast path 的开销等价于 atomic 判断。内核里维护按地址维护一张 wait queue 的哈希表,发现锁变量值的变化(解锁)时,唤醒对应的 wait queue 中的一个 task。wait queue 这个哈希表的槽在更新时也会遭遇争用,这时继续通过 spin lock 保护。 说白了就是mutex会陷入内核态(大部分情况下),而内核使用比较复杂的算法维护锁\nstd::atomic\u0026lt;\u0026gt;一定是无锁的吗?\nwrong!\n举个例子:\nlong x; struct A {long x;} struct B {long x; long y;} struct C {long x; long y; long z;} 那么atomic, T取x, A, B, C的时候哪些是lock-free,哪些不是呢?\n我们可以用std::atomic::is_lock_free()找出答案。上述选项中,T = x, A的时候一定是lock_free的,T = C的时候一定不是lock_free的。\nC++17提供std::is_always_lock_free可以在编译器进行判断,如果为false不代表一定是 no lock_free的\nreference 介绍atomic的talk,比较全面 Sutter的talk, atomic weapons Memory Models for C/C++ Programmers ","permalink":"https://caaatch22.github.io/posts/c-%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B--%E7%8E%B0%E4%BB%A3architecture%E7%9A%84%E5%A6%A5%E5%8D%8F/","summary":"\u003c!-- cover:\n image: img/memory-model/title-graph.png\n alt: \"一本介绍缓存一致性的书\"\n caption: \"~~图文无关~~\"\n hidden: true --\u003e\n\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cp\u003e什么是内存模型(Memory Model)呢?这里介绍的内存模型并非\u003ca href=\"https://citeseerx.ist.psu.edu/document?repid=rep1\u0026amp;type=pdf\u0026amp;doi=160489e8b12cd9a44cbff0cd85fb6aa05437d1ac\"\u003eC++对象的内存排布模型\u003c/a\u003e,而是一个非编程语言层面的概念。我们知道在C++11中,标准引入了 \u003ccode\u003estd::atomic\u0026lt;\u0026gt;\u003c/code\u003e原子对象,同时还引入了\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ememory_order_relaxed\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ememory_order_consume\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ememory_order_acquire\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ememory_order_release\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ememory_order_acq_rel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ememory_order_seq_cst\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这六种 \u003ccode\u003ememory order\u003c/code\u003e。引入可以让我们进行\u003cstrong\u003e无锁编程\u003c/strong\u003e,而如果你想要更高性能的程序,你就必须深挖这六种内存模型的含义并正确应用。(当然,在不显式指明memory order的情况下,你能保证获得正确的代码,但存在性能损失)\u003c/p\u003e\n\u003ch2 id=\"内存模型\"\u003e内存模型\u003c/h2\u003e\n\u003cp\u003e在介绍C++ memory order之前,我们先回答另一个问题。\u003cem\u003e你的计算机执行的程序就是你写的程序吗? —— 显然不是的。\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e原因也很简单,为了更高效的执行指令,编译器、CPU结构、缓存及其他硬件系统都会对指令进行增删,修改,重排。但要回答具体进行了什么样的修改,又是一个极其复杂的问题。或者说,整个现代体系结构,就是在保证程序正确性的前提下利用各种手段对程序优化。我们可以粗略的将其分成几个部分:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003esource code order:\u003c/strong\u003e 程序员在源代码中指定的顺序\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eprogram code order:\u003c/strong\u003e 基本上可以看成汇编/机器码的顺序,它可以由编译器优化后得到\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eexecution code order:\u003c/strong\u003e CPU执行指令顺序也不见得与汇编相同,不同CPU在执行相同机器码时任然存在优化空间。\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eperceived order/physical order:\u003c/strong\u003e 最终的执行顺序。即便CPU按照某种确定指令执行,物理时间上的执行顺序仍然可能不同。例如,在超标量CPU中,一次可以fetch and decode多个指令,这些指令之间的物理执行顺序就是不确定的;由于不同层级缓存之间延时不同,以及缓存之间的通信需要等带来的不确定的执行顺序等\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/memory-model/optimization-by-different-parts-of-computer.png\" alt=\"optimizations-by-compyter\" /\u003e\n\u003c/p\u003e\n\u003cp\u003e上图简要说明了你的源代码可能经历的优化步骤。\u003c/p\u003e\n\u003cp\u003e这些优化的一个主要原因在于 掩盖memory access操作与CPU执行速度上的巨大鸿沟。如果没有cache,CPU每个访存指令都需要stall一两百个时钟周期,这是不可接受的。但是引入cache的同时又会带来 \u003ccode\u003ecache coherence\u003c/code\u003e等问题,这也是造成x初始为0,两个线程同时执行 \u003ccode\u003ex++\u003c/code\u003e,而x最终不一定为 \u003ccode\u003e2\u003c/code\u003e的元凶。\u003cstrong\u003e而一个内存模型则对上述并发程序的同一块内存进行了一定的限制,它给出了在并发程序下,任意一组写操作时,可能读到的值。\u003c/strong\u003e 不同体系结构(x86, arm, power\u0026hellip;)通过不同的内存模型来保证程序的正确性。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003ebonus question: 不同等级的cache latency?\u003c/p\u003e\n\u003cp\u003eanswer: l1: 1ns, l2: 5ns, l3: 50~100ns, main memory: 200ns\u003c/p\u003e","title":"C++内存模型 —— 现代Architecture的妥协"},{"content":" 本文不会介绍cache的组织形式等基本内容,但也算不上什么\u0026quot;Advanced\u0026quot;。主要包含一些从硬件层面优化cache的手段。\n优化cache的几种方法 pipeline caches 上图为教科书上经常出现的cache形式(2-way associative为例),它很精炼的解释了cache的实现。但也稍微引入了些“误导”:\n图中v、tag和data部分画在连续的一行上,仿佛硬件上他们就是同一块 SRAM 的不同bit 图中识别tag与data是并行完成的,这很好,某种意义上能降低时延;但我们经常遗忘一个事实,只有读cache的时候我们才能这么操作(或者说在写cache时,读取data block是没有意义的) 对于第一点,在实际的实现当中,tag和data部分都是分开放置的,tag一般是由一种叫CAM(Context-Addressable Memory)的材料构成。当然,这与pi不pipeline没什么关系;\n读cache主要就两个部分:比较tag,获取data;我们暂且不考虑以pipeline的方式优化,那么serial的先比较tag再读data一定不如parallel的方式进行吗?当我们并行的读取tag和data的时候,我们会发现,读出来的data有可能没用(没有匹配的tag);并且,在n-way set associate cache中,我们会浪费的读出$n-1$个data项;这给我们什么启示呢?如果我们串行的读cache,那么我们可以在比较tag阶段就知道我们想要的数据在不在cache当中;更有意义的是,根据tag比较的结果,我们就知道哪一路的数据是需要被访问的(提前知道了在n-way中的哪一way),那么我们访问data block时,就无需多路选择器,直接访问指定的way,将其他way的data访问的使能信号置为无效,这种做法的优点在于有效减小功耗。\nserial的做法肯定比parallel的延时要大,若这时访问cache处于处理器的critical path(关键路径)上,我们可以再将其进行流水线化。 我们现在再来看看写cache时的情况:\n写cache时,只有通过tag比较,确认要写的地址在cache中后,才可以写data SRAM,在主频较高的处理器中,这些操作很难在一个周期内完成,这也要求我们将其流水线化。下图为对cache进行写操作使用的流水线示意图:\n在上图的实现方式中,store第一个周期读取Tag并进行比较,根据比较的结果,在第二个周期选择是否将数据写到Data SRAM中。还需要注意的是,当执行load指令时,它想要的数据可能正好在store指令的流水线寄存器中(RAW的情况;上图中的DelayedStoreData寄存器),而不是来自于Data SRAM。因此需要一种机制检测这种情况,这需要将load指令所携带的地址和store指令的流水线寄存器(即DelayedStoreAddr寄存器)进行比较,如果相等,那么就将store指令的数据作为load指令的结果。\n由此可以看出,对写D-Cache使用流水线之后,不仅增加了流水线本身的硬件,也带来了其他一些额外的硬件开销。其实。不仅在Cache中有这种现象,在处理器的其他部分增加流水线的级数,也会伴随着其他方面的硬件需求,因此过深的流水线带来的硬件复杂度是非常高的,就像Intel的Pentium 4处理器,并不会从过深的流水线中得到预想的好处。当然,cache的流水线化已经是一种广泛使用的用于降低latency的方法了。\nwrite buffers 我愿称之为buffer of buffer,本来cache就起buffer的作用了,但我们再加一个buffer,如下图所示: 这和多一级的cache有什么不同呢?这是一个专门为写操作设计的buffer(注意:load也可能造成写操作)。原因在于我们知道写通常比读更慢,特别对于write-through来说;其次,当上层cache满后,需要先将dirty cache line写回下层cache,再读取下层cache中的数据。若下层cache只有一个读写端口,那么这种串行的过程导致D-Cache发生缺失的处理时间变得很长,此时就可以采用write buffer来解决这个问题。脏状态的cache-line会首先放到写级存中,等到下级存储器有空闲的时候,才会将写缓存中的数据写到下级存储器中。\n对于write buffer,我们还可以对其进行 合并(merging) 操作。所谓merging,指的是将在同一个cache-line上的数据一并写入下层cache中,而非多次写入同一个cache-line。 上图中的右侧表示了一个采用了merging write buffer策略的写缓冲区。\ncritial word first and early restart 先来看一下cache miss时的cpu: 图中展示了一个blocking cache在cache miss时,cpu stall,而后cache将需要取得的cache-line放入后,cpu resume的timeline。我们可以发现,若我们只需要cache-line中的第3个word,cpu完全可以提早resume。如下图所示: 这就是early restart,而critial word first指的是,是在此基础上,在取cache-block时,不按照0~7 word的顺序而是按照3,4,5,6,7,0,1,2的顺序获取,配合early restart以减小miss penalty。\n当然,这种优化手段一般在cache-block size越大的时候效果越好。\nnon-blocking caches 本节主要参考了超标量处理器设计9.6.2\nblocking cache:在D-Cache发生缺失并且被解决之前将D-Cache与物理内存之间的数据通路被锁定,只处理当前这个缺失的数据,处理器不能够再执行其他的load/store指令。 这是最容易实现的一种方式,但是考虑到D-Cache缺失的处理时间相对是比较长的,这段时间容易阻塞其他load/store指令的执行,特别是在超标量处理器中,一个周期可能发射多个load/store指令,这样的阻塞就大大减少了程序执行时可以寻找的并行性。\n为了解决这个问题,Kroft提出了non-blocking cache,(aka lookup-free cache, aka out-of-order-memory system),如下图所示:\n其实就是在cache miss之后,cpu继续发送load/store指令;那么我们可能会有hit after miss,也可能有miss after miss,甚至多次miss连续发生。\n我们来看看我们需要增加哪些结构以支持non-blocking cache。\n采用这种方法之后,load/store指令的D-Cache缺失被处理完的时间可能和原始的指令顺序不一样了。举例来说,两条顺序的指令load1和load2都发生了D-Cache缺失,但是load1指令可能需要到物理内存中才能够找到需要的数据,而load2指令在L2 Cache中就可以找到所需的数据,因此load2指令会更早地得到需要的数据。要处理这个问题,就需要保存那些已经发生缺失的load/store指令的信息,这样当这些缺失的load/store指令被处理完成,将得到的数据写回到D-Cache时,仍然可以知道哪条load/store 指令需要这个数据。\n这个保存原始load/store的结构一般称为MSHR(Miss Status/information Holding Register),如下图所示:\n上图中(a)称为MSHR的本体,它只用来保存所有产生首次缺失的load/store 指令的信息,它包括三项内容。\n首次缺失(Primary Miss): 对于一个给定的地址来说,访问D-Cache时第一次产生的缺失称为首次缺失。 再次缺失(Secondary Miss): 在发生了首次缺失并且没有被解决完毕之前,后续的访问存储器的指令再次访问这个发生缺失的Cache line,这时就称为再次缺失。 V: valid bit用来指示当前的entry是否被占用,当首次缺失发生时,MSHR本体中的一个表项会被占用,此时valid位会被标记为1。当所需要的Cache line从下级存储器中被取回来时,会释放MSHR本体中被占用的表项,因此valid位会被清零。 Block Address: 指的是Cache line 中数据块(data block)的公共地址。每次当load/store 指令发生D-Cache缺失时,都会在MSHR的本体中在找它所需的数据块是否处于正在被取回的过程中,这需要和Block Address这一项进行比较才可以知道,通过这种方式,所有访向同一个数据块的指令只需要处理一次就可以了,这样可以避免存储器带宽的浪费。 Issued: 表示发生首次缺失的load/store指令是否已经开始处理。由于存储器的带宽有限,占用MSHR本体的首次缺失不一定马上就会被处理。 上图(b)称为LOAD/STORE Table,保存不论是发生首次缺失还是再次缺失的load/store指令,它包括五项内容。\nV: valid位,表示一个entry是否被占用。 MSHR entry: 表示一条发生缺失的load/store指令属于MSHR本体中的哪个表项,由于产生D-Cache缺失的许多load/store指令都可能对应着同一个Cache line,为了避免重复地占用下级存储器的带宽,这些指令只会占据MSHR本体中的一个表项,但是它们需要占用LOAD/STORE Table中不同的表项,这样才能够保证每条指令的信息都不会丢失。同时也会记录下这些指令在MSHR本体中占据了哪个表项,这样当一个Cache line被从下级存储器取回来时,通过和MSHR本体中的block address进行比较,就可以知道它在MSHR本体中的位置。然后就可以在LOAD/STORE table中找到哪些load/store指令属于这个Cache line。 Dest.register: 对于load指令来说,这部分记录目的寄存器的编号(如果采用了寄存器重命名,就需要记录物理寄存器的编号),就可以将对应的数据送到这部分所记录的寄存器中;而对于store指令来说,这部分用来记录store指令在Store Buffer中的编号。(这与超标量的处理有关,暂时不展开) Type: 记录访问存储器指令的类型,这部分取决于具体指令集的实现,对于MIPS来说,访向存储器的指令类型包括LW(Load Word)、LH(Load Half word)、LB(Load Byte) 、SW(Store Word)、SH(Store Half word)和SB(Store Byte),通过记录指令的类型,才能够使D-Cache缺失被解决之后,能够继续正确地执行指令。 Offset: 访向存储器的指令所需要的数据在数据块中的位置,例如对于大小是64字节的数据块来说,这部分的位宽需要6位。 通过MSHR本体和LOAD/STORE Table的配合,可以支持非阻塞的操作方式,当一条访向存储器的指令发生D-Cache缺失时,首先会在找MSHR的本体,这个查找过程是将发生D-Cache缺失的地址和MSHR中所有的block adress进行比较,根据比较的结果,处理如下:\n如果发现有相等的表项存在,则表示这个缺失的数据块正在被处理,这是再次缺失,此时只需要将这条访问存储器的指令写到LOAD/STORE Table中就可以了。 如果没有发现相等的项,则表示缺失的数据块是第一次被处理,也就是首次缺失。此时需要将这条访向存储器的指令写到MSHR本体和LOAD/STORETable两个地方。 如果MSHR本体或者LOAD/STORE Table 中的任意一个已经满了,则表示不能够再处理新的访问存储器指令,此时应该暂停流水线继续选择新的load/store指令送到FU中执行,等待之前的某个D-Cache缺失被解决完毕此时MSHR或者LOAD/STORE Table中就会有空闲的空间了,允许流水线继续执行。在现实世界中的处理器一般出于硅片面积和功耗的考虑,MSHR本体的容量不会很大,多为4~8个,也就是 处理器支持4~8个D-Cache缺失同时进行处理 。\nmultibank caches 一般cache上的读写端口都只有一个,这样限制了我们的并行性。那么我们增加cache上的读写端口如何呢?以双端口Cache为例,在这种设计中,所有在cache中的控制通路和数据通路都需要进行复制:两套地址解码器,两个多路选择器,比较器的数量多出一倍,两个Aligner(完成字节或半字读取),更重要的是Tag SRAM和Data SRAM的每个cell都需要同时支持两个并行的读取操作。这种方法增大了芯片面积,且功耗随之上升,一般不会在实际的处理器上使用。\n那么我们如何既要一次性读取多个数据又要稍微小的硬件代价呢?multibank cache可以实现这样的功能: 我们把cache分成多个不重合的bank,每个bank还是采用单端口的设计。若在一个周期中,我们所要操作的数据“均匀的”分布在不同的bank当中,我们就可以一次性对多块数据进行操作。这时候,我们只需要增加一些控制通路,但是不用让每个cell都支持并行读取,有效地减小了硬件复杂度。\n影响这种multibank cache性能的关键因素是bank-conflict:它指的是 若我们某一周期内对cache的操作数据集中在一个bank上,我们就必须串行的完成访问,相当于丧失了bank的作用。 如何降低bank-conflict呢?一种启发式的切分bank的方法是interleaving得把连续的cache-line分到不同的bank中,如下图: 主要的思想就是我们经常同时对相连得cache-line进行访问,那么把它们放在不同的bank里就能避免bank conflict。\n值得一提的是,multibank也是一种提高main memory bandwidth的技巧。\nsummary 还有一些软件硬件技巧,如prefetch,分块,way-predicting caches等就不一一展开了。\nworking with TLB: VIPT(Virtual Indexed physical Tagged) 在学习TLB相关知识后,我们常认为cpu关于地址翻译的过程是如下图所示的(忽略ASID):将virtual address经由TLB/page table翻译为物理地址,在利用该物理地址在cache hierarchy中获取数据。\n这种设计在理论上是完全没有问题的,但在真实的处理器中却很少被采用。因为他完全串行了TLB和Cache的访问。事实上我们在上图中可以看出,VA到PA的转化时,低位的offset是完全没有变化的。若物理地址中寻址Cache的部分使用offset就足够的话,那么就不需要等到TLB得到物理地址后再去寻址Cache,而是直接使用虚拟地址的offet部分。 如下图所示:\n相当于用VA做Index,用PA做Tag。这就是VIPT(virtual indexed physical tagged),当然,这只是VIPT的一种情况。\n假设每个Cache line中包含$2^b$个字节数据,Cache Line的数量是$2^L$,$k$为页大小。那么我们会有几种情况:\n先看前两种情况,实现比较简单,他们的virtual index部分实际上与physical index是一样的(应为在block-offset内,不参与地址翻译)。\n但前两种情况也有缺点,就是这种结构限制了cache的大小,cache的byte数不能超过$2^k(一般为4KB)$。我们现在随便一个l1i-cache就有64~128KiB。要想使用更大的Cache,在不考虑图中(c)结构时,可以采用增加way的个数,这不会引起寻址Cache需要位数的增加。但我们粗略的估算一下,若cache为128KiB,页大小为4KiB,那么我们需要32way的L1i-Cache。根据经验我们不会使用way数这么大的cache,因为这会使得比对tag的时延大大上升。\n那么,如何才能摆脱cache容量受限制这一问题呢?解决方案就是让$L+b\u0026gt;k$,也就是图中(c)的设计,这种设计就是真正的virtual-indexed。但这会遇到 重名(aliasing) 问题:不同的虚拟地址会对应同一个物理地址。例如在页大小为4KB的系统中,有两个不同虚报地址 VA1和VA2,映射到同一个物理地址PA。有一个直接映射结构的Cache,容量为8KB,需要13位的index才可以对其进行寻址,如下图: 此时va1与va2的cache index不同,也就造成了Cache中的不同位置存放着物理存储器中的同一位置中的数据。这造成了Cache的浪费,且还会有一致性上的问题。 如何解决呢?可以用bank的方式或者利用下层cache做标记的方式。这里就不仔细展开了\nreference youtube上一个讲memory hierachy design的系列 超标量处理器设计 姚永斌 H\u0026amp;P Computer Architecture: A Quantitative Approach coursera上Princeton的Computer Architecture 高级体系结构课程,介绍了超标量乱序多发射结构cpu等 ","permalink":"https://caaatch22.github.io/posts/advanced-cache/","summary":"\u003cblockquote\u003e\n\u003cp\u003e本文不会介绍cache的组织形式等基本内容,但也算不上什么\u0026quot;Advanced\u0026quot;。主要包含一些从硬件层面优化cache的手段。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch1 id=\"优化cache的几种方法\"\u003e优化cache的几种方法\u003c/h1\u003e\n\u003ch2 id=\"pipeline-caches\"\u003epipeline caches\u003c/h2\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/advanced-cache/2way-set-associative.png\" alt=\"2way-set-associative-cache\" /\u003e\n\u003c/p\u003e\n\u003cp\u003e上图为教科书上经常出现的cache形式(2-way associative为例),它很精炼的解释了cache的实现。但也稍微引入了些“误导”:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e图中\u003ccode\u003ev\u003c/code\u003e、\u003ccode\u003etag\u003c/code\u003e和\u003ccode\u003edata\u003c/code\u003e部分画在连续的一行上,仿佛硬件上他们就是同一块 SRAM 的不同bit\u003c/li\u003e\n\u003cli\u003e图中识别\u003ccode\u003etag\u003c/code\u003e与\u003ccode\u003edata\u003c/code\u003e是并行完成的,这很好,某种意义上能降低时延;但我们经常遗忘一个事实,只有\u003cem\u003e读cache\u003c/em\u003e的时候我们才能这么操作(或者说在写cache时,读取data block是没有意义的)\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e对于第一点,在实际的实现当中,tag和data部分都是分开放置的,tag一般是由一种叫\u003ca href=\"https://en.wikipedia.org/wiki/Content-addressable_memory\"\u003eCAM(Context-Addressable Memory)\u003c/a\u003e的材料构成。当然,这与pi不pipeline没什么关系;\u003c/p\u003e\n\u003cp\u003e读cache主要就两个部分:比较tag,获取data;我们暂且不考虑以pipeline的方式优化,那么serial的先比较tag再读data一定不如parallel的方式进行吗?当我们并行的读取tag和data的时候,我们会发现,读出来的data有可能没用(没有匹配的tag);并且,在n-way set associate cache中,我们会浪费的读出$n-1$个data项;这给我们什么启示呢?如果我们串行的读cache,那么我们可以在比较tag阶段就知道我们想要的数据在不在cache当中;更有意义的是,根据tag比较的结果,我们就知道哪一路的数据是需要被访问的(提前知道了在n-way中的哪一way),那么我们访问data block时,就无需多路选择器,直接访问指定的way,将其他way的data访问的使能信号置为无效,这种做法的优点在于\u003cstrong\u003e有效减小功耗\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003eserial的做法肯定比parallel的延时要大,若这时访问cache处于处理器的critical path(关键路径)上,我们可以再将其进行流水线化。\n\u003cimg loading=\"lazy\" src=\"/img/advanced-cache/pipelined-cache.png\" alt=\"pipeline cache\" /\u003e\n\u003c/p\u003e\n\u003cp\u003e我们现在再来看看写cache时的情况:\u003c/p\u003e\n\u003cp\u003e写cache时,只有通过tag比较,确认要写的地址在cache中后,才可以写data SRAM,在主频较高的处理器中,这些操作很难在一个周期内完成,这也要求我们将其流水线化。下图为对cache进行写操作使用的流水线示意图:\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/advanced-cache/pipelined-cache-write.png\" alt=\"pipeline cache in write\" /\u003e\n\u003c/p\u003e\n\u003cp\u003e在上图的实现方式中,store第一个周期读取Tag并进行比较,根据比较的结果,在第二个周期选择是否将数据写到Data SRAM中。还需要注意的是,当执行load指令时,它想要的数据可能正好在store指令的流水线寄存器中(RAW的情况;上图中的DelayedStoreData寄存器),而不是来自于Data SRAM。因此需要一种机制检测这种情况,这需要将load指令所携带的地址和store指令的流水线寄存器(即DelayedStoreAddr寄存器)进行比较,如果相等,那么就将store指令的数据作为load指令的结果。\u003c/p\u003e\n\u003cp\u003e由此可以看出,对写D-Cache使用流水线之后,不仅增加了流水线本身的硬件,也带来了其他一些额外的硬件开销。其实。不仅在Cache中有这种现象,在处理器的其他部分增加流水线的级数,也会伴随着其他方面的硬件需求,因此过深的流水线带来的硬件复杂度是非常高的,就像Intel的Pentium 4处理器,并不会从过深的流水线中得到预想的好处。当然,cache的流水线化已经是一种广泛使用的用于降低latency的方法了。\u003c/p\u003e\n\u003ch2 id=\"write-buffers\"\u003ewrite buffers\u003c/h2\u003e\n\u003cp\u003e我愿称之为buffer of buffer,本来cache就起buffer的作用了,但我们再加一个buffer,如下图所示:\n\u003cimg loading=\"lazy\" src=\"/img/advanced-cache/write-buffer.png\" alt=\"write buffer\" /\u003e\n\n这和多一级的cache有什么不同呢?这是一个专门为写操作设计的buffer(注意:load也可能造成写操作)。原因在于我们知道写通常比读更慢,特别对于write-through来说;其次,当上层cache满后,需要先将dirty cache line写回下层cache,再读取下层cache中的数据。若下层cache只有一个读写端口,那么这种串行的过程导致D-Cache发生缺失的处理时间变得很长,此时就可以采用write buffer来解决这个问题。脏状态的cache-line会首先放到写级存中,等到下级存储器有空闲的时候,才会将写缓存中的数据写到下级存储器中。\u003c/p\u003e\n\u003cp\u003e对于write buffer,我们还可以对其进行 \u003cstrong\u003e合并(merging)\u003c/strong\u003e 操作。所谓\u003ccode\u003emerging\u003c/code\u003e,指的是将在同一个cache-line上的数据一并写入下层cache中,而非多次写入同一个cache-line。\n\u003cimg loading=\"lazy\" src=\"/img/advanced-cache/merging-write-buffer.png\" alt=\"merging write buffer\" /\u003e\n\u003c/p\u003e\n\u003cp\u003e上图中的右侧表示了一个采用了merging write buffer策略的写缓冲区。\u003c/p\u003e\n\u003ch2 id=\"critial-word-first-and-early-restart\"\u003ecritial word first and early restart\u003c/h2\u003e\n\u003cp\u003e先来看一下cache miss时的cpu:\n\u003cimg loading=\"lazy\" src=\"/img/advanced-cache/cpu-cache-miss.png\" alt=\"timeline in cache miss\" /\u003e\n\n图中展示了一个blocking cache在cache miss时,cpu stall,而后cache将需要取得的cache-line放入后,cpu resume的timeline。我们可以发现,若我们只需要cache-line中的第3个word,cpu完全可以提早resume。如下图所示:\n\u003cimg loading=\"lazy\" src=\"/img/advanced-cache/early-restart.png\" alt=\"early restart\" /\u003e\n\u003c/p\u003e","title":"Computer Architecture —— 高级缓存技术"},{"content":" 众所周知,C++标准库的 unordered_map在性能上向来不是一个好的选择。开源市场上有非常多的高性能哈希表可供选择,phmap继承自 absl-hashmap,有着非常好的插入、查找性能。在著名的Comprehensive C++ Hashmap Benchmarks 2022榜单中名列前茅。事实上,我比对了 phmap::flat_hash_map与榜单中综合性能第一的 ankerl::unordered_dense::map,我的benchmark中只有遍历哈希表时,flat_hash_map的性能低于 unordered_dense::map,其余无论是插入还是随即查找,大部分情况下 flat_hash_map的性能都更优。本文简单介绍了 flat_hash_map相关情况,以及一些使用上的建议与坑点。\nflat_hash_map 和 node_hash_map区别 phmap中有提供了两类哈希表,其内部布局示意图如下:\n由上图(忽略了bucket的细节)可以看出,flat_hash_map的最大的优点在于\nnode之间的内存是连续的(虽然可能中间存在空node),遍历的时候对cache更加友好 并且相比于 node_hash_map版少一次寻址过程(std::unordered_map的设计与 node_hash_map)相同。 而 flat_*系列的缺点就是在 rehash的时候:\n会引发原来的value失效(这里的失效指的是原来的那个对象所对应的内存失效,而不是value所包含的内容失效,例如,value是一个指针,那它的值——所指向的对象,不会受到影响)。举个例子: flat_hash_map\u0026lt;int, Data\u0026gt; mp; node_hash_map\u0026lt;int, Data\u0026gt; nodemp; mp[0] = Data(); nodemp[0] = Data(); auto\u0026amp; mp0 = mp[0]; auto\u0026amp; nodemp0 = nodemp[0]; // tigger rehash for (int i = 1; i \u0026lt;= 10; i ++) { mp[i] = Data(); nodemp[i] = Data(); } assert(std::addressof(mp[0]) != std::addressof(mp0)); assert(std::addressof(nodemp[0]) == std::addressof(nodemp0)); 原因就是 flat_hash_map的内存布局导致的。而 node_hash_map或者 std::unordered_map就保证不会出现这种情况,因为当他们rehash的时候,只需要将bucket内的指针重新分配,指针的值还是指向原来的 node\u0026lt;key, value\u0026gt;.\n由上述原因引发的性能问题: 当 node\u0026lt;pair, value\u0026gt;很大的时候,flat_hash_map每次 rehash 需要重新构造的开销大,而 node_hash_map只需要重新构造相同数量的指针。并且遍历时也因为两个node之间相隔较大使得 cache locality 下降。 使用建议(单线程) 绝大多数情况下直接用 flat_hash_map:\nint -\u0026gt; int int -\u0026gt; string string-\u0026gt;int string(sso) -\u0026gt; string(sso) string(none-sso)-\u0026gt;string(none-sso) 少数情况用 node_hash_map:\nkey -\u0026gt; Large Data 这种情况强烈建议用 flat_hash_map\u0026lt;key, ptr\u0026lt;Large Data\u0026gt;\u0026gt;代替\n有意思的一点是为什么经过实测 string(none-sso) -\u0026gt; string(none-sso)这种情况下仍然是 flat_hash_map更快一点,难道string(none-sso)不是Large Data吗?\n事实上,string 虽然可能很大,但是其 raw data存在另外分配的一个空间(超过短字符串优化限定大小时),所以当rehash的时候,新生成的string只需要move由来的string就可以了。下图展示了其内存布局:\n另外,如果提前知道需要插入的pair的数量(或者大致数量),特别是只作为一个look-up table使用时(一次性插入,只查询或更改已存在的key),那么使用提前 reverse是个明智的选择,这能有效提高哈希表的性能。一般需要reserve成需要插入数量的 两倍 而不是一倍 ,因为与vector这样的容器不同的是,为了防止过于频繁的碰撞,会在每个bucket还没满的时候就进行扩容\nparallel_flat_hash_map phmap库除了包含继承自 abseil的 {flat/node}_hash_{set/map}外,还有支持并发操作下的 parallel_*系列。以 parallel_flat_hash_map为例,其实现如下图:\n一个 parallel_flat_hash_map有(默认 2^4 = )16 个 子flat_hash_map组成。这样做可以\n更加细粒度的使用锁,从而减少锁之间的等待 一次rehash一个 submap,减小修改操作的开销 基本的使用方法也很简单,需要在模板参数上指定一个锁,一般使用(std::mutex或者std::shared_mutex,官方文档说shared_mutex性能更优,我个人的测试下mutex性能更优,但是相差不大)。其余的使用上与非parallel版本基本一致,以下是简单的例子:\n// 前两个模板参数是key, value,最后两个模板参数是 N (2^N表示子map的数量, N默认为4), 和指定的锁 phmap::parallel_flat_hash_map\u0026lt;int, int, phmap::priv::hash_default_hash\u0026lt;int\u0026gt;, phmap::priv::hash_default_eq\u0026lt;int\u0026gt;, phmap::priv::Allocator\u0026lt;std::pair\u0026lt;constint, int\u0026gt;\u0026gt;, 4, std::mutex\u0026gt; para_mp; para_mp[0] = 1; para_mp.subcnt(); // return sub map count 经过测试,在插入和修改操作混合的情况下(随机插入1000000个数,非重复数字在600000以上),parallel_mp耗时 80ms 左右,而手动给 flat_hash_map 加锁耗时 250ms左右。\n另外,既然 parallel_*是由多个子map形成,那个在特定条件下,我们甚至可以无需锁就能保证线程安全。\n具体操作如下:\n已知所有的需要插入的key 每个线程需要有一个线程idx标识,线程数量少于 子map数量 对于特定的key,得到hashval后将这个key的所有相关操作绑定到某个特定线程上进行。 这样就可以实现无锁并发\n我们可以通过下面这个例子具体观察:\ntemplate \u0026lt;typename K, typename V\u0026gt; using para_mp = parallel_flat_hash_map\u0026lt;K, V\u0026gt;; const int n = 1e6; auto const keys = random_vector\u0026lt;int\u0026gt;(1, n); constexpr int64_t num_threads = 8; auto thread_fn = [\u0026amp;num_threads](para_mp\u0026lt;int, int\u0026gt;\u0026amp; mp, vector\u0026lt;int\u0026gt; const\u0026amp; keys, int thread_idx) { size_t modulo = mp.subcnt() / num_threads; for (int64_t i = 0; i \u0026lt;keys.size(); ++i) { int key = keys[i]; size_t hashval = mp.hash(key); size_t idx = mp.subidx(hashval); if (idx / modulo == thread_idx) // if the submap is suitable for this thread { mp[key] = rng(); // insert random value } } }; std::unique_ptr\u0026lt;std::thread\u0026gt; threads[num_threads]; for (int i = 0; i \u0026lt; num_threads; i++) { threads[i].reset( new std::thread(thread_fn, std::ref(mp), std::cref(keys), i)); } timer.reset(); for (int64_t i =0; i \u0026lt; num_threads; ++i) threads[i]-\u0026gt;join(); 上面的代码基本展示了无锁使用 parallel_*的过程,我们会将hashval按照 submap 进行区分,如果 idx = mp.subidx(hashval) 表示这个key对应的 submap。在上述代码中,一个线程对应两个 submap可以进行插入。\n这种用法的局限性也很明显:线程数量需要是 submap 数量的倍数;一个线程一定要对应一个下标传入需要处理的函数;总之不是很好用\u0026hellip;\n(在多线程下只用map更加有效的方法或许是对于不同的key直接用不同的线程进行处理,且提前reserve防止rehash的时候迭代器失效)\nbenchmarks 以下操作的个数都为 1'000'000次,最后一列string-\u0026gt;string 插入次数为 100'000次\nvector作为baseline插入pair\u0026lt;K,V\u0026gt;进行比较\nbenchmarks(insert) insert ints insert ints(reserve first) insert string(sso)-\u0026gt;ptr insert int-\u0026gt;LargeData insert string(8)-\u0026gt;string(4096) std::vector 17ms 7ms 33ms 102ms 153ms std::unordered_map 170ms 72ms 343ms 249ms 213ms flat_hash_map 47ms 34ms 90ms 462ms 182ms node_hash_map 152ms 68ms 182ms 236ms 183ms benchmarks(read) iterate ints find int iterate string find string std::vector 0ms * 3ms * std::unordered_map 20ms 52ms 55ms 152ms flat_hash_map 3ms 23ms 8ms 71ms node_hash_map 14ms 38ms 14ms 108ms About parallel_flat_hash_map\ninserting 1000000 ints 1-thread 8-threads 8-threads flat_hash_map 31ms 23ms(no lock) 214ms(mutex manually) parallel_flat_hash_map 35ms * 70ms(mutex/shared_mutex) 一个小问题 在测试的时候发现,无论是 flat_hash_map还是 node_hash_map都没法插入 None-moveable的数据,但是 std::unordered_map可以。\nstructNonCopy { NonCopy() = default; NonCopy(NonCopyconst\u0026amp;) = delete; NonCopy\u0026amp;operator=(NonCopyconst\u0026amp;) = delete; NonCopy\u0026amp;operator=(NonCopy\u0026amp;\u0026amp; rhs) = default; NonCopy(NonCopy\u0026amp;\u0026amp; rhs) = default; }; structNonMove { NonMove() = default; NonMove(NonMoveconst\u0026amp;) = default; NonMove\u0026amp;operator=(NonMoveconst\u0026amp;) = default; NonMove(NonMove\u0026amp;\u0026amp;) = delete; NonMove\u0026amp;operator=(NonMove\u0026amp;\u0026amp;) = delete; }; template \u0026lt;K, V\u0026gt; using fmp = flat_hash_map\u0026lt;K, V\u0026gt;; template \u0026lt;K, V\u0026gt; using nodemp = node_hash_map\u0026lt;K, V\u0026gt;; TEST_CASE(\u0026#34;hash_map\u0026#34;) { fmp\u0026lt;int, NonCopy\u0026gt; mp_nocp; std::unordered_map\u0026lt;int, NonCopy\u0026gt; stdmp_nocp; nodemp\u0026lt;int, NonCopy\u0026gt; nodemp_nocp; fmp\u0026lt;int, tbs::NonMove\u0026gt; mp_nomv; std::unordered_map\u0026lt;int, NonMove\u0026gt; stdmp_nomv; nodemp\u0026lt;int, NonMove\u0026gt; nodemp_nomv; constint n = 1e6; for (int i =0; i \u0026lt; n; i++) { stdmp_nocp[i] = std::move(NonCopy()); mp_nocp[i] = std::move(NonCopy()); node_nocp[i] = std::move(NonCopy()); } for (int i =0; i \u0026lt; n; i++) { stdmp_nomv.emplace(i, NonMove()); } // the following code can\u0026#39;t pass complile // for (int i = 0; i \u0026lt; n; i++) { // nodemp_nomv.emplace(i, NonMove()); // } // for (int i = 0; i \u0026lt; n; i++) { // mp_nomv.emplace(i, NonMove()); // } } 如果说 flat_hash_map在 rehash的时候需要move所以禁止使用NoneMoveable还可以理解,但是 node_hash_map的行为应该和 unordered_map是一致,还是有这个问题。\n","permalink":"https://caaatch22.github.io/posts/phmap/","summary":"\u003cblockquote\u003e\n\u003cp\u003e众所周知,C++标准库的 \u003ccode\u003eunordered_map\u003c/code\u003e在性能上向来不是一个好的选择。开源市场上有非常多的高性能哈希表可供选择,\u003ca href=\"https://github.com/greg7mdp/parallel-hashmap\"\u003ephmap\u003c/a\u003e继承自 \u003ccode\u003eabsl-hashmap\u003c/code\u003e,有着非常好的插入、查找性能。在著名的\u003ca href=\"https://martin.ankerl.com/2022/08/27/hashmap-bench-01/#benchmark-results-table\"\u003eComprehensive C++ Hashmap Benchmarks 2022\u003c/a\u003e榜单中名列前茅。事实上,我比对了 \u003ccode\u003ephmap::flat_hash_map\u003c/code\u003e与榜单中综合性能第一的 \u003ccode\u003eankerl::unordered_dense::map\u003c/code\u003e,我的benchmark中只有遍历哈希表时,\u003ccode\u003eflat_hash_map\u003c/code\u003e的性能低于 \u003ccode\u003eunordered_dense::map\u003c/code\u003e,其余无论是插入还是随即查找,大部分情况下 \u003ccode\u003eflat_hash_map\u003c/code\u003e的性能都更优。本文简单介绍了 \u003ccode\u003eflat_hash_map\u003c/code\u003e相关情况,以及一些使用上的建议与坑点。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"flat_hash_map-和-node_hash_map区别\"\u003eflat_hash_map 和 node_hash_map区别\u003c/h3\u003e\n\u003cp\u003ephmap中有提供了两类哈希表,其内部布局示意图如下:\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/phmap/flat_hash_map-vs-node_hash_map.png\" alt=\"\" /\u003e\n\u003c/p\u003e\n\u003cp\u003e由上图(忽略了bucket的细节)可以看出,\u003ccode\u003eflat_hash_map\u003c/code\u003e的最大的优点在于\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003enode之间的内存是连续的(虽然可能中间存在空node),遍历的时候对cache更加友好\u003c/li\u003e\n\u003cli\u003e并且相比于 \u003ccode\u003enode_hash_map\u003c/code\u003e版少一次寻址过程(\u003ccode\u003estd::unordered_map\u003c/code\u003e的设计与 \u003ccode\u003enode_hash_map\u003c/code\u003e)相同。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e而 \u003ccode\u003eflat_*\u003c/code\u003e系列的缺点就是在 \u003ccode\u003erehash\u003c/code\u003e的时候:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e会引发原来的value\u003cstrong\u003e失效\u003c/strong\u003e(这里的失效指的是原来的那个对象所对应的内存失效,而不是value所包含的内容失效,例如,value是一个指针,那它的值——所指向的对象,不会受到影响)。举个例子:\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eflat_hash_map\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e, Data\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e mp;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enode_hash_map\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e, Data\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e nodemp;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emp[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e Data();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enodemp[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e Data();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e mp0 \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e mp[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e nodemp0 \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e nodemp[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// tigger rehash\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e i \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e; i \u003cspan style=\"color:#f92672\"\u003e\u0026lt;=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e10\u003c/span\u003e; i \u003cspan style=\"color:#f92672\"\u003e++\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tmp[i] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e Data();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\tnodemp[i] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e Data(); \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eassert(std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eaddressof(mp[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e]) \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eaddressof(mp0));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eassert(std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eaddressof(nodemp[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e]) \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e std\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eaddressof(nodemp0));\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e原因就是 \u003ccode\u003eflat_hash_map\u003c/code\u003e的内存布局导致的。而 \u003ccode\u003enode_hash_map\u003c/code\u003e或者 \u003ccode\u003estd::unordered_map\u003c/code\u003e就保证不会出现这种情况,因为当他们rehash的时候,只需要将bucket内的指针重新分配,指针的值还是指向原来的 \u003ccode\u003enode\u0026lt;key, value\u0026gt;\u003c/code\u003e.\u003c/p\u003e","title":"phmap —— 缓存友好的高效hashtable"},{"content":"没写记录,学弟写了,直接用\nICPC西安邀请赛\n","permalink":"https://caaatch22.github.io/posts/icpc%E8%A5%BF%E5%AE%89%E6%B8%B8%E8%AE%B0/","summary":"\u003cp\u003e\u003cdel\u003e没写记录,学弟写了,直接用\u003c/del\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://tthac09.github.io/2023/05/24/icpc2023-xian-invitational-notes/\"\u003eICPC西安邀请赛\u003c/a\u003e\u003c/p\u003e","title":"ICPC西安游记"},{"content":" 原先只是了解这个名词,想着C++20后静态多态直接用 concept来实现就好了就没细看,没必要整这些模板元编程的奇技淫巧。没想到面试某量化C++开发的时候被狠狠拷打\u0026hellip;\u0026hellip;.\nCRTP (curiously recurring template pattern) 一般认为,CRTP可以用来实现静态多态\ntemplate \u0026lt;typename T\u0026gt; class Base { void func() { static_cast\u0026lt;T*\u0026gt;(this)-\u0026gt;funcImpl(); } }; class Derived : public Base\u0026lt;Derived\u0026gt; { void funcImpl() { // do works here } }; 通过CRTP可以使得类具有类似于虚函数的效果,同时又没有虚函数调用时的开销(虚函数调用需要通过虚函数指针查找虚函数表进行调用),同时类的对象的体积相比使用虚函数也会减少(不需要存储虚函数指针),但是缺点是无法动态绑定,感觉有点过于鸡肋。\n有什么用呢?可以用来向纯虚类一样做接口:(以下类似的代码在大量数学库中出现)\ntemplate \u0026lt;typename ChildType\u0026gt; struct VectorBase { ChildType \u0026amp;underlying() { return static_cast\u0026lt;ChildType \u0026amp;\u0026gt;(*this); } inline ChildType \u0026amp;operator+=(const ChildType \u0026amp;rhs) { this-\u0026gt;underlying() = this-\u0026gt;underlying() + rhs; return this-\u0026gt;underlying(); } }; struct Vec3f : public VectorBase\u0026lt;Vec3f\u0026gt; { float x{}, y{}, z{}; Vec3f() = default; Vec3f(float x, float y, float z) : x(x), y(y), z(z) {} }; inline Vec3f operator+(const Vec3f \u0026amp;lhs, const Vec3f \u0026amp;rhs) { Vec3f result; result.x = lhs.x + rhs.x; result.y = lhs.y + rhs.y; result.z = lhs.z + rhs.z; return result; } 定义好VectorBase后,Vec3f, Vec4f等直接实现接口就好。相比虚函数的形式,在空间和runtime都有优势。\nenable_shared_from_this ? 另一个常见的用处是与 shared_ptr 配套的 std::enable_shared_from_this。若我们有一个用shared_ptr管理的资源,当我们想要接管该资源,返回一个接管 this的 shared_ptr时\n// buggy struct Bad { auto get() -\u0026gt; shared_ptr\u0026lt;Bad\u0026gt; { return std::shared_ptr\u0026lt;Bad\u0026gt;(this); } }; TEST() { auto p1 = std::make_shared\u0026lt;Bad\u0026gt;(); auto p2 = p1-\u0026gt;get(); assert(p2.use_cont() == 1); // Unexpected! } 上述代码错误的原因在于std::make_shared总是创建一个控制块,从而导致p1, p2虽然管理的对象相同,却并不知道彼此的存在。两者的 ref-cnt都是1,在析构的时候该对象会被析构两次。类似的bug还有 effective modern c++ Item19 中的例子:\nstd::vector\u0026lt;std::shared_ptr\u0026lt;Widge\u0026gt;\u0026gt; processedWidgets; class Widget { public: ... void process() { ... // process processedWidgets.emplace_back(this); // buggy! } }; 正确的做法是\nclass Widget: public std::enable_shared_from_this\u0026lt;Widget\u0026gt; { void process() { ... // process processedWidgets.emplace_back(std::shared_from_this()); } }; 而这里的解决方案就是CRTP。实现大概就是借用一个 weak_ptr,在std::shared_from_this时候利用这个weak_ptr进行新的shared_ptr的拷贝构造,从而增加引用计数。\nwith deducing this c++23 引入了deducing this 这可以“简化”我们的CRTP模式,使得我们不需要显式地传递模板参数。\n// C++17 template \u0026lt;typename Derived\u0026gt; struct add_postfix_increment { Derived operator++(int) { auto\u0026amp; self = static_cast\u0026lt;Derived\u0026amp;\u0026gt;(*this); Derived tmp(self); ++self; return tmp; } }; struct some_type : add_postfix_increment\u0026lt;some_type\u0026gt; { some_type\u0026amp; operator++() { ... } }; with deducing this\nstruct add_postfix_increment { template \u0026lt;typename Self\u0026gt; auto operator++(this Self\u0026amp;\u0026amp; self, int) { auto tmp = self; ++self; return tmp; } }; struct some_type : add_postfix_increment { some_type\u0026amp; operator++() { ... } }; 相对来说看起来自然多了。当然,deducing this的主要作用不是为了简化CRTP,具体可以看原始的proposal,结合optional\u0026lt;T\u0026gt;的实现。(另外g++13竟然还不支持deducing this :( )\n","permalink":"https://caaatch22.github.io/posts/c-crtp/","summary":"\u003cblockquote\u003e\n\u003cp\u003e原先只是了解这个名词,想着C++20后静态多态直接用 \u003ccode\u003econcept\u003c/code\u003e来实现就好了就没细看,没必要整这些模板元编程的奇技淫巧。没想到面试某量化C++开发的时候被狠狠拷打\u0026hellip;\u0026hellip;.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"crtp-curiously-recurring-template-pattern\"\u003eCRTP (\u003cstrong\u003ecuriously recurring template pattern\u003c/strong\u003e)\u003c/h2\u003e\n\u003cp\u003e一般认为,CRTP可以用来实现静态多态\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etemplate\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e T\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eBase\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003evoid\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efunc\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003estatic_cast\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eT\u003cspan style=\"color:#f92672\"\u003e*\u0026gt;\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003efuncImpl();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDerived\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003epublic\u003c/span\u003e Base\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eDerived\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003evoid\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efuncImpl\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// do works here\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e通过CRTP可以使得类具有类似于虚函数的效果,同时又没有虚函数调用时的开销(虚函数调用需要通过虚函数指针查找虚函数表进行调用),同时类的对象的体积相比使用虚函数也会减少(不需要存储虚函数指针),但是缺点是无法动态绑定,感觉有点过于鸡肋。\u003c/p\u003e\n\u003cp\u003e有什么用呢?可以用来向纯虚类一样做接口:(以下类似的代码在大量数学库中出现)\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003etemplate\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003etypename\u003c/span\u003e ChildType\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eVectorBase\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ChildType \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003eunderlying() { \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003estatic_cast\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eChildType \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026gt;\u003c/span\u003e(\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e); }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003einline\u003c/span\u003e ChildType \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eoperator\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e+=\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e ChildType \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003erhs) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eunderlying() \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eunderlying() \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e rhs;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eunderlying();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003estruct\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eVec3f\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003epublic\u003c/span\u003e VectorBase\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eVec3f\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003efloat\u003c/span\u003e x{}, y{}, z{};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Vec3f() \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edefault\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Vec3f(\u003cspan style=\"color:#66d9ef\"\u003efloat\u003c/span\u003e x, \u003cspan style=\"color:#66d9ef\"\u003efloat\u003c/span\u003e y, \u003cspan style=\"color:#66d9ef\"\u003efloat\u003c/span\u003e z) \u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e x(x), y(y), z(z) {}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einline\u003c/span\u003e Vec3f \u003cspan style=\"color:#66d9ef\"\u003eoperator\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e Vec3f \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003elhs, \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e Vec3f \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003erhs) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e Vec3f result;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e result.x \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lhs.x \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e rhs.x;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e result.y \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lhs.y \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e rhs.y;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e result.z \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e lhs.z \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e rhs.z;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e result;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e定义好VectorBase后,Vec3f, Vec4f等直接实现接口就好。相比虚函数的形式,在空间和runtime都有优势。\u003c/p\u003e","title":"C++ CRTP"},{"content":" 本博客部署在两个服务器上,其中一个的 HTTP 服务就是由本文介绍的 FalconLink 提供。你可以通过IP地址 101.34.211.126:20080 尝试访问。 项目地址 https://github.com/caaatch22/FalconLink\nOverview FalconLink是一个轻量级的高并发网络库。它封装了网络编程套接字API,将其抽象成一个易用,可拓展框架。用户只需通过设置回调函数的形式注入业务逻辑。它同时也具有 HTTP 服务请求与解析的功能。\n上图是FalconLink系统架构的一个简单概括性图示。\n采用非阻塞socket配合边缘触发,及one loop per thread的主从 reactor设计 Acceptor 是专门用于处理接受新用户连接请求的模块。它守候在监听端口。收到请求后建立 Connection 分配给 EventLoop。 FalconLink 将每个 TCP连接抽象成一个 Connection,一个 Connection对应一个连接 socket 套接字。用户可以为每一条Connection注册回调函数。 每个 EventLoop 都拥有一个 Poller。 Poller 负责监听已连接的套接字,将有事件触发的连接反馈给 EventLoop。 EventLoop是该系统的核心组件, 每个都单独运行在一个线程中. 它从 Poller 中接收到有事件触发的用户连接后, 会获取并执行它们的回调函数. ThreadPool 线程池管理着系统中有多少个 EventLoop 在运行,并调度线程,防止注册过多线程导致性能下降。 支持 HTTP(GET,HEAD)请求的解析与回复,支持挂载静态 html 文件(本博客使用FalconLink的 HTTP 服务) 实现异步logger API 使用falconlink,可以轻易且优雅的在20行内实现一个 echo server。\n#include \u0026#34;falconlink.hpp\u0026#34; int main() { falconlink::InetAddr local_address(\u0026#34;0.0.0.0\u0026#34;, 8090); falconlink::Server echo_server(local_address); echo_server .onHandle([\u0026amp;](falconlink::Connection* client_conn) { int from_fd = client_conn-\u0026gt;fd(); auto [read, exit] = client_conn-\u0026gt;recv(); if (exit) { client_conn-\u0026gt;getEventLoop()-\u0026gt;deleteConnection(from_fd); // client_conn ptr is invalid below here, do not touch it again return; } // 只有以下四行是业务逻辑 if (read) { client_conn-\u0026gt;WriteToWriteBuffer(client_conn-\u0026gt;ReadAsString()); client_conn-\u0026gt;send(); client_conn-\u0026gt;clearReadBuffer(); } }) .start(); return 0; } Build \u0026amp; Test 将代码 clone 到本地,进入主目录\nsudo sh ./build_support/pachages.sh mkdir build cd build cmake .. # Debug mode by default # or cmake -DCMAKE_BUILD_TYPE=Release .. # to use release mode make # test make build-tests make test # or you can test each file make xxx_test ./test/xxx_test http service 你可以进行使用 falconlink 搭建自己的 http 服务器。 在 build 目录下 make http_server,然后运行\n./bin/http_server [optional: port default=8090] [optional:directory default=../examples/http_server/http_resource/] 你可以替换自行 http_resource中的文件,并在浏览器中输入 localhost:{port}查看\nBenchmark 使用 webbench进行压力测试\nmake benchmark 硬件:\nIntel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz OS:Ubuntu 20.04 LTS, 4 核 CPU, 16GiB内存, 100GiB磁盘存储。 QPS: 40K\n","permalink":"https://caaatch22.github.io/posts/%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E7%BD%91%E7%BB%9C%E5%BA%93-httpserver/","summary":"\u003cblockquote\u003e\n\u003cp\u003e本博客部署在两个服务器上,其中一个的 HTTP 服务就是由本文介绍的 FalconLink 提供。你可以通过IP地址 101.34.211.126:20080 尝试访问。 项目地址 \u003ca href=\"https://github.com/caaatch22/FalconLink\"\u003ehttps://github.com/caaatch22/FalconLink\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eFalconLink\u003c/strong\u003e是一个轻量级的高并发网络库。它封装了网络编程套接字API,将其抽象成一个易用,可拓展框架。用户只需通过设置回调函数的形式注入业务逻辑。它同时也具有 HTTP 服务请求与解析的功能。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/FalconLink/falconlink-architecture.png\" alt=\"\" /\u003e\n\u003c/p\u003e\n\u003cp\u003e上图是FalconLink系统架构的一个简单概括性图示。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e采用\u003cstrong\u003e非阻塞socket\u003c/strong\u003e配合\u003cstrong\u003e边缘触发\u003c/strong\u003e,及\u003cem\u003eone loop per thread\u003c/em\u003e的主从 \u003ccode\u003ereactor\u003c/code\u003e设计\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eAcceptor\u003c/code\u003e 是专门用于处理接受新用户连接请求的模块。它守候在监听端口。收到请求后建立 \u003ccode\u003eConnection\u003c/code\u003e 分配给 EventLoop。\u003c/li\u003e\n\u003cli\u003eFalconLink 将每个 TCP连接抽象成一个 \u003ccode\u003eConnection\u003c/code\u003e,一个 \u003ccode\u003eConnection\u003c/code\u003e对应一个连接 socket 套接字。用户可以为每一条Connection注册回调函数。\u003c/li\u003e\n\u003cli\u003e每个 \u003ccode\u003eEventLoop\u003c/code\u003e 都拥有一个 \u003ccode\u003ePoller\u003c/code\u003e。 \u003ccode\u003ePoller\u003c/code\u003e 负责监听已连接的套接字,将有事件触发的连接反馈给 \u003ccode\u003eEventLoop\u003c/code\u003e。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eEventLoop\u003c/code\u003e是该系统的核心组件, 每个都单独运行在一个线程中. 它从 \u003ccode\u003ePoller\u003c/code\u003e 中接收到有事件触发的用户连接后, 会获取并执行它们的回调函数.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eThreadPool\u003c/code\u003e 线程池管理着系统中有多少个 \u003ccode\u003eEventLoop\u003c/code\u003e 在运行,并调度线程,防止注册过多线程导致性能下降。\u003c/li\u003e\n\u003cli\u003e支持 HTTP(GET,HEAD)请求的解析与回复,支持挂载静态 html 文件(本博客使用FalconLink的 HTTP 服务)\u003c/li\u003e\n\u003cli\u003e实现异步logger\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"api\"\u003eAPI\u003c/h2\u003e\n\u003cp\u003e使用falconlink,可以轻易且优雅的在20行内实现一个 \u003ccode\u003eecho server\u003c/code\u003e。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-cpp\" data-lang=\"cpp\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#include\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e\u0026#34;falconlink.hpp\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e falconlink\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eInetAddr local_address(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;0.0.0.0\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e8090\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e falconlink\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eServer echo_server(local_address);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e echo_server\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e .onHandle([\u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u003c/span\u003e](falconlink\u003cspan style=\"color:#f92672\"\u003e::\u003c/span\u003eConnection\u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e client_conn) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e from_fd \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e client_conn\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003efd();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eauto\u003c/span\u003e [read, exit] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e client_conn\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003erecv();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (exit) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e client_conn\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003egetEventLoop()\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003edeleteConnection(from_fd);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// client_conn ptr is invalid below here, do not touch it again\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e// 只有以下四行是业务逻辑\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (read) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e client_conn\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eWriteToWriteBuffer(client_conn\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eReadAsString());\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e client_conn\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003esend();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e client_conn\u003cspan style=\"color:#f92672\"\u003e-\u0026gt;\u003c/span\u003eclearReadBuffer();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e })\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e .start();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"build--test\"\u003eBuild \u0026amp; Test\u003c/h2\u003e\n\u003cp\u003e将代码 clone 到本地,进入主目录\u003c/p\u003e","title":"轻量级高并发网络库+httpserver"},{"content":"Overview 在这次实验写完后,我们已经能使用bustub-shell完成执行 sql 语句了,还是挺有成就感的。同时,TA 为我们准备了浏览器上的bustub ,方便和我们写的对比调试。你也可以使用 explain 来查看他的优化策略与执行步骤。\n这次实验的主要难点在于读代码,理清 bustub 的执行引擎的数据流以及代码中的实现。搞懂了之后各个算子的实现就很简单了(相对 B+树)。\n上图是 bustub 的整体架构。\nParser sql 语句的解析就像其他编程语言一样,同样需要翻译成比较结构化的东西。Parser 阶段会生成一个抽象语法树(AST, Abstract Syntax Tree)。 这并不是数据库核心部分,bustub 直接使用了 PostgreSQL 的 parser 库 libpg_query。\nBinder 得到 AST 后,需要将这些词语绑定到数据库实体上,这就是 Binder 的工作。例如有这样一条 sql:\nSELECT table1.y, table2.x FROM table1 INNER JOIN table2 ON table1.x = table2.y; 其中 SELECT 和 FROM 是关键字,x 和 table1 是标识符。我们可以使用 explain 来看看 binder 层(bustub-shell 未完成时也可以使用 explain):\n=== BINDER === BoundSelec { table=BoundJoin { type=Inner, left=BoundBaseTableRef { table=table1, oid=25 }, right=BoundBaseTableRef { table=table2, oid=26 }, condition=(table1.x=table2.y) }, columns=[table1.y, table2.x], groupBy=[], having=, where=, limit=, offset=, order_by=[], is_distinct=false, ctes=, } 可以看出,binder的作用就是对AST的各个节点绑定一个(物理)实体。\nPlanner 得到 Bustub AST 后,Planner 遍历这棵树,生成初步的查询计划。查询计划也是一棵树的形式。例如这条 sql:\nSELECT table1.y, table2.x FROM table1 INNER JOIN table2 ON t1.x = t2.y; 查看 explain:\n=== PLANNER === Projection { exprs=[#0.1, #0.2] } | (table1.y:INTEGER, table2.x:INTEGER) NestedLoopJoin { type=Inner, predicate=(#0.0=#1.1) } | (table1.x:INTEGER, table1.y:INTEGER, table2.x:INTEGER, table2.y:INTEGER) SeqScan { table=table1 } | (table1.x:INTEGER, table1.y:INTEGER) SeqScan { table=table2 } | (table2.x:INTEGER, table2.y:INTEGER) 上面的解释其实是树型的,如下图: 查询计划规定了数据的流向。数据从树叶流向树根,自底向上地流动,在根节点输出结果。\nOptimizer 生成查询计划后 由 Planner 得到初步的查询计划后,再将查询计划交给 Optimizer 进行修改优化,生成优化过后的最终查询计划。Optimizer 主要有两种实现方式:\nRule-based. 通过自动重写查询来避免效率低的方法。例如我们在 Task 3 中将要实现的,将 Limit + Sort 合并为 TopN。 这种 Optimizer 不需要知道数据的具体内容,仅是根据预先定义好的规则修改 Plan Node。 Cost-based. 用某种模型来预估执行计划的时间,这就需要存很多跟数据相关的数据 通过对不同模型的 cost 比较选出执行 cost 最小的。 这也会造成一些额外开销 (运行这个cost model) Bustub 的 Optimizer 采用第一种实现方式。\n一般来说,Planner 生成的是 Logical Plan Node,代表抽象的 Plan。Optimizer 则生成 Physical Plan Node,代表具体执行的 Plan。例如是 Join。在 Planner 生成的查询计划中,Join 就是 Join。在 Optimizer 生成的查询计划中,Join 会被优化成具体的 HashJoin 或 NestedIndexJoin 等等。在 Bustub 中,并不区分 Logical Plan Node 和 Physical Plan Node。Planner 会直接生成 Physical Plan Node。\nExecutor 在拿到 Optimizer 生成的具体的查询计划后,就可以生成真正执行查询计划的一系列算子了。算子也是我们在 Project 3 中需要实现的主要内容。生成算子的步骤很简单,遍历查询计划树,将树上的 PlanNode 替换成对应的 Executor。算子的执行模型也大致分为三种:\nIterator/Pipeline Model(volcano model)。每个算子都有 Init() 和 Next() 两个方法。Init() 对算子进行初始化工作。Next() 则是向下层算子请求下一条数据。当 Next() 返回 false 时,则代表下层算子已经没有剩余数据,迭代结束。火山模型一次调用只向下层算子请求一条数据,占用内存较小,但函数调用开销大。 Materialization Model. 所有算子立即计算出所有结果并返回。和 Iterator Model 相反。这种模型的弊端显而易见,当数据量较大时,内存占用很高,但减少了函数调用的开销。比较适合查询数据量较小的 OLTP workloads。\nVectorized/Batch Model. 对上面两种模型的中和,一次调用返回一批数据。利于 SIMD 加速。目前比较先进的 OLAP 数据库采用这种模型。\nBustub 采用 Iterator Model。\nMetadata 上面介绍了 sql语句执行过程,足以让我们对整个执行引擎有大体了解。但是我在做这个 lab 时候还是有很多困惑的地方。最后是迷迷糊糊的写完了才整理了下。大体上的信息包括在下图中: (图片改自这篇博客)\nTask #1 - Access Method Executors SeqScan 实现比较简单,获取 table_iter 直接遍历即可。这里说说这个plan_-\u0026gt;filter_predicate_, 他是一个AbstractExpressionRef,而一个AbstractExpression意思是一个 表达式 ,他里面最重要的两个函数是 Evaluate(const Tuple *tuple, const Schema \u0026amp;schema)和 EvaluateJoin(const Tuple *left_tuple, const Schema \u0026amp;left_schema, const Tuple *right_tuple, const Schema \u0026amp;right_schema),主要就是用来做 filter 的。例如\nselect * from table1 where x = 1; 如果任何优化都没有,那么上述语句可以解析为两层:\n从 table1 中选出所有的 tuple。 选出 x = 1的 tuple。相当于底下的 seqscan 结点会将所有 tuple 发到上一层,然后上一层再做一次 filter。但是我们通过谓词下推,可以在seqscan 时候就通过 filter_predicate_ 将需要的过滤出来,不需要的不用发给上一层。(当然,这个例子不太准确。在这个情况下本来就是一次搞定的,总之是在复杂的时候可以把谓词下推)。 这个 Evaluate 返回一个 Value,实际上如果做filter应该返回 boolean,所以需要通过filter_predicate_-\u0026gt;Evaluate(tuple, table_info_-\u0026gt;schema_).GetAs\u0026lt;bool\u0026gt;()转化一下。 另外,seqscan 实际上不会用到 filter_predicate。后面遇到需要 predicate 的地方会再强调。 Insert \u0026amp; Delete 这两个算子是唯二的写算子(实际上后面的优化过程中需要实现一个 update算子)。\n我们先看下这两个算子的行为:\nbustub\u0026gt; insert into t1 values (0, \u0026#39;🥰\u0026#39;, 10), (1, \u0026#39;🥰🥰\u0026#39;, 11), (2, \u0026#39;🥰🥰🥰\u0026#39;, 12), (3, \u0026#39;🥰🥰🥰🥰\u0026#39;, 13), (4, \u0026#39;🥰🥰🥰🥰\u0026#39;, 14); // output +-------------------------------+ | __bustub_internal.insert_rows | +-------------------------------+ | 5 | +-------------------------------+ 这两个算子他们会一直 next,然后返回一次,返回的是插入/删除 tuple 的个数(所生成的tuple)。有点像 pipeline breaker,但是由于他们一定是顶层算子,所以好像不叫 pipeline breaker。\n个人感觉这个返回个数的设计有点不是很合理,它一定要将个数转化成一个tuple返回。大概是这样的操作。\n*tuple = Tuple(std::vector\u0026lt;Value\u0026gt;(GetOutputSchema().GetColumnCount() /* ColumnCount() should be one*/, Value(TypeId::INTEGER, counter)), \u0026amp;GetOutputSchema()); 另外需要注意的就是这些 增加/删除 tuple 时,对应的索引项也需要增加/删除。\nIndexScan 这个就简单了,在 Init() 时候保存 index_iter, next() 时直接调用再自增就可以。\nTask #2 - Aggregation \u0026amp; Join Executors Aggregation aggregation 操作是一个 pipeline breaker。他会在 init 得到全部答案然后再 next 时一条一条返回。\nSimpleAggregationHashTable 维护一张哈希表,键为 AggregateKey,值为 AggregateValue,均为 std::vector\u0026lt;Value\u0026gt;。key 代表 group by 的字段的数组,value 则是需要 aggregate 的字段的数组。在下层算子传来一个 tuple 时,将 tuple 的 group by 字段和 aggregate 字段分别提取出来,调用 InsertCombine() 将 group by 和 aggregate 的映射关系存入 SimpleAggregationHashTable。若当前 hashmap 中没有 group by 的记录,则创建初值;若已有记录,则按 aggregate 规则逐一更新所有的 aggregate 字段,例如取 max/min,求 sum 等等。例如下面这条 sql:\nSELECT min(t.z), max(t.z), sum(t.z) FROM t GROUP BY t.x, t.y; group by(AggregateKey)为 {t.x, t.y},aggregate(AggregateValue)为 {t.z, t.z, t.z}。aggregate 规则为 {min, max, sum}。\n需要额外注意 count(column) 和 count(*) 的区别,以及对空值的处理。\n在 Init() 中计算出整张 hashmap 后,在 Next() 中直接利用 hashmap iterator 将结果依次取出。这里的输出形式有点奇怪,需要这样的输出: schema 已经在 GetOutputSchema() 中准备好了。\nNestedLoopJoin 我的实现比较 tricky。我在 init 时候直接把下层算子(left_executor和right_executor)所有的tuple都得到了(相当于当作 pipeline breaker),保存在两张表中。再 next时候进行匹配。这里的 left-join 和 inner-join 是需要分开实现的,可以在网上查下left-join和inner-join的区别。 在这里判断左右两个 tuple 是否 match 就需要用到plan_-\u0026gt;Predicate().EvaluateJoin(),同样要 GetAs\u0026lt;bool\u0026gt;()。\n当我们得到一个 match 后,返回前记得保存上下文,例如,你可以保存 match 的 tuple 再左表中的下标和右表中的下标,这样下次调用 next 时候,就不用重新扫描一次。\nNestedIndexJoin 在进行 equi-join 时,如果发现 JOIN ON 右边的字段上建了 index,则 Optimizer 会将 NestedLoopJoin 优化为 NestedIndexJoin。具体实现和 NestedLoopJoin 差不多,只是在尝试匹配右表 tuple 时,会拿 join key 去 B+Tree Index 里进行查询。如果查询到结果,就拿着查到的 RID 去右表获取 tuple 然后装配成结果输出。\nTask #3 - Sort + Limit Executors and Top-N Optimization sort 这个实验的 sort 无需进行外部排序,重载小于后就可以实现。就是比较的方式有点奇怪,可以类比以下写:\norder_key.second-\u0026gt;Evaluate(\u0026amp;lhs, schema).CompareLessThan(order_key.second-\u0026gt;Evaluate(\u0026amp;rhs, schema)) limit 简单,limit 限制在 plan_-\u0026gt;Getlimit()里面。\nTop-N Optimization Rule 简单,优先队列重载小于(重载方式与 sort 相同),然后截取前 n 个。\nSort + Limit As TopN 这是 Project 3 里最后一个必做的小问,也是唯一一个 Optimizer ,将 Sort + Limit 优化为 TopN。先看看 Optimizer 是如何执行优化规则的:\nauto Optimizer::OptimizeCustom(const AbstractPlanNodeRef \u0026amp;plan) -\u0026gt; AbstractPlanNodeRef { auto p = plan; p = OptimizeMergeProjection(p); p = OptimizeMergeFilterNLJ(p); p = OptimizeNLJAsIndexJoin(p); p = OptimizeNLJAsHashJoin(p); // Enable this rule after you have implemented hash join. p = OptimizeOrderByAsIndexScan(p); p = OptimizeSortLimitAsTopN(p); // what we should add return p; } 可以看到,让未经优化的原始 plan 树依次经历多条规则,来生成优化过的 plan。我们的任务就是新增一条规则。看看其他规则是怎么实现的,例如 NLJAsIndexJoin:\nauto Optimizer::OptimizeNLJAsIndexJoin(const AbstractPlanNodeRef \u0026amp;plan) -\u0026gt; AbstractPlanNodeRef { std::vector\u0026lt;AbstractPlanNodeRef\u0026gt; children; for (const auto \u0026amp;child : plan-\u0026gt;GetChildren()) { children.emplace_back(OptimizeNLJAsIndexJoin(child)); } auto optimized_plan = plan-\u0026gt;CloneWithChildren(std::move(children)); if (optimized_plan-\u0026gt;GetType() == PlanType::NestedLoopJoin) { // apply the rule and return } return optimized_plan; } 可以看到,实际上就是对 plan tree 进行后序遍历,自底向上地适用规则,改写节点。遍历到某个节点时,通过 if 语句来判断当前节点的类型是否符合我们要优化的类型,若符合则进行优化。\n大致了解如何对 plan 进行优化后,就可以开始写我们的优化规则了。需要特别注意的是,能优化为一个 TopN 算子的形式是,上层节点为 Limit,下层节点为 Sort,不能反过来。同样,我们对 plan tree 进行后续遍历,在遇到 Limit 时,判断其下层节点是否为 Sort,若为 Sort,则将这两个节点替换为一个 TopN。还是比较好实现的,只是代码看起来可能有点复杂。\nLeaderboard Task 暂时还没写,有空了补补\nAC! Resources bustub on web 课程官网 Github Repo Youtube课程视频 2022fall (如果对英文字幕有压力的话可以在 chrome 插件里下个中英文双字幕插件) ","permalink":"https://caaatch22.github.io/posts/cmu15445-project3-query-execution/","summary":"\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e在这次实验写完后,我们已经能使用bustub-shell完成执行 sql 语句了,还是挺有成就感的。同时,TA 为我们准备了\u003ca href=\"https://15445.courses.cs.cmu.edu/fall2022/bustub/\"\u003e浏览器上的bustub\u003c/a\u003e ,方便和我们写的对比调试。你也可以使用 \u003ccode\u003eexplain\u003c/code\u003e 来查看他的优化策略与执行步骤。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e这次实验的主要难点在于读代码,理清 bustub 的执行引擎的数据流以及代码中的实现。搞懂了之后各个算子的实现就很简单了(相对 B+树)。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/busTub/query_excution/project-structure.png\" alt=\"\" /\u003e\n\u003c/p\u003e\n\u003cp\u003e上图是 bustub 的整体架构。\u003c/p\u003e\n\u003ch3 id=\"parser\"\u003eParser\u003c/h3\u003e\n\u003cp\u003esql 语句的解析就像其他编程语言一样,同样需要翻译成比较结构化的东西。Parser 阶段会生成一个\u003cem\u003e抽象语法树(AST, Abstract Syntax Tree)\u003c/em\u003e。 这并不是数据库核心部分,bustub 直接使用了 PostgreSQL 的 parser 库 libpg_query。\u003c/p\u003e\n\u003ch3 id=\"binder\"\u003eBinder\u003c/h3\u003e\n\u003cp\u003e得到 AST 后,需要将这些词语绑定到数据库实体上,这就是 Binder 的工作。例如有这样一条 sql:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eSELECT\u003c/span\u003e table1.y, table2.x \u003cspan style=\"color:#66d9ef\"\u003eFROM\u003c/span\u003e table1 \u003cspan style=\"color:#66d9ef\"\u003eINNER\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eJOIN\u003c/span\u003e table2 \u003cspan style=\"color:#66d9ef\"\u003eON\u003c/span\u003e table1.x \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e table2.y;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e其中 \u003ccode\u003eSELECT\u003c/code\u003e 和 \u003ccode\u003eFROM\u003c/code\u003e 是关键字,\u003ccode\u003ex\u003c/code\u003e 和 \u003ccode\u003etable1\u003c/code\u003e 是标识符。我们可以使用 \u003ccode\u003eexplain\u003c/code\u003e 来看看 \u003ccode\u003ebinder\u003c/code\u003e 层(bustub-shell 未完成时也可以使用 explain):\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e===\u003c/span\u003e BINDER \u003cspan style=\"color:#f92672\"\u003e===\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e BoundSelec \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e table\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eBoundJoin \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eInner, left\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eBoundBaseTableRef \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e table\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003etable1, oid\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e25\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e, right\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eBoundBaseTableRef \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e table\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003etable2, oid\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e26\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e, condition\u003cspan style=\"color:#f92672\"\u003e=(\u003c/span\u003etable1.x\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003etable2.y\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e, \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e columns\u003cspan style=\"color:#f92672\"\u003e=[\u003c/span\u003etable1.y, table2.x\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e, \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e groupBy\u003cspan style=\"color:#f92672\"\u003e=[]\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e having\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e, \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e where\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e, \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e limit\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e, \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e offset\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e order_by\u003cspan style=\"color:#f92672\"\u003e=[]\u003c/span\u003e, \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e is_distinct\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003efalse, \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e ctes\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e可以看出,binder的作用就是对AST的各个节点绑定一个(物理)实体。\u003c/p\u003e","title":"CMU15445-project3 Query Excution"},{"content":"Wavelet Tree for Competitive Programming 最近在学FM-Index相关算法用于数据库,了解到Wavelet Tree这一数据结构,发现其还可以应用在算法竞赛中。网上相关中文资料比较少,权当自己做个学习笔记\n开始之前 在学习wavelet tree前,不妨看看他能解决什么样的问题。\n假设我们有一长为 $n$ 的序列 $A[0\u0026hellip;n - 1]$ 。在算法竞赛中,典型的数据量是 $n = 1e5, |A[i]| \u0026lt;= 1e9$\n区间 $[L, R)$ 中元素$x$的出现次数 区间 $[L, R)$ 中的第k小数 区间 $[L, R)$ 上 小于等于x的数的个数 \u0026hellip; 以上问题都可以通过可持久化线段树在解决。那为什么还需要wavelet tree呢,我们都知道可持久化线段树的常数很大,并且十分消耗空间,在有些苛刻的题目下可能会被卡 好吧应该都是金牌题,不是我该考虑的 。利用wavelet tree可以在$log(\\sigma)$时间内完成的同时(且优秀的常数),若使用bitvector优化空间,空间上大概比可持久化线段树少一个量级。最重要的一点是,我个人觉得他比主席树更加直观易懂。 $\\sigma$ = | $\\Sigma = {1, 2, \\cdots, \\sigma}$| (用于序列上时是值域大小)。\n用wavelet tree的缺点就是带修改操作比较难写,码量较大,一般不会在比赛时使用。\nWavelet Tree 该图给出了用序列 $A = [7, 3, 5, 6, 1, 3, 2, 7, 8, 4]$ 构建的wavelet tree的形态。对于树上的每个节点,我们会将其按照值域分成两个部分$[low, mid), [mid, high)$。通过 稳定划分(stable_partition,即不改变相对顺序的情况下划分)将该节点上的序列中小于 $mid$的划分到左子树中,大于等于mid的划分到右子树中,递归直至节点中只有一种值时为叶节点。需要注意的是,我们并不会在叶子节点中直接存储序列的值,而是通过某个方法使得我们能够使用较小的空间的情况下得到足够的信息。\n设根节点编号为 $u = 1$ ,其左子树的根节点为 $2 * u$ , 右子树的根节点为 $2 * u + 1$ ,以此类推。每个节点都对应一对左闭右开的区间 $[lo, hi)$,表示该节点中数值的值域范围。同时有一个 $mid = \\lfloor \\frac{lo + hi}{2} \\rfloor$ ,表示该节点左右子树分裂标准,即左子树中值域范围是 $[lo, mid)$ , 右子树 $[mid, hi)$ 。\n在wavelet tree中,我们实际上在维护一个二维数组vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; c,我们不妨叫他前缀计数数组,其中 c[u][i]表示的是u结点中下标为[0, i)中的数有多少个小于该节点对应的mid。另外,若$u$结点中有$n$个数,那么c[u].size() = n + 1, 我们另c[u][0] = 0。例如,下图给出了部分结点对应的 c[u][i]数组 现在,我们来看如何用这个构建好的前缀计数数组完成以下的查询问题:\nrank(int val, int pos) 该函数返回区间 $[0, pos)$ 中值为$val$的数的个数(我也不知道为什么叫rank。。。或许这个名称是由bitvector中继承而来?)。有了这个函数,我们就容易得到区间 $[i, j)$ 内某个数的出现次数,就是 $rank(val, j) - rank(val, i)$\n设 $rank_u (val, pos)$ 为结点$u$中值为val的数在 $[0, pos)$ 中的出现次数( $pos \u0026lt;= size(u)$ )$mid$为节点$u$分裂标准,我们可以得到:\n若 $val \u0026lt; mid$,则 $rank_u(val, pos) = rank_{LeftChild(u)}(val, c[u][pos])$ 若 $val \u0026gt;= mid$, $rank_u(val, pos) = rank_{RightChild(u)}(val, pos - c[u][pos])$ 如何理解上述变化呢,其实也很简单,就是要理解c[u][i]的意义,它同时也表示将u结点中下标为i的点映射到子结点中后他的位置。而映射规则为若这个数小于mid,则将其映射到左儿子的c[u][i]处;若这个数大于等于mid,则将其映射到右儿子的i-c[u][i]处 不理解的可再仔细想想c[u][i]的这两个解释之间的等价性。\n有了上述说明,我们就容易递归的完成$rank$操作。例如,假设我们需要得到 $rank_1(val = 3, pos = 7)$ -由于 $3\u0026lt;mid,c[1][7] = 4$, 则递归左子树 $rank_2(3, 4)$;\n左子树中,$3 \u0026gt;= (mid = 2), 4-c[2][4] = 3$,递归到右子树 $rank_5(3, 3)$ 右子树中,$3 \u0026gt;= (mid = 3), 3-c[5][3] = 2$,递归到右子树 右子树为叶子节点,则此时结点内的树的个数(即为上一步中 $3-c[5][3] = 2$)为$val$的个数 quantile(int k, int l, int r) 该函数返回区间 $[l, r)$间的第k小数(最小的为第一小)。我们知道,c[u][l]表示下标为结点 $u$中有多少个下标在 $[0, l)$中的数被映射到了左子树。那么,\n若c[u][r] - c[u][l] \u0026gt;= k,则区间 $[l, r)$内第k小即为左子树中的第k小。 若c[u][r] - c[u][l] \u0026lt; k,则区间 $[l, r)$内第k小即为右子树中的第k - (c[u][r] - c[u][l])小。 从而我们可以递归的进行求解。\nc数组的构建 实际上上面已经讲的差不多了,直接看代码:\n// 参数都是该结点对应序列相关 // u: 该结点编号 // begin, end: 该结点对应序列的首个,末尾迭代器 // lo, hi: 该结点对应值域为 [lo, hi) void build(iter begin, iter end, int lo, int hi, int u) { if(hi - lo == 1) { return; } int m = (lo + hi) / 2; c[u].reserve(end - begin + 1); // reverse只分配空间不进行构造,所以后面还可以push_back c[u].push_back(0); for (auto it = begin; it != end; ++it) { c[u].push_back(c[u].back() + (*it \u0026lt; m)); } // 稳定划分,将[begin, end)间的小于m的值划分到前半部分,pivot为后半部分首个迭代器 auto pivot = stable_partition(begin, end, [=](int i){return i \u0026lt; m};); build(begin, pivot, lo, m, 2 * u); build(pivot, end, m, hi, 2 * u + 1); } 到这个,我们已经可以利用没有进行空间优化的wavelet tree轻松切掉这道 可持久化线段树的模板题了,代码如下\n模板 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; struct WaveletTree { using iter = vector\u0026lt;int\u0026gt;::iterator; vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; c; const int SIGMA; WaveletTree(vector\u0026lt;int\u0026gt; a, int sigma): c(sigma*2), SIGMA(sigma) { build(a.begin(), a.end(), 0, SIGMA, 1); } void build(iter begin, iter end, int lo, int hi, int u) { if(hi - lo == 1) return; int m = (lo + hi) / 2; c[u].reserve(end - begin + 1); c[u].push_back(0); for (auto it = begin; it != end; ++it) { c[u].push_back(c[u].back() + (*it \u0026lt; m)); } auto p = stable_partition(begin, end, [=](int i) { return i \u0026lt; m; }); build(begin, p, lo, m, 2 * u); build(p, end, m, hi, 2 * u + 1); } // occurrences of val in position[0, i) int rank(int val, int i) const { if(val \u0026lt; 0 or val \u0026gt;= SIGMA) return 0; int lo = 0, hi = SIGMA, u = 1; while(hi - lo \u0026gt; 1) { int m = (lo + hi) / 2; if(val \u0026lt; m) { i = c[u][i], hi = m; u = u * 2; } else { i -= c[u][i], lo = m; u = u * 2 + 1; } } return i; } // get kth smallest number in [l, r) int quantile(int k, int l, int r) const { // assert(k \u0026gt; 0 \u0026amp;\u0026amp; k \u0026lt;= j - i); int lo = 0, hi = SIGMA, u = 1; while(hi - lo \u0026gt; 1) { int m = (lo + hi) / 2; int nl = c[u][l], nr = c[u][r]; if(k \u0026lt;= nr - nl) { r = nr, l = nl, hi = m; u = 2 * u; } else { k -= nr - nl; r -= nr, l -= nl, lo = m; u = 2 * u + 1; } } return lo; } }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); int n, q; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; q; vector\u0026lt;int\u0026gt; a(n); for (int \u0026amp;x : a) { cin \u0026gt;\u0026gt; x; } WaveletTree wt(a, *max_element(a.begin(), a.end()) + 1); while(q --) { int k, l, r; cin \u0026gt;\u0026gt; l \u0026gt;\u0026gt; r \u0026gt;\u0026gt; k; l--; cout \u0026lt;\u0026lt; wt.quantile(k, l, r) \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; } return 0; } ","permalink":"https://caaatch22.github.io/posts/%E7%AE%97%E6%B3%95%E7%AB%9E%E8%B5%9B%E4%B8%AD%E7%9A%84wavelet-tree/","summary":"\u003ch1 id=\"wavelet-tree-for-competitive-programming\"\u003eWavelet Tree for Competitive Programming\u003c/h1\u003e\n\u003cblockquote\u003e\n\u003cp\u003e最近在学\u003cem\u003eFM-Index\u003c/em\u003e相关算法用于数据库,了解到Wavelet Tree这一数据结构,发现其还可以应用在算法竞赛中。网上相关中文资料比较少,权当自己做个学习笔记\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"开始之前\"\u003e开始之前\u003c/h2\u003e\n\u003cp\u003e在学习\u003ccode\u003ewavelet tree\u003c/code\u003e前,不妨看看他能解决什么样的问题。\u003c/p\u003e\n\u003cp\u003e假设我们有一长为 $n$ 的序列 $A[0\u0026hellip;n - 1]$ 。在算法竞赛中,典型的数据量是 $n = 1e5, |A[i]| \u0026lt;= 1e9$\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e区间 $[L, R)$ 中元素$x$的\u003ccode\u003e出现次数\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e区间 $[L, R)$ 中的\u003ccode\u003e第k小数\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e区间 $[L, R)$ 上 \u003ccode\u003e小于等于x的数的个数\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u0026hellip;\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e以上问题都可以通过\u003cem\u003e可持久化线段树\u003c/em\u003e在解决。那为什么还需要wavelet tree呢,我们都知道可持久化线段树的常数很大,并且十分消耗空间,在有些苛刻的题目下可能会被卡 \u003cdel\u003e好吧应该都是金牌题,不是我该考虑的\u003c/del\u003e 。利用wavelet tree可以在$log(\\sigma)$时间内完成的同时(且优秀的常数),若使用\u003ccode\u003ebitvector\u003c/code\u003e优化空间,空间上大概比可持久化线段树少一个量级。最重要的一点是,我个人觉得他比主席树更加直观易懂。\n$\\sigma$ = | $\\Sigma = {1, 2, \\cdots, \\sigma}$| (用于序列上时是值域大小)。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e用wavelet tree的缺点就是带修改操作比较难写,码量较大,一般不会在比赛时使用。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"wavelet-tree\"\u003eWavelet Tree\u003c/h2\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/waveletTree/waveletTree.png\" alt=\"\" /\u003e\n\u003c/p\u003e\n\u003cp\u003e该图给出了用序列 $A = [7, 3, 5, 6, 1, 3, 2, 7, 8, 4]$ 构建的wavelet tree的形态。对于树上的每个节点,我们会将其按照值域分成两个部分$[low, mid), [mid, high)$。通过 \u003cstrong\u003e稳定划分\u003c/strong\u003e(stable_partition,即不改变相对顺序的情况下划分)将该节点上的序列中小于 $mid$的划分到左子树中,大于等于mid的划分到右子树中,递归直至节点中只有一种值时为叶节点。需要注意的是,我们并不会在叶子节点中直接存储序列的值,而是通过某个方法使得我们能够使用较小的空间的情况下得到足够的信息。\u003c/p\u003e","title":"将Wavelet Tree用于算法竞赛"},{"content":"Overview 这个实验应该是最难的一个实验了。。。(感觉和Project 4 —— Transaction实现的难度差不多) 另外,2022fall版本的B+ tree更是变态,因为几乎没有给任何内部的API,让人无从下手。建议到 Github repo 里找到20年的,我基本是根据里面定义的函数来实现的。\n实验材料贴心的为你分成了两个检查点,四个小任务。\nCheckpoint #1\nTask #1 - B+Tree Pages Task #2 - B+Tree Data Structure (Insertion, Deletion, Point Search) Checkpoint #2\nTask #3 - Index Iterator Task #4 - Concurrent Index Task #1 - B+Tree Pages 第一个任务算是热身,主要是搞清楚 B+ 树的一些类之间的关系。 我们知道,数据库中的索引也是数据,同样以 page 的形式被组织。我们先来看看要完成的这些类之间的关系。\nB+ tree internal page 与 B + tree leaf page 都继承自B + tree page,B + tree page 中定义了 B+ 树每个结点的一些信息。而B + Tree 这个类则是Checkpoint 1的主要对象,它对internal page 以及 leaf page 进行管理,并对外开放接口。而在内存中,internal page 与 leaf page 都属于 page 的一部分,关系如下图所示。他们就是 page 中的 data 部分。因此,每次 从 buffer pool manager 得到一个 page 后,若是将他们用作 B+树的结点,则需要对这个 data 进行释义,也就是将他强制转化为internal page 或者 leaf page。这在 C++ 中通过 reinterpret_cast 完成。\n然后就是一些getter,setter的实现。这个 MappingType array[1] 是个奇技淫巧叫flexible array member,由于整个internal page的大小是确定的(由data[buffer_size]转义而来),这个 array 的大小就是去掉 header后的大小。(另外,实际上应该写作 MappingType array[0],不然过不了 check-format)。\nTask #2 - B+Tree Data Structure (Insertion, Deletion, Point Search) 这个任务就是本实验最为核心的一点——实现基于磁盘的 B+ 树数据结构。这里不会详细展开,因为过于复杂且很多细节因为忘光了。给个20年fall的B+ tree,功能是一样的,但是提供了完善的内部接口,照着这个和书上完成要清晰不少。\ndebug TAs准备了b_plus_tree_printer工具,并且已经准备的Draw/ToString方法,善用它们将B+树可视化,更好的观察插入、删除行为是否正确。 示例:\nint step = 0; for (auto key : keys) { int64_t value = key \u0026amp; 0xFFFFFFFF; rid.Set(static_cast\u0026lt;int32_t\u0026gt;(key \u0026gt;\u0026gt; 32), value); index_key.SetFromInteger(key); tree.Insert(index_key, rid, transaction); tree.Draw(bpm, \u0026#34;xxxxx/InsertTest_step\u0026#34; + std::to_string(step++) + \u0026#34;_insert\u0026#34; + std::to_string(key) + \u0026#34;.dot\u0026#34;); } tree.Draw(bpm, \u0026#34;xxxxxx/InsertTest_step.dot\u0026#34;); 得到生成的文件后打开,可以和reference solution比较。\nTask #3 - Index Iterator 我想重点说下这个 iterator 的实现,因为在这个实验中,我调了最久的 bug 就出现在这个子任务中。(实际上已经过了lab-2的评测已经过了,是在 lab3评测时发生锁资源的问题)\n记得哪个大佬说过,如何看一个人的 C++ 水平,从他写的构造函数就可以略窥一二。C++ 的构造函数属实花里胡哨,copy ctor, copy assignment, move ctor, move assignment , 再加上 initializatier list以及模板\u0026hellip; 哪些要写,哪些应当禁止都是门学问。\n根据 RAII 的思想,C++ 的 contructor(配合dtor)肩负了管理资源的作用。这个资源不知包括内存资源,还包括锁资源等等。而对于 Index iterator 来说,每个 iterator 都带有一个隐含的读属性,并发读要求对 page 上读锁,但是我们的 iterator 又不直接管理 page 资源,需要通过传入指针(用shared_ptr最好)的方式对 page 进行操作。\n我们来看这个例子 在\nindex_iter_ = GetBPlusTreeIndex()-\u0026gt;GetBeginIterator(); 这是lab3中用到索引迭代器的一个地方的代码。注意到这会产生一个临时对象,并把它赋值给 index_iter_。如果我们在析构的时候释放了对应 page 的锁而没有写copy assignment,编译器生成的assignment是不会在赋值的时候为page上锁的。\n这个bug归根结底在于,我们的迭代器应该是 值语义(value semantic) 的(至少对于锁资源来讲)\n我们用 copy and swap idiom 来解决这个问题\nINDEX_TEMPLATE_ARGUMENTS INDEXITERATOR_TYPE::IndexIterator(BufferPoolManager *bpm, Page *page, int index, page_id_t page_id) : bpm_(bpm), page_(page), index_(index), page_id_(page_id) { if (page != nullptr) { leaf_ = reinterpret_cast\u0026lt;LeafPage *\u0026gt;(page-\u0026gt;GetData()); } else { leaf_ = nullptr; } } INDEX_TEMPLATE_ARGUMENTS INDEXITERATOR_TYPE::IndexIterator(const IndexIterator \u0026amp;rhs) : bpm_(rhs.bpm_), index_(rhs.index_), page_id_(rhs.page_id_), leaf_(rhs.leaf_) { if (page_id_ != INVALID_PAGE_ID) { page_ = bpm_-\u0026gt;FetchPage(page_id_); page_-\u0026gt;RLatch(); } } INDEX_TEMPLATE_ARGUMENTS auto INDEXITERATOR_TYPE::operator=(IndexIterator rhs) -\u0026gt; IndexIterator \u0026amp; { Swap(*this, rhs); return *this; } friend void Swap(IndexIterator \u0026amp;lhs, IndexIterator \u0026amp;rhs) { using std::swap; swap(lhs.bpm_, rhs.bpm_); swap(lhs.page_, rhs.page_); swap(lhs.index_, rhs.index_); swap(lhs.page_id_, rhs.page_id_); swap(lhs.leaf_, rhs.leaf_); } Task #4 - Index Iterator 这是并发 B+ 树的重点。我们要使此前实现的 B+ 树支持并发的 Search/Insert/Delete 操作。整棵树一把锁逻辑上来说当然是可以的,但性能肯定不行,我们需要更加细粒度的锁管理。在这里,我们会使用一种特殊的加锁方式,叫做 latch crabbing。顾名思义,就像螃蟹一样,移动一只脚,放下,移动另一只脚,再放下。基本思想是:\n先锁住 parent page, 再锁住 child page, 假设 child page 是安全的,则释放 parent page 的锁。安全指当前 page 在当前操作下一定不会发生 split/steal/merge。同时,安全对不同操作的定义是不同的,Search 时,任何节点都安全;Insert 时,判断 max size;Delete 时,判断 min size。 这么做的原因和正确性还是比较明显的。当 page 为安全的时候,当前操作仅可能改变此 page 及其 child page 的值,因此可以提前释放掉其祖先的锁来提高并发性能。\n最后是AC截图:\nResources 课程官网 Github Repo Youtube课程视频 2022fall (如果对英文字幕有压力的话可以在 chrome 插件里下个中英文双字幕插件) ","permalink":"https://caaatch22.github.io/posts/cmu15445-project2-concurrent-b-tree/","summary":"\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e这个实验应该是最难的一个实验了。。。(感觉和Project 4 —— Transaction实现的难度差不多) 另外,2022fall版本的B+ tree更是变态,因为几乎没有给任何内部的API,让人无从下手。建议到 Github repo 里找到20年的,我基本是根据里面定义的函数来实现的。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e实验材料\u003cdel\u003e贴心的\u003c/del\u003e为你分成了两个检查点,四个小任务。\u003c/p\u003e\n\u003cp\u003eCheckpoint #1\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eTask #1 - B+Tree Pages\u003c/li\u003e\n\u003cli\u003eTask #2 - B+Tree Data Structure (Insertion, Deletion, Point Search)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eCheckpoint #2\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eTask #3 - Index Iterator\u003c/li\u003e\n\u003cli\u003eTask #4 - Concurrent Index\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"task-1---btree-pages\"\u003eTask #1 - B+Tree Pages\u003c/h2\u003e\n\u003cp\u003e第一个任务算是热身,主要是搞清楚 B+ 树的一些类之间的关系。\n我们知道,数据库中的索引也是数据,同样以 page 的形式被组织。我们先来看看要完成的这些类之间的关系。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/img/busTub/B-tree/B-tree-page-relation.png\" alt=\"\" /\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eB+ tree internal page\u003c/code\u003e 与 \u003ccode\u003eB + tree leaf page\u003c/code\u003e 都继承自\u003ccode\u003eB + tree page\u003c/code\u003e,\u003ccode\u003eB + tree page\u003c/code\u003e 中定义了 B+ 树每个结点的一些信息。而B + Tree 这个类则是Checkpoint 1的主要对象,它对\u003ccode\u003einternal page\u003c/code\u003e 以及 \u003ccode\u003eleaf page\u003c/code\u003e 进行管理,并对外开放接口。而在内存中,internal page 与 leaf page 都属于 page 的一部分,关系如下图所示。他们就是 \u003ccode\u003epage\u003c/code\u003e 中的 data 部分。因此,每次\n从 buffer pool manager 得到一个 page 后,若是将他们用作 B+树的结点,则需要对这个 data 进行\u003cstrong\u003e释义\u003c/strong\u003e,也就是将他强制转化为internal page 或者 leaf page。这在 C++ 中通过 \u003ca href=\"https://en.cppreference.com/w/cpp/language/reinterpret_cast\"\u003e\u003cstrong\u003ereinterpret_cast\u003c/strong\u003e\u003c/a\u003e 完成。\u003c/p\u003e","title":"CMU15445-project2 Concurrent B+ tree"},{"content":"Buffer Pool Manager cmu15445 是一门关于数据库管理系统(DBMS)设计与实现的经典公开课,是很多dba和内核开发人员的入门课程。开课教授Andy Pavlo 非常风趣幽默,他有自己上课的DJ,他曾在浴缸里录课,且时常语出惊人。这门课的实验项目BusTub非常有挑战性,并对所有人开放评测资源。\nOverview BusTub是一个面向磁盘的 DBMS(Database Management System)。由于磁盘上的数据不支持字节粒度的访问,这就需要一个管理页的中间层,而 Andy 坚持\u0026ldquo;The OS is not your friend\u0026rdquo;, 反对使用 mmap 进行页操作,因此实验一的目标便在于通过实现 Buffer Pool Manager 主动管理磁盘中的页(page)在内存中的缓存,从而,最小化磁盘访问次数(时间上)、最大化相关数据连续(空间上)。\n这次实验有三个子任务,分别是\nTask #1: Extendible Hash Table Task #2: LRU-K Replacer Task #3: Buffer Pool Manager Instance 可拓展哈希表是这个是实验中相对独立的模块。这里不会讲它的细节,后面的两个任务中需要用到哈希表的地方我直接用std::unordered_map替代,而且效率还更高。应该是因为 Extendible Hash Table 要求线程安全,为了方便在我在每个函数入口都加了大锁。 想要 Extendible Hash Table 具体细节的可以看这个b站视频。关于它的优化,我想可以进行更细粒度的锁管理甚至写一个无锁(lockfree)的哈希表。\nBuffer Pool Manager 在后两个实验开始之前,我建议先将Task #2和Task #3的实验材料完整的看完在开始写代码。因为 replacer 和 buffer pool manager 有较大程度的耦合。很多API设计需要对照两个组件才能知道自己应该维护的数据与功能的边界。为了更符合直觉,我会先阐述 buffer pool manager 的设计,同时会穿插着 LRU-k 的API什么时候,在哪用。\nBuffer Pool Manager 维护的数据的基本单位为一段逻辑连续的字节数组,在磁盘上表现为页(page),页内部结构有自己的一些结构(包括header, content等),在这个实验中我们只关心 page_id_ (页的唯一标识), pin_conut_ 以及 is_dirty_ 。对应在内存上,我们用 frame 这个词代表 框,就是装着某个物理页的框。在代码中其实就是buffer pool管理着一大片内存 Page pages[pool_size_],但是 buffer pool 初始化时(资源获取时)我们得到的 pages 并不包含我们想要的页,它里面的数据是没有意义的,从而它是一个空的框。frame_id指的就是这个数组的下标。里面装的页有自己的page_id。另外,我们用一个哈希表page_table_保存从 页号(page id) 到 框号(frame_id) 的映射。\n管理帧的内存池大小一般来说是远小于磁盘的,因此在内存池满了后,再从磁盘加载新的页到内存池,需要某种替换策略(replacer)将一些不再使用的页踢出内存池以腾出空间。\nbuffer pool manager 的实现核心在于对所有 page 的状态的管理。每个page有四种状态:\nNot Allocated(Not Exist): 我们将不存在的 page 也当作一种状态 Allocated but Not in MEM: allocated 但是不存在于内存中(某一时刻内存装的 page 达到上线后被 evict 出内存) Unpinned and in MEM: 随时可能被 evict Pinned(definitely in MEM) 这几种状态是互斥的\n待实现函数:\nauto BufferPoolManagerInstance::NewPgImp(page_id_t *page_id) -\u0026gt; Page * auto BufferPoolManagerInstance::FetchPgImp(page_id_t page_id) -\u0026gt; Page * auto BufferPoolManagerInstance::UnpinPgImp(page_id_t page_id, bool is_dirty) -\u0026gt; bool auto BufferPoolManagerInstance::DeletePgImp(page_id_t page_id) -\u0026gt; bool 便是驱动状态机中上述状态发生改变的动作(action),(注意: AllocatePage 和 DeallocatePage不对外部公开) 状态机如下:\n每个函数声明处的注释已经非常详细的描述了函数行为了。以下列出一些我在做的时候容易困惑的点:\npage_table_维护的只是在内存中的page的page_id到frame_id的映射。也就是说它不保存 Allocated but not in MEM 的 page(因为在磁盘上的page也没有一个到 frame_id 的映射) Unpin 操作并不一定会使 page 变成 Unpinned and in MEM,因为这只是这一个 worker(一般来说是一个thread) 对该 page 进行 unpin,其他线程可能也正在读取这个 page 的内容。 只有当 某次 Unpin 操作后,它的pin_count_ 等于0时,才能让这个 page 变成 evitable的状态(在 replacer 中维护) AllocatePage 只在 NewPage中用到,DeallocatePage 只在 DeletePage 中用到 Unpin 的 is_dirty 参数为 true 时,将这个 page 的is_dirty_设置为true, 而当参数为false时,不可以将page的 is_dirty_ 设置为false! 而应该保持原 dirty 状态不变 因为 is_dirty 参数表示的只是这个线程是否对这个 page 有修改操作。(这个点害我debug了好久) 现在我们借助 replacer 的 API 来理解一下每个 page 在他的生命周期中需要被维护什么内容。\n/** * 发生在NewPage和FetchPage时,且free_list_为空 * @param[out] frame_id id of frame that is evicted. */ auto Evict(frame_id_t *frame_id) -\u0026gt; bool; /* 发生在NewPage和FetchPage中 */ void RecordAccess(frame_id_t frame_id); /** 在NewPage和FetchPage set_evictable 设置为 false * 在 Unpin后,pin_count为0下 set_evictable 设置为 true */ void SetEvictable(frame_id_t frame_id, bool set_evictable); /* 发生在DeletePage中 */ void Remove(frame_id_t frame_id); 以 FetchPageImpl 为例强调下一些实现的细节。\nauto BufferPoolManagerInstance::FetchPgImp(page_id_t page_id) -\u0026gt; Page * { // 1. 上锁! // 2. 查看 page 是否在 page_table_ 中 // 2.1 若在 // 2.1.2 得到frame_id, 目标为pages[frame_id] // 2.1.3 目标内部的pin_count状态维护 // 2.1.4 replacer 维护recordAccess和setEvictable // 2.1.5 返回 // 2.2 若不在 // 2.2.1 找一个可用的 frame (先从free list里找,没有则让 replacer evict一个) // 2.2.2 清空frame_id中原来的信息 (包括old_page dirty的话写回磁盘) // 2.2.3 维护 page_table (删除这个frame_id对应的old_page的信息) // 2.2.4 设置新的page_id,从磁盘中读入,并维护新 page 内部信息 // 2.2.5 维护 replacer 相关 // 2.2.6 维护page_table // 2.2.7 返回 } LRU-K Replacer LRU-k evict policy 是让访问次数未到 k 的结点会被优先 evict 出去(这么做或许是为了防止一些微小的 扰动访问 污染了原来经常访问的区域),然后对于访问次数到达 k 次的结点则按照最近访问时间(或逻辑上的时间戳)最久远的被 evict。(实际上按照原始论文和 slides 上的意思应该是最近访问时间和第前 k 次访问时间之差相差最大的被 evict,但是按照前面的方法也过了。。。)\nLRU-k的原始论文 leetcode上的lru算法,不熟悉的可以先试试 针对 LRU-k 算法,我实现了两种方法,简单说一下思路。\n方法一 维护两个链表,一个存未到 k 次访问的frame_id,另一个存已到(或超过)k 次访问的 frame_id,我们分别将其叫做 history_list以及cache_list。\n同时维护一个从frame_id到 FrameEntry的哈希表,这个FrameEntry保存了hit_count,evictible以及一个list\u0026lt;frame_id_t\u0026gt;的迭代器。\nEvict: 若 hist_list 非空,找到第一个 evictable 的 frame 将其踢出,否则找cache_list 中第一个evictable 的 frame。 RecordAccess(frame_id): 通过 FrameEntry 查看 hit_count (1)若他是第一次访问,则将其放在 history_list 尾部,注意,同时还要维护这个 frame_id 在FrameEntry 中的迭代器 。(2)若 new_count == k 则在history_list中删去这个frame(无需遍历,通过保存在哈希表中的迭代器进行 $\\mathcal{O}(1)$ 删除),添加到 cache_list 中,同时维护哈希表 (3)new_count \u0026gt; k,将这个 frame_id 移动到 cache_list 最后(删除和添加都可以做到 $\\mathcal{O}(1)$ ) SetEvictable 和 Remove都可以类似的操作。 方法一时间复杂度: 假设 history_list 和 cache_list 中的元素个数都是 n, 两个队列中 non-evictable 的个数都为 m,则 Evict 的时间复杂度为 $\\mathcal{O}(m)$(最差遍历 m 个元素) 其余操作时间复杂度都是 $\\mathcal{O}(1)$。需要注意的是,RecordAccess 要比 Evict 经常使用的多,且 Evict 遍历 m 个元素是在 non-evictable page 都在 evictable page 的先前访问。实际上可以看作一个常数。\n方法二 为了绝对 $\\mathcal{O}(1)$ 的 evict,我们可以使用std::set(红黑树),结点内部维护 hit_count 和 到达时间,需要重载\u0026lt;=,就可以达到。但是这种做法不仅常数大(结点之间比较需要先比较访问了k次没有,对到达k次和没到达k次的结点还需分别比较),更致命的是它使得 RecordAccess(frame_id)的复杂度变成 $\\mathcal{O}(log(n))$ (需要一次 find, 一次 delete,一次 insert)。\nSummary 这是四个实验(不算primer)中最简单的一个了,如果实在卡在某部分的话试试通过分析测试样例来得到预期行为,或者使用 gdb (可以使用 lldb 配合 vscode 代替命令行条件下进行调试)。相信我,越早学会如何调试对后面越有益。\n最后当然是 AC 截图了\nResources 课程官网 Github Repo Youtube课程视频 2022fall (如果对英文字幕有压力的话可以在 chrome 插件里下个中英文双字幕插件) ","permalink":"https://caaatch22.github.io/posts/cmu15445-project1-buffer-pool-manager/","summary":"\u003ch1 id=\"buffer-pool-manager\"\u003eBuffer Pool Manager\u003c/h1\u003e\n\u003cblockquote\u003e\n\u003cp\u003ecmu15445 是一门关于数据库管理系统(DBMS)设计与实现的经典公开课,是很多dba和内核开发人员的入门课程。开课教授Andy Pavlo 非常风趣幽默,他有自己上课的DJ,他曾在浴缸里录课,且时常语出惊人。这门课的实验项目BusTub非常有挑战性,并对所有人开放评测资源。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003eBusTub是一个面向磁盘的 DBMS(Database Management System)。由于磁盘上的数据不支持字节粒度的访问,这就需要一个管理页的中间层,而 Andy 坚持\u003ca href=\"https://db.cs.cmu.edu/mmap-cidr2022/\"\u003e\u0026ldquo;The OS is not your friend\u0026rdquo;\u003c/a\u003e, 反对使用 mmap 进行页操作,因此实验一的目标便在于通过实现 Buffer Pool Manager 主动管理磁盘中的页(page)在内存中的缓存,从而,最小化磁盘访问次数(时间上)、最大化相关数据连续(空间上)。\u003c/p\u003e\n\u003cp\u003e这次实验有三个子任务,分别是\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eTask #1: Extendible Hash Table\u003c/li\u003e\n\u003cli\u003eTask #2: LRU-K Replacer\u003c/li\u003e\n\u003cli\u003eTask #3: Buffer Pool Manager Instance\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e可拓展哈希表是这个是实验中相对独立的模块。这里不会讲它的细节,后面的两个任务中需要用到哈希表的地方我直接用std::unordered_map替代,而且效率还更高。应该是因为 Extendible Hash Table 要求线程安全,为了方便在我在每个函数入口都加了大锁。\n想要 Extendible Hash Table 具体细节的可以看这个\u003ca href=\"https://www.bilibili.com/video/BV1nV4y1N7LM/?spm_id_from=333.788\u0026amp;vd_source=11c680307875dda5d5b5c13fca2e5c57\"\u003eb站视频\u003c/a\u003e。关于它的优化,我想可以进行更细粒度的锁管理甚至写一个无锁(lockfree)的哈希表。\u003c/p\u003e\n\u003ch2 id=\"buffer-pool-manager-1\"\u003eBuffer Pool Manager\u003c/h2\u003e\n\u003cp\u003e在后两个实验开始之前,我建议先将Task #2和Task #3的\u003ca href=\"https://15445.courses.cs.cmu.edu/fall2022/project1/\"\u003e实验材料\u003c/a\u003e完整的看完在开始写代码。因为 \u003ccode\u003ereplacer\u003c/code\u003e 和 \u003ccode\u003ebuffer pool manager\u003c/code\u003e 有较大程度的耦合。很多API设计需要对照两个组件才能知道自己应该维护的数据与功能的边界。为了更符合直觉,我会先阐述 \u003ccode\u003ebuffer pool manager\u003c/code\u003e 的设计,同时会穿插着 \u003ccode\u003eLRU-k\u003c/code\u003e 的API什么时候,在哪用。\u003c/p\u003e","title":"CMU15445-project1 Buffer Pool Manager"},{"content":"vscode 插件:\nC/C++, ~ Extension Pack, Themes, InteliCode CMake Tools, CodeLLDB Vim remote ssh, docker Error Lens, GitLens user的settings.json\n\u0026#34;editor.fontFamily\u0026#34;: \u0026#34;Fira Code\u0026#34;, \u0026#34;workbench.panel.defaultLocation\u0026#34;: \u0026#34;right\u0026#34;, \u0026#34;vim.handleKeys\u0026#34;: { \u0026#34;\u0026lt;C-c\u0026gt;\u0026#34;: false, \u0026#34;\u0026lt;C-f\u0026gt;\u0026#34;: false, \u0026#34;\u0026lt;C-x\u0026gt;\u0026#34;: false, \u0026#34;\u0026lt;C-a\u0026gt;\u0026#34;: false, \u0026#34;\u0026lt;C-p\u0026gt;\u0026#34;: false, }, \u0026#34;C_Cpp.vcFormat.newLine.beforeOpenBrace.block\u0026#34;: \u0026#34;sameLine\u0026#34;, \u0026#34;C_Cpp.vcFormat.newLine.beforeOpenBrace.function\u0026#34;: \u0026#34;sameLine\u0026#34;, \u0026#34;editor.formatOnSave\u0026#34;: true, \u0026#34;C_Cpp.clang_format_fallbackStyle\u0026#34;: \u0026#34;{ BasedOnStyle: LLVM, UseTab: Never, IndentWidth: 2, TabWidth: 2}\u0026#34;, pip, python, pyenv 使用pyenv控制多个python版本\npyenv versions # 查看所有安装的python脚本 pyenv install 3.8.0 # 安装python 3.8.0 pyenv global 3.8.0 # 将3.8.0设置为全局python,对应pip也会改变 删除__pycache__\nfind . -name \u0026#34;*.pyc\u0026#34; -type f -print -exec rm -rf {} \\; about clash # start.sh nohup ./clash -d . \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 \u0026amp; #shutdown.sh ps -A | grep clash | awk \u0026#39;{print $1}\u0026#39; | xargs kill 暂时设置7890做全局代理\nexport HTTPS_PROXY=\u0026#34;http://127.0.0.1:7890\u0026#34; # unset HTTPS_PROXY docker 启动镜像 sudo docker run -it -d --name=\u0026lt;name\u0026gt; --privileged --net=host --ipc=host --gpus=all -v /opt:/opt2 repo/tag 2.进入容器的bash命令行\nsudo docker exec -it \u0026lt;name\u0026gt; bash make and run sudo docker build -t \u0026lt;name\u0026gt; -f Dockerfile . 将当前容器的镜像push到远程repo,首先确保自己登录了docker,没有登录的话先登录: docker login -u \u0026lt;用户名\u0026gt; -p \u0026lt;密码\u0026gt; sudo docker commit \u0026lt;container_id\u0026gt; \u0026lt;repo\u0026gt;/\u0026lt;tag\u0026gt; sudo docker push \u0026lt;repo\u0026gt;/\u0026lt;tag\u0026gt; 删除image之前需要先删除容器 sudo docker ps sudo docker rm \u0026lt;container\u0026gt; sudo docker image ls sudo docker rmi \u0026lt;image\u0026gt; others import torch print(*torch.__config__.show().split(\u0026#34;\\n\u0026#34;), sep=\u0026#34;\\n\u0026#34;) ","permalink":"https://caaatch22.github.io/posts/memo/","summary":"\u003ch2 id=\"vscode\"\u003evscode\u003c/h2\u003e\n\u003cp\u003e插件:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eC/C++, ~ Extension Pack, Themes, InteliCode\u003c/li\u003e\n\u003cli\u003eCMake Tools, CodeLLDB\u003c/li\u003e\n\u003cli\u003eVim\u003c/li\u003e\n\u003cli\u003eremote ssh, docker\u003c/li\u003e\n\u003cli\u003eError Lens, GitLens\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003euser的settings.json\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;editor.fontFamily\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Fira Code\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;workbench.panel.defaultLocation\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;right\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;vim.handleKeys\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026#34;\u0026lt;C-c\u0026gt;\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026#34;\u0026lt;C-f\u0026gt;\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026#34;\u0026lt;C-x\u0026gt;\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026#34;\u0026lt;C-a\u0026gt;\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026#34;\u0026lt;C-p\u0026gt;\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;C_Cpp.vcFormat.newLine.beforeOpenBrace.block\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;sameLine\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;C_Cpp.vcFormat.newLine.beforeOpenBrace.function\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;sameLine\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;editor.formatOnSave\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;C_Cpp.clang_format_fallbackStyle\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;{ BasedOnStyle: LLVM, UseTab: Never, IndentWidth: 2, TabWidth: 2}\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"pip-python-pyenv\"\u003epip, python, pyenv\u003c/h2\u003e\n\u003cp\u003e使用\u003ccode\u003epyenv\u003c/code\u003e控制多个python版本\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epyenv versions \u003cspan style=\"color:#75715e\"\u003e# 查看所有安装的python脚本\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epyenv install 3.8.0 \u003cspan style=\"color:#75715e\"\u003e# 安装python 3.8.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epyenv global 3.8.0 \u003cspan style=\"color:#75715e\"\u003e# 将3.8.0设置为全局python,对应pip也会改变\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e删除\u003ccode\u003e__pycache__\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003efind . -name \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;*.pyc\u0026#34;\u003c/span\u003e -type f -print -exec rm -rf \u003cspan style=\"color:#f92672\"\u003e{}\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"about-clash\"\u003eabout clash\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# start.sh\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enohup ./clash -d . \u0026gt;/dev/null 2\u0026gt;\u0026amp;\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e \u0026amp;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#shutdown.sh\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eps -A | grep clash | awk \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;{print $1}\u0026#39;\u003c/span\u003e | xargs kill\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e暂时设置7890做全局代理\u003c/p\u003e","title":"个人常用配置备忘"},{"content":"Computer Network introduction dominant model : bidirectional, reliable byte stream connection\nhttp: hypertext transfer protocol : designed to be a document centric way for programs to communicate. Client \u0026mdash;\u0026gt; Server model\nBit-Torrent: (peer-to-peer model) a client requests document from other clients, a single client can request from many others. these collections of collaborating clients are called swarms when a client wants to downloads a file, it first find torrent, usually using www and download using http. torrent file describes information about data file, also tells bit-torrent about the tracker (a node keeps track names of clients of the swarm)\nskype: client \u0026lt;\u0026ndash;NAT\u0026ndash;\u0026gt;client two clients request data from each other\nNAT : network address translator if you\u0026rsquo;re behind a NAT, you can open connections out to the internet, but other nodes on the internet can\u0026rsquo;t easily open connections to you.\nThe 4 layer Internet Model Network layer and link layer The Internet is made up of `event hosts`, `links` and `routers`. data is delivered in packets a packet is a self-contained unit consisting of the data we want to be delivered.\nlink layer\u0026rsquo;s job is to carry the data over one link at a time. ethernet and wifi \u0026ndash;\u0026gt; two examples of different links layers\nNetwork layer\u0026rsquo;s job is to deliver packets end to end across the internet. a packet is a collection data with header. network layer packet are called datagram.\nThe network layer is \u0026ldquo;special\u0026rdquo; we must use the internet Protocol (IP)\nIP makes a best-effort attempt to deliver our datagrams to the other end. But it make no promise IP datagrams can get lost, delivered out of order, and be corrupted. No guarantees. Transport layer the most common transport layer is TCP (transmission control protocol)\nguarantee correct in-order delivery of data some applications doesn\u0026rsquo;t need reliable delivery, it can use UDP (user datagram protocol).\nan alternative transport layer that bundles up application data and hands it to the network layer it offers no delivery guarantees at all Application they have their own protocol to define the syntax and semantics of data flowing between two end points (e.g. http, bit-torrent)\nothers IP Service model Property behavior Datagram Individually routed packets. Unreliable packet might be dropped Best effort only if necessary Connectionless No per-flow state. IP is \u0026ldquo;simple\u0026rdquo;\nfaster, lower cost to build and maintain The end-to-end principle allows a variety of reliable (or unreliable) service to be built on top make very few assumptions about link layer IP Service Model\nTried to prevent packets looping forever add a hop count field in the header of every datagram (ttl:time to live), start at a number like 128, decremented by every router passes through, when it reaches 0, IP think it be stuck in a loop then drop it. will fragment packets if they are too long. bc most links have a limit on the size of packets.(ethernet \u0026ndash; 1500bytes) uses a header checksum to reduce chances of delivering datagram to wrong destination. allows for new version of IP IPv4 32 bit addresses IPv6 128 bit addresses allows for new options to be added to header Life of a Packet three-way handshake Client \u0026mdash;\u0026ndash;sends a synchronized message(同步信息)\u0026mdash;\u0026ndash;\u0026gt; Server (synchronize, SYN)\nServer \u0026mdash;\u0026ndash;responds with a synchronized message and also acknowledges the client synchronize\u0026mdash;-\u0026gt; Client (synchronize and acknowledge, SYN/ACK)\nClient \u0026mdash;\u0026ndash;responds by acknowledging the server synchronized \u0026mdash;-\u0026gt; Server (acknowledge, ACK)\nIP addressed: like computer addresses. TCP port: tells which applications to deliver data to.\nWeb Server usually run on tcp port 80.\n","permalink":"https://caaatch22.github.io/posts/cs144-note1/","summary":"\u003ch1 id=\"computer-network\"\u003eComputer Network\u003c/h1\u003e\n\u003ch2 id=\"introduction\"\u003eintroduction\u003c/h2\u003e\n\u003cp\u003edominant model : bidirectional, reliable byte stream connection\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003ehttp\u003c/strong\u003e: hypertext transfer protocol : designed to be a document centric way for programs to communicate.\nClient \u0026mdash;\u0026gt; Server model\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eBit-Torrent\u003c/strong\u003e: (peer-to-peer model) a client requests document from other clients, a single client can request from many others.\nthese collections of collaborating clients are called \u003cstrong\u003eswarms\u003c/strong\u003e\nwhen a client wants to downloads a file, it first find \u003ccode\u003etorrent\u003c/code\u003e, usually using www and download using http.\n\u003cstrong\u003etorrent\u003c/strong\u003e file describes information about data file, also tells bit-torrent about the \u003cstrong\u003etracker\u003c/strong\u003e (a node keeps track names of clients of the swarm)\u003c/p\u003e","title":"cs144-note1"},{"content":"icpc模板 Previous Next \u0026nbsp; \u0026nbsp; / [pdf] View the PDF file here. algorithm-template Competitive Programming template\nbasic algorithm 双指针 离散化,去重 前缀和与差分 二分 单调栈 单调队列 尺取法 归并排序,快速排序,第k小 树的中心 拓扑排序 math 素数,筛素数,素性测试, 反素数 质因数分解,预处理质因数 欧拉函数 组合数 拓展欧几里得,线性同余方程 中国剩余定理 容斥原理 高斯消元,高斯异或 矩阵乘法 博弈论,Nim游戏 莫比乌斯反演 BSGS FFT 生成函数 线性基 date strcture 并查集 Sparse table Trie, 01Trie 树状数组 线段树,扫描线 树链剖分 可持久化线段树,kth number, 主席树 splay 分块 莫队 点分治 kdtree Graph Floyd bellman-ford, spfa,判负环 dijkstra, 拆点 分层图最短路 差分约束 最小生成树 prim, kruskal 瓶颈生成树 kruskal重构树 lca 二分图匹配 欧拉回路欧拉路径 强连通分量 2-sat 网络流相关 string 字符串hash KMP, 前缀函数 Z-algorithm ac自动机 SA SAM dp 01pack, 完全背包,多重背包及优化,分组背包 线性dp, LCS, LIS, 编辑距离 区间dp 字符串压缩 数位dp 状压dp 树形dp 基环树dp 单调队列优化dp 斜率优化dp Geometry 常用模板 二维凸包 github地址 https://github.com/caaatch22/algorithm-template\n","permalink":"https://caaatch22.github.io/posts/xcpc-template/","summary":"\u003ch2 id=\"icpc模板\"\u003eicpc模板\u003c/h2\u003e\n\u003cscript src= '/js/pdf-js/build/pdf.js'\u003e\u003c/script\u003e\n\n\u003cstyle\u003e\n #embed-pdf-container {\n position: relative;\n width: 100%;\n height: auto;\n min-height: 20vh;\n \n }\n \n .pdf-canvas {\n border: 1px solid black;\n direction: ltr;\n width: 100%;\n height: auto;\n display: none;\n }\n \n #the-canvas {\n border: 1px solid black;\n direction: ltr;\n width: 100%;\n height: auto;\n display: none;\n }\n \n \n .pdf-loadingWrapper {\n display: none;\n justify-content: center;\n align-items: center;\n width: 100%;\n height: 350px;\n }\n \n .pdf-loading {\n display: inline-block;\n width: 50px;\n height: 50px;\n border: 3px solid #d2d0d0;;\n border-radius: 50%;\n border-top-color: #383838;\n animation: spin 1s ease-in-out infinite;\n -webkit-animation: spin 1s ease-in-out infinite;\n }\n \n \n \n \n \n #overlayText {\n word-wrap: break-word;\n display: grid;\n justify-content: end;\n }\n \n #overlayText a {\n position: relative;\n top: 10px;\n right: 4px;\n color: #000;\n margin: auto;\n background-color: #eeeeee;\n padding: 0.3em 1em;\n border: solid 2px;\n border-radius: 12px;\n border-color: #00000030;\n text-decoration: none;\n }\n \n #overlayText svg {\n height: clamp(1em, 2vw, 1.4em);\n width: clamp(1em, 2vw, 1.4em);\n }\n \n \n \n @keyframes spin {\n to { -webkit-transform: rotate(360deg); }\n }\n @-webkit-keyframes spin {\n to { -webkit-transform: rotate(360deg); }\n }\n \u003c/style\u003e\u003cdiv class=\"embed-pdf-container\" id=\"embed-pdf-container-e21a8caf\"\u003e\n \u003cdiv class=\"pdf-loadingWrapper\" id=\"pdf-loadingWrapper-e21a8caf\"\u003e\n \u003cdiv class=\"pdf-loading\" id=\"pdf-loading-e21a8caf\"\u003e\u003c/div\u003e\n \u003c/div\u003e\n \u003cdiv id=\"overlayText\"\u003e\n \u003ca href=\"/catch22-algorithm-template.pdf\" aria-label=\"Download\" download\u003e\n \u003csvg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 18 18\"\u003e\n \u003cpath d=\"M9 13c.3 0 .5-.1.7-.3L15.4 7 14 5.6l-4 4V1H8v8.6l-4-4L2.6 7l5.7 5.7c.2.2.4.3.7.3zm-7 2h14v2H2z\" /\u003e\n \u003c/svg\u003e\n \u003c/a\u003e\n \u003c/div\u003e\n \u003ccanvas class=\"pdf-canvas\" id=\"pdf-canvas-e21a8caf\"\u003e\u003c/canvas\u003e\n\u003c/div\u003e\n\n\u003cdiv class=\"pdf-paginator\" id=\"pdf-paginator-e21a8caf\"\u003e\n \u003cbutton id=\"pdf-prev-e21a8caf\"\u003ePrevious\u003c/button\u003e\n \u003cbutton id=\"pdf-next-e21a8caf\"\u003eNext\u003c/button\u003e \u0026nbsp; \u0026nbsp;\n \u003cspan\u003e\n \u003cspan class=\"pdf-pagenum\" id=\"pdf-pagenum-e21a8caf\"\u003e\u003c/span\u003e / \u003cspan class=\"pdf-pagecount\" id=\"pdf-pagecount-e21a8caf\"\u003e\u003c/span\u003e\n \u003c/span\u003e\n \u003ca class=\"pdf-source\" id=\"pdf-source-e21a8caf\" href=\"/catch22-algorithm-template.pdf\"\u003e[pdf]\u003c/a\u003e\n\u003c/div\u003e\n\n\u003cnoscript\u003e\nView the PDF file \u003ca class=\"pdf-source\" id=\"pdf-source-noscript-e21a8caf\" href=\"/catch22-algorithm-template.pdf\"\u003ehere\u003c/a\u003e.\n\u003c/noscript\u003e\n\n\u003cscript type=\"text/javascript\"\u003e\n (function(){\n var url = '\\/catch22-algorithm-template.pdf';\n\n var hidePaginator = \"\" === \"true\";\n var hideLoader = \"\" === \"true\";\n var selectedPageNum = parseInt(\"\") || 1;\n\n \n var pdfjsLib = window['pdfjs-dist/build/pdf'];\n\n \n if (pdfjsLib.GlobalWorkerOptions.workerSrc == '')\n pdfjsLib.GlobalWorkerOptions.workerSrc = \"https:\\/\\/caaatch22.github.io\\/\" + 'js/pdf-js/build/pdf.worker.js';\n\n \n var pdfDoc = null,\n pageNum = selectedPageNum,\n pageRendering = false,\n pageNumPending = null,\n scale = 3,\n canvas = document.getElementById('pdf-canvas-e21a8caf'),\n ctx = canvas.getContext('2d'),\n paginator = document.getElementById(\"pdf-paginator-e21a8caf\"),\n loadingWrapper = document.getElementById('pdf-loadingWrapper-e21a8caf');\n\n\n \n showPaginator();\n showLoader();\n\n \n\n function renderPage(num) {\n pageRendering = true;\n \n pdfDoc.getPage(num).then(function(page) {\n var viewport = page.getViewport({scale: scale});\n canvas.height = viewport.height;\n canvas.width = viewport.width;\n\n \n var renderContext = {\n canvasContext: ctx,\n viewport: viewport\n };\n var renderTask = page.render(renderContext);\n\n \n renderTask.promise.then(function() {\n pageRendering = false;\n showContent();\n\n if (pageNumPending !== null) {\n \n renderPage(pageNumPending);\n pageNumPending = null;\n }\n });\n });\n\n \n document.getElementById('pdf-pagenum-e21a8caf').textContent = num;\n }\n\n \n\n function showContent() {\n loadingWrapper.style.display = 'none';\n canvas.style.display = 'block';\n }\n\n \n\n function showLoader() {\n if(hideLoader) return\n loadingWrapper.style.display = 'flex';\n canvas.style.display = 'none';\n }\n\n \n\n function showPaginator() {\n if(hidePaginator) return\n paginator.style.display = 'block';\n }\n\n \n\n function queueRenderPage(num) {\n if (pageRendering) {\n pageNumPending = num;\n } else {\n renderPage(num);\n }\n }\n\n \n\n function onPrevPage() {\n if (pageNum \u003c= 1) {\n return;\n }\n pageNum--;\n queueRenderPage(pageNum);\n }\n document.getElementById('pdf-prev-e21a8caf').addEventListener('click', onPrevPage);\n\n \n\n function onNextPage() {\n if (pageNum \u003e= pdfDoc.numPages) {\n return;\n }\n pageNum++;\n queueRenderPage(pageNum);\n }\n document.getElementById('pdf-next-e21a8caf').addEventListener('click', onNextPage);\n\n \n\n pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) {\n pdfDoc = pdfDoc_;\n var numPages = pdfDoc.numPages;\n document.getElementById('pdf-pagecount-e21a8caf').textContent = numPages;\n\n \n if(pageNum \u003e numPages) {\n pageNum = numPages\n }\n\n \n renderPage(pageNum);\n });\n })();\n\u003c/script\u003e\n\n\u003ch1 id=\"algorithm-template\"\u003ealgorithm-template\u003c/h1\u003e\n\u003cblockquote\u003e\n\u003cp\u003eCompetitive Programming template\u003c/p\u003e","title":"xcpc template"},{"content":"I am Mingjie Liu (刘明杰). Currently, I\u0026rsquo;m a ML/AI platform engineer in Alibaba. I\u0026rsquo;m interested in computer architecture and machine learning system.\n","permalink":"https://caaatch22.github.io/about/","summary":"\u003cp\u003eI am Mingjie Liu (刘明杰). Currently, I\u0026rsquo;m a ML/AI platform engineer in Alibaba. I\u0026rsquo;m interested in computer architecture and machine learning system.\u003c/p\u003e","title":"About Me"},{"content":" You can find my pdf version CV both in zh and en\nEducation Nanjing University of Aeronautics and Astronautics\nBachelor of Artificial Intelligence@Computer Science Dpt. GPA: 87 / 100 Relevant Courses: C++ programming language(98), DataStructure(92), Multivariate Statistical Analysis(96), Computer Architecture(92), etc. English: CET6:587 Experience Alibaba Jul. 2024 – Present\nML framework engineer | pytorch, cuda, c++, python, etc.\nEvolution Asset Management, Ltd.(AUM: above 10 billion-yuan) Jul. 2023 – Oct. 2023 Quant Developer Intern | C++20\nParticipated in the development and optimization of low-latency trading systems, as well as the development of backtesting and simulation trading platforms. Contributed to the development of K-line generation module, implementing lock-free concurrency during backtesting; utilizing Arrow —— in-memory database for read \u0026amp; write operations. Optimized the time required to backtest one day\u0026rsquo;s K-line data from approximately 60 seconds to around 10 seconds. Conducted performance analysis of various modules within the internal trading system using tools like perf, resulting in flame graph visualizations. Leveraged methods such as Cache locality, lock-free, branchless programming, SIMD to enhance performance. Implemented thread pools and other techniques to improve throughput in the backtesting system. Conducted research on various high-quality open-source libs such as parallel_hashmap and mimalloc etc. Wrote relevant docs and unit tests and applied these libraries to the internal systems. Honors and Awards Gold Medal at ICPC China National Invitation Programming Contest (Xi\u0026rsquo;an) May 2023 Bronze Medal at ICPC International Collegiate Programming Contest (Hangzhou) December 2022 National Third Prize at 2022 Group Programming Ladder Tournament(GPLT) May 2022 Second Prize at 16th NUAA Collegiate Programming Contest March 2022 Bronze Medal at Collegiate Algorithm Design \u0026amp; Programming Challenge Contest November 2021 Skills C++: C++11/17/20, STL, conan, lock-free, unittest etc. general: Git, CMake, Docker, vim, perf, clickhouse, consul framework, latex Programming languages: C++ \u0026gt; Python \u0026gt; Rust $\\approx$ Go Fundamental knowledge: Familiar with operating system principles, database designs, Linux network programming, TCP/IP principles; Familiar with modern computer architecture memory model, SIMD etc. Projects BusTub: A Disk-oriented Database Management System – C++17 Developed buffer-pool manager to maintain all page operations, using LRU-K eviction policy to fetch in and flush out pages. Designed a concurrent B+ tree as index for better range query. Deployed volcano model in queries execution which support SELECT, INSERT, DELETE, AGGREGATION, JOIN, LIMIT, TOP-N operations. Applied two-phase locking in atomic transaction supporting, and employed cycle-detect algorithm to prevent deadlock FalconLink: A lightweight high-concurrency network library – C++17 FalconLink abstracts tedious manipulations on the TCP socket into low coupling and highly extensible framework. It allows a fast custom server side setup where the business logic could be specified for each client TCP connection in the form of a callback function. Adopted reactor pattern with non-block TCP socket and Edge trigger. Every request will be handle by threadpool. Adopt Webbench as the stress testing tool, QPS reaches 40K. Implemented HTTP module, supporting parsing and response for HTTP request. Built project with CMake, UnitTest with GoogleTest, support Docker build. ToyMips: Five stage CPU base on MIPS architecture – Verilog Implemented 73 instructions, covering most MIPS32 instruction set, including arithmetic, logical, branch, storage and load, and exception-related instructions. Adopted a five-stage pipeline design of instruction fetching, decoding, execution, memory access, and write-back, which improved the CPU clock frequency. Data hazards were resolved through data forwarding, and control hazards were handled using delay slots. The design also included the coprocessor CP0 and controls for exceptions and interrupts. Built a test frame in a Linux environment using the MIPS cross-compiler tool, Makefile, Python, and shell scripts. ","permalink":"https://caaatch22.github.io/cv/","summary":"\u003cblockquote\u003e\n\u003cp\u003eYou can find my pdf version CV both in \u003ca href=\"/resume/CV.pdf\"\u003ezh\u003c/a\u003e and \u003ca href=\"/resume/CV_en.pdf\"\u003een\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"education\"\u003eEducation\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ca href=\"http://nuaa.edu.cn/\"\u003eNanjing University of Aeronautics and Astronautics\u003c/a\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eBachelor of Artificial Intelligence@Computer Science Dpt.\u003c/li\u003e\n\u003cli\u003eGPA: 87 / 100\u003c/li\u003e\n\u003cli\u003eRelevant Courses: C++ programming language(98), DataStructure(92), Multivariate Statistical Analysis(96), Computer Architecture(92), etc.\u003c/li\u003e\n\u003cli\u003eEnglish: CET6:587\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch3 id=\"experience\"\u003eExperience\u003c/h3\u003e\n\u003cp\u003eAlibaba \u003cspan style=\"float:right\"\u003eJul. 2024 – Present\u003c/span\u003e\u003c/p\u003e\n\u003cp\u003eML framework engineer | pytorch, cuda, c++, python, etc.\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003e\u003ca href=\"http://www.jhlfund.com/home\"\u003eEvolution Asset Management, Ltd.\u003c/a\u003e(AUM: above 10 billion-yuan)\nJul. 2023 – Oct. 2023\nQuant Developer Intern | C++20\u003c/p\u003e","title":"Curriculum Vitae"}]